React Pattern: Compound components
Bài đăng này đã không được cập nhật trong 5 năm
Compound components là 1 design pattern rất hay trong React, 1 kiểu component có thể quản líinternal state
của nó và component đó được render như thế nào thì nằm ở phía implementation
chứ không phải declaration
.
<select>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
Đây cũng là 1 compound component.
Xem ví dụ để hình dung rõ hơn nhé:
<Toggle onToggle={onToggle}>
<Toggle.On>The button is on</Toggle.On>
<Toggle.Off>The button is off</Toggle.Off>
<Toggle.Button />
</Toggle>
Ở ví dụ trên ta có:
<Toggle.On>
,<Toggle.Off>
,<Toggle.Button>
: chính là các compound components<Toggle>
là parent bao bọc các compound components- "1 cách nào đó" để có thể parent Toggle và children có thể chia sẽ chung 1
internal state
. Chúng ta sẽ sử dụng 1 số low-level React APIs để thực hiện điều này:React.children.map()
andReact.cloneElement()
Ưu điểm của pattern này là tất cả các logics của component được đóng gói bên trong <Toggle>
và các compound components của nó ( <Toggle.On>
, <Toggle.Off>
, <Toggle.Button />
) được export cùng với <Toggle>
. Từ đó khi sử dụng <Toggle>
thì người sử dụng nó sẽ có toàn quyền control được <Toggle>
sẽ render như thế nào.
Áp dụng Compound Components Pattern
Vậy chúng ta hãy thử áp dụng compound components pattern để build Toogle component ở trên nhé. 🏁
- Đầu tiên
<Toggle>
được define cơ bản như sau:
import React from 'react'
import {Switch} from '../switch'
class Toggle extends React.Component {
state = {on: false}
toggle = () =>
this.setState(
({on}) => ({on: !on}),
() => this.props.onToggle(this.state.on),
)
render() {
const {on} = this.state
return <Switch on={on} onClick={this.toggle} />
}
}
export default Toggle
-
Build các compound components:
<On>
,<Off>
,<Button>
- Ở đây mình sử dụng
static
để define các compound components bên trong<Toggle>
để giúp tối ưuencapsulation
và tường minh hơn. Vẫn có thể define ra 1 class hoặc function component riêng nhé. <On>
,<Off>
nhận vào các props làon
vàchildren
.on
: để control điều kiện render ( từ state của<Toggle>
)children:
content child của các compound components.
<Button>
, nhận vào các props làon
vàtoggle
.on
: để control điều kiện render<Switch>
( từ state của<Toggle>
)toggle:
toggle function handler ( từ method của<Toggle>
)
- Ở đây mình sử dụng
import React from 'react'
import {Switch} from '../switch'
class Toggle extends React.Component {
static On = ({on, children}) => (on ? children : null)
static Off = ({on, children}) => (on ? null : children)
static Button = ({on, toggle, ...props}) => (
<Switch on={on} onClick={toggle} {...props} />
)
/// ...
-
Truyền state từ
<Toggle>
parent xuống các child:<On>
,<Off>
,<Button>
React.children.map()
: method dùng để map các children của<Toggle>
React.cloneElement()
: method dùng để clone các child component và đây là nơi mình chia sẽ được state của<Toggle>
tới các children.- Nhiều bạn sẽ tự hỏi tại sao không sử dụng
this.props.children.map()
thì trong React khi component chỉ có 1 children thìchildren
chính là children đó chứ không trả về 1 array. Chi tiết hơn thì các bạn xem docs về hàm này nhé.
Và ta có
<Toggle>
hoàn chỉnh:
import React from 'react'
import {Switch} from '../switch'
class Toggle extends React.Component {
static On = ({on, children}) => (on ? children : null)
static Off = ({on, children}) => (on ? null : children)
static Button = ({on, toggle, ...props}) => (
<Switch on={on} onClick={toggle} {...props} />
)
state = {on: false}
toggle = () =>
this.setState(
({on}) => ({on: !on}),
() => this.props.onToggle(this.state.on),
)
render() {
return React.Children.map(this.props.children, child =>
React.cloneElement(child, {
on: this.state.on,
toggle: this.toggle,
}),
)
}
}
export default Toggle
- Sử dụng
<Toggle>
component
const onToggle = () => console.log('toggled')
<Toggle onToggle={onToggle}>
<Toggle.On>The button is on</Toggle.On>
<Toggle.Off>The button is off</Toggle.Off>
<Toggle.Button />
</Toggle>
Tận dụng Compound components chúng ta còn có thể tạo ra những highly customized
, reusable components
và clean component APIs
cho khi sử dụng. Giúp chúng ta có thể tách riêng được business logic và rendering logic.
Cám ơn các bạn đã theo dõi bài viết 😎
Nguồn tham khảo:
All rights reserved