在使用React中尚揣,你是否會出現(xiàn)過一個文件的代碼很多,既存在應(yīng)用數(shù)據(jù)的讀取和處理掖举,又存在數(shù)據(jù)的顯示快骗,而且每個組件還不能復(fù)用。
首先我們來看一個容器組件和展示組件一起的例子吧塔次。
class TodoList extends React.Component{
constructor(props){
super(props);
this.state ={
todos:[]
}
this.fetchData = this.fetchData.bind(this);
}
componentDidMount(){
this.fetchData();
}
fetchData(){
fetch('/api/todos').then(data =>{
this.setState({
todos:data
})
})
}
render(){
const {todos} = this.state;
return (<div>
<ul>
{todos.map((item,index)=>{
return <li key={item.id}>{item.name}</li>
})}
</ul>
</div>)
}
}
大家可以看到這個例子是沒有辦法復(fù)用的方篮,因為數(shù)據(jù)的請求和數(shù)據(jù)的展示都在一個組件進行,要實現(xiàn)組件的復(fù)用励负,我們就需要將展示組件和容器組件分離出來藕溅。
具體代碼如下:
//展示組件
class TodoList extends React.Component{
constructor(props){
super(props);
}
render(){
const {todos} = this.props;
return (<div>
<ul>
{todos.map((item,index)=>{
return <li key={item.id}>{item.name}</li>
})}
</ul>
</div>)
}
//容器組件
class TodoListContainer extends React.Component{
constructor(props){
super(props);
this.state = {
todos:[]
}
this.fetchData = this.fetchData.bind(this);
}
componentDidMount(){
this.fetchData();
}
fetchData(){
fetch('/api/todos').then(data =>{
this.setState({
todos:data
})
})
}
render(){
return (<div>
<TodoList todos={this.state.todos} />
</div>)
}
}
當(dāng)我們把組件分離成容器組件和展示組件這兩類時,你會發(fā)現(xiàn)他們能夠很方便的實現(xiàn)復(fù)用继榆。
展示組件(Presentational Component)
- 關(guān)注頁面的展示效果(外觀)
- 內(nèi)部可以包含展示組件和容器組件巾表,通常會包含一些自己的DOM標(biāo)記和樣式(style)
- 通常允許通過this.props.children方式來包含其他組件。
- 對應(yīng)用程序的其他部分沒有依賴關(guān)系略吨,例如Flux操作或store集币。
- 不用關(guān)心數(shù)據(jù)是怎么加載和變動的。
- 只能通過props的方式接收數(shù)據(jù)和進行回調(diào)(callback)操作翠忠。
- 很少擁有自己的狀態(tài)鞠苟,即使有也是用于展示UI狀態(tài)的。
- 會被寫成函數(shù)式組件除非該組件需要自己的狀態(tài)秽之,生命周期或者做一些性能優(yōu)化当娱。
Example:
Page,Header,Sidebar,UserInfo,List
容器組件(Container Component)
- 關(guān)注應(yīng)用的是如何工作的
- 內(nèi)部可以包含容器組件和展示組件考榨,但通常沒有任何自己的DOM標(biāo)記跨细,除了一些包裝divs,并且從不具有任何樣式董虱。
- 提供數(shù)據(jù)和行為給其他的展示組件或容器組件扼鞋。
- 調(diào)用Flux操作并將它們作為回調(diào)函數(shù)提供給展示組件造成。
- 往往是有狀態(tài)的掉盅,因為它們傾向于作為數(shù)據(jù)源
- 通常使用高階組件生成挽放,例如React Redux的connect(),Relay的createContainer()或Flux Utils的Container.create()淫半,而不是手工編寫。
Example:
UserPage, FollowersSidebar, StoryContainer, FollowedUserList
優(yōu)點(benifit)
- 展示和容器組件更好的分離匣砖,有助于更好的理解應(yīng)用和UI
- 重用性高科吭,展示組件可以用于多個不同數(shù)據(jù)源昏滴。
- 展示組件就是你的調(diào)色板,可以把他們放到單獨的頁面对人,在不影響應(yīng)用程序的情況下谣殊,讓設(shè)計師調(diào)整UI。
- 這迫使您提取諸如側(cè)邊欄牺弄,頁面姻几,上下文菜單等“布局組件”并使用this.props.children,而不是在多個容器組件中復(fù)制相同的標(biāo)記和布局势告。
何時引入容器組件
我建議你從開始創(chuàng)建組件時只使用展示組件蛇捌,到最后會意識到你傳遞了很多props到中間組件,而這些中間組件根本不會用到他們接收到的這些props咱台,僅僅是向下傳遞。而當(dāng)這些子組件需要更多的數(shù)據(jù)時回溺,你需要從新配置這些中間組件春贸。這個時候就需要引入容器組件了遗遵。使用容器組件的方式,您可以將數(shù)據(jù)和行為通過props傳遞給葉子組件瓮恭,而不必麻煩一些不相關(guān)的中間組件雄坪。
這是一個重構(gòu)的過程屯蹦,所以不必在第一次就做對。當(dāng)你嘗試這種模式登澜。在何時應(yīng)抽取為容器組件你將會有一種直觀的感覺阔挠。就像您知道何時抽取函數(shù)一樣
二分法
重要的是你需要明白容器組件和展示組件之間不是技術(shù)上的區(qū)別脑蠕,而是目的上的區(qū)別购撼。
相比之下,這里有幾個相關(guān)的(但不同的)技術(shù)區(qū)別:
- 有狀態(tài)【Stateful】和 無狀態(tài)【Stateless】:
容器組件傾向于有狀態(tài)谴仙,展示組件傾向于無狀態(tài)迂求,這不是硬性規(guī)定,因為容器組件和展示組件都可以是有狀態(tài)的晃跺。
- 類【Classes】 和 函數(shù)【Functions】:
組件可以被申明成類或函數(shù)揩局,函數(shù)組件定義簡單,但是他缺乏目前僅用于類的一些功能掀虎。雖然函數(shù)組件有很多限制凌盯,但是直到現(xiàn)在還有人使用付枫,是因為函數(shù)組件容易理解,建議在不需要自己的state,lifecycle hooks,或性能優(yōu)化的情況下使用函數(shù)組件驰怎。這些僅適用于類組件阐滩。
//我們將上邊的展示組件改寫成函數(shù)組件可以如下
function TodoList(props){
return (<div>
<ul>
{props.todos.map((item,index)=>{
return <li key={item.id}>{item.name}</li>
})}
</ul>
</div>)
}
可能很多人不清楚函數(shù)組件和類組件的區(qū)別,可以去React的官網(wǎng)看一下函數(shù)組件和類組件
- 純粹【Pure】 和 不純粹 【Impure】:
純粹:輸入什么就輸出什么县忌,不會再其中做相應(yīng)的變動掂榔。可以被定義為類或函數(shù)芹枷,可以是無狀態(tài)或有狀態(tài)的衅疙,純組件的另一個重要方面是它們不依賴props或state中的深度變動,所以他們的渲染性能可以通過在shouldComponentUpdate()鉤子中進行淺層比較來優(yōu)化鸳慈,當(dāng)前只有類可以定義shouldComponentUpdate()方法饱溢。
不管是展示組件還是容器組件都會有上面的二分特性。在我看來走芋,展示組件往往是沒有狀態(tài)的純函數(shù)绩郎,而容器組件往往是有狀態(tài)的純類,這僅僅個人觀點并非規(guī)則翁逞。
當(dāng)不重要或說很難分清時,不要把分離容器組件和展示組件當(dāng)做教條肋杖,
如果你不確定該組件是容器組件還是展示組件是,就暫時不要分離挖函,寫成展示組件吧状植。
譯文來源:https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0