Component patterns in React
Bài đăng này đã không được cập nhật trong 5 năm
Đặt vấn đề
Tuần vừa rồi mình tham gia một buổi Meetup
nho nhỏ và được nghe các developer
"tay to" chém gió nhiều kiến thức hay ho trong ReactJS
. Cá nhân mình nghĩ rằng, điều tối quan trọng để ta có thể tạo ra những project có design structures
tuyệt vời là hiểu rõ được điểm mạnh của React
và vận dụng triệt để nó. Điều mình đang muốn nhắc tới đó là tính component-based
trong React
😺😺
Trong bài viết này chúng ta sẽ cùng nhau tìm hiểu về các React component patterns
nhé😽
Component là cái chi chi?
Khi nhấp vào đọc vài biết này gần như phần lớn bạn nào cũng dư sức biết component là gì rồi, song, mình vẫn xin phép overview qua về conponent definication để các bạn mới sẽ nắm qua một chút ^^
Theo reactjs.org:
Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.
Lần đầu tiên npm install react
, ta sẽ có component
và các API's
của nó. Tương tự như các Javascript function
, một component
nhận props
và trả về một React element
. Đó là lý do đôi khi, React
được xem như là một declarative API. Bạn chỉ cần để nó biết (declare
) UI
trông như thế nào? Việc còn lại cứ để React
lo. 😄😄
Note:
Để hiểu rõ hơn về tính declarative
, xét một ví dụ như việc bạn đi taxi tới một điểm đến. Bạn chỉ cần nói với tài xế nơi bạn muốn tới, và bác tài xế xe sẽ đưa bạn tới nơi an toàn. Ngược lại với declarative
, bạn có xe, và chính bản thân mình, nhưng bạn phải tự lái xe tới địa điểm đó !!!
Component API’s
Khi bạn install React
, có các API's
chính đó là:
render()
state
props
context
lifecycle events
Mặc dù component
có thể sử dụng tất cả các API's
trên, song, trên thực tế, các component
được dùng chỉ một vài trong số đó.
Okay, điểm qua một chút về component
như vậy được rồi, tiếp theo ta sẽ đi vào các component patterns
trong React
. Đó là các best practise
có tác dụng phân tách tầng data - logic
và tầng UI - presentational
.
Bằng việc chia component theo mục đích sử dụng, ta có được các component tái sử dụng, dễ quản lý và kết hợp vào các component có UI phức tạp hơn.
Vào chi tiết nào 🖖🖖
Component patterns
Các component patterns phổ biến:
Container
Presentational
Higher order components (HOC’s
)Render callback
Notes: Với Container component
vs. Presentational Component
, ta có các từ khóa tương tự như Fat vs. Skinny
, Smart vs. Dumb
, Stateful vs. Pure
, Screens vs. Components
,...
Container
A container does data fetching and then renders its corresponding sub-component.
Note: Corresponding
ở đây có nghĩa là thành phần cùng tên:
Eg:
StockWidgetContainer => StockWidget
TagCloudContainer => TagCloud
Tư tưởng
Container
trả lời câu hỏi: How things work?- Có thể chứa cả
presentational
vàcontainer components
- Không có bất kì các
DOM markup
vàstyles
nào ngoại trừ một vàiwrapping div-s
- Truyền
data
vàcallbacks
chopresentational
hoặc cáccontainer component
khác (data sources
,callback sources
) Call/dispatch actions
, lấystate
trênstore
... quaHOC's
(nhưconnect()
từRedux
,createContainer()
fromRelay
, hayContainer.create()
từFlux Utils
).
Containers
là tầng xử lý data - logic và các stateful API’s, lifecycle events... Chúng ta có thể kết nối với store
quản lý state
như Redux, Flux,... sau đó truyền data || callbacks
như một props
xuống các component con. Cũng chính vì việc sử dụng, truy cập các statefull API's
nên container component
thường được tạo qua Class (ES6)
.
Ví dụ
Một số trường hợp dùng container components như UserPage, FollowersSidebar, StoryContainer, FollowedUserList...
class Greeting extends React.Component {
constructor() {
super();
this.state = {
name: "",
};
}
componentDidMount() {
// AJAX
this.setState(() => {
return {
name: "William",
};
});
}
render() {
return (
<div>
<h1>Hello! {this.state.name}</h1>
</div>
);
}
Trong ví dụ này, Greeting
là một stateful class component
.
Để Greeting
trở thành một container component
, chúng ta cần tách UI
ra khỏi component này thành một Presentational component
:
Presentational
Tư tưởng
Presentational
trả lời câu hỏi: How things look?- Có thể chứa cả
presentational
vàcontainer components
, hoặcthis.props.children
- Thường kèm các
DOM markup
và cácstyles
riêng của nó. - Không quan tâm tới các thành phần còn lại của
app
(ví dụ nhưFlux actions
hoặcstores
) haythird-party
- Chỉ nhận
data
quaprops
vàemit event
quacallbacks
, không loaddata
, không xử lý các luồng dữ liệu - Hiếm khi có
state
riêng (nếu có, thì nó thường làUI state
hơn) - Được
declare
dưới dạngfunctional components
vì chúng không dùng cácstateful API's
(state
,lifecycle hooks
, orperformance optimizations
)
Ví dụ
Trong Presentational components
thường có các props
, render
, and context
(stateless API’s
), như là stateless component
:
const GreetingCard = (props) => {
return (
<div>
<h1>Hello! {props.name}</h1>
</div>
)
}
Ngoài ra thường được dùng cho các Page, Sidebar, Story, UserInfo, List.
Như vậy, containers
gói gọn các logic
và truyền data
và callbacks
xuống presentational components
để thể hiển thị ra UI
của nó.
const GreetingCard = (props) => {
return (
<div>
<h1>{props.name}</h1>
</div>
)
}
class Greeting extends React.Component {
constructor() {
super();
this.state = {
name: "",
};
}
componentDidMount() {
// AJAX
this.setState(() => {
return {
name: "William",
};
});
}
render() {
return (
<div>
<GreetingCard name={this.state.name} />
</div>
);
}
}
Kết luận
Một component
đảm nhiệm cả vai trò fetching data
(container components) và rendering
(presentational components) chẳng có gì là sai cả, làm vậy thì ứng dụng vẫn chạy nhưng vô hình chung ta đã bỏ qua mất một vài lợi ích mà React
mang lại.
Hơn nữa, nếu bạn nào đã đọc qua cuốn Clean code thần thánh hẳn đã nghe qua Single responsibility principle
thì cũng hiểu lý do việc phân chia ra như vậy lại là một Best practise 😄😄
Việc phân chia thành phần ra container component
và presentational component
có các lợi ích như sau:
- Tách biệt rõ ràng phần xử lý logic của
component
và phầnview
, giúp ta quản lýUI
dễ dàng hơn Reusability
: Có thể tái sử dụng cácpresentational component
với cácdata sources
haycallback sources
trong nhiềucontext
khác nhauPresentational components
được xem như là “palette” củaapp
. Ta có thể để chúng ở một folder riêng để Designer dễ dàng chỉnh sửa các biến màu, fontsize... mà không đụng vàologic
củaapp
. Còn có thể test riêng trên mỗicomponent
một cách dễ dàng, sử dụng triệt để được Data structure với PropsTypes trong React- Ngoài ra, chúng ta có thể trích xuất các
layout components
như Sidebar, Page, ContextMenu và sử dụngthis.props.children
cho một số cáccontainer component
.
Bonus
Ta có một vài notes về Technicals
cần phải phân biệt rõ như:
- Stateful vs. Stateless
Container components
thường làstateful
,presentational components
thường làstateless
. Tuy nhiên, các một vài trường hợpcontainer component
vẫn có thể làstateless
haypresentational component
vẫn có thể làstateless
. (Bạn có thể hiểu rõ hơn về Stateful vs. Stateless component trong bài viết này nhé) - Classes vs. Functions
Kể từReact 0.14
,components
có thể được định nghĩa theoclasses
hoặcfunctions
. Trước đây thìfunctional components
"thua kém"class component
ở chỗ là quản lýstate
vàlifecycle
, song, bây giờ vớilifecycle hook
vẫn giúp ta có thể khai báo cáccontainer component
haypresentational component
đều được. Cá nhân mình thì thường dùngclass
cho cáccontainer component
vàfunction
cho cácpresentational component
. - Pure and Impure
Theo định nghĩa, Pure component is pure if it is guaranteed to return the same result given the same props and state.Pure components
có thể được định nghĩa quaclasses
hoặcfunctions
, nó có thể làstateful
hoặcstateless
. Một điểm nổi bật củapure components
là có tínhshallow mutations in props or state
( việcrendering performance
có thể được tối ưu qua cácshallow comparison
trongshouldComponentUpdate() hook
).
Presentational components
và container components
có thể là một trong các kiểu component
được liệt kê trên. Theo những trải nghiệm bé nhỏ của mình, presentational components
có xu hướng là stateless pure functions
, và container components
có xu hướng là stateful pure classes
.
Okay, tiếp theo mình tìm hiểu về HOC's và Render Callbacks nhé 😽😽
Higher order components (HOC’s)
Tư tưởng
Theo MDN
:
A higher order component is a function that takes a component as an argument and returns a new component.
Đây là một trong những pattern
có sức mạnh nổi bật trong cung cấp các fetching
và data
cho nhiều components
và tái sử dụng lại các logic
.
Ví dụ
Để minh họa, ta quay lại với React Router version 4
và Redux
một xíu 😉😉 :
Với React Router
có withRouter()
để kế thừa các methods
được truyền qua props
. Với Redux
ta có thể truy cập vào các actions
được truyền như một props
thông qua hàm connect({})
.
import {withRouter} from 'react-router-dom';
class App extends React.Component {
constructor() {
super();
this.state = {path: ''}
}
componentDidMount() {
let pathName = this.props.location.pathname;
this.setState(() => {
return {
path: pathName,
}
})
}
render() {
return (
<div>
<h1>Hi! I'm being rendered at: {this.state.path}</h1>
</div>
)
}
}
export default withRouter(App);
Render callbacks
Tư tưởng
Tương tự như HOC's
, render callbacks
hay render props
được sử dụng như một component
để chia sẻ, tái sử dụng các logic
.
Thông thường ta hay có xu hướng dùng các HOC's
hơn, song, có một vài điểm mạnh với render callbacks
như hạn chế namspace collision
, logic
đó xuất phát từ đâu ...
Ví dụ
Bạn có thể xem chi tiết tại đây.
Một đoạn code minh họa cho Render callbacks
:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
increment = () => {
this.setState(prevState => {
return {
count: prevState.count + 1,
};
});
};
render() {
return (
<div onClick={this.increment}>{this.props.children(this.state)}</div>
);
}
}
class App extends React.Component {
render() {
return (
<Counter>
{state => (
<div>
<h1>The count is: {state.count}</h1>
</div>
)}
</Counter>
);
}
}
Kết
Yayyyy... Vậy là chúng ta đã điểm qua một vài component patterns
trong React
rồi 🤗🤗
Mong rằng bài viết này sẽ mang lại cái nhìn tổng quan nhất cho các bạn, giúp các bạn hiểu rõ về Container - Representational Component
, HOC's
, Render callbacks
và các use cases
liên quan để có những best practise
trong project
của mình nhé ^^
Mình cảm ơn các bạn đã đọc bài chia sẻ này. Tặng mình 1 upvote
để có thêm động lực cho những bài viết sắp tới nha 😺😺
Tham khảo thêm các bài viết về Technical
tại đây ^^
Happy Coding !!!
Reference: Michael Chan's Video, Medium, Scotch
All rights reserved