在使用React中,你是否會出現過一個檔案的程式碼很多,既存在應用資料的讀取和處理,又存在資料的顯示,而且每個元件還不能複用。
首先我們來看一個容器元件和展示元件一起的例子吧。
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>)
}
}
複製程式碼
大家可以看到這個例子是沒有辦法複用的,因為資料的請求和資料的展示都在一個元件進行,要實現元件的複用,我們就需要將展示元件和容器元件分離出來。
具體程式碼如下:
//展示元件
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>)
}
}
複製程式碼
當我們把元件分離成容器元件和展示元件這兩類時,你會發現他們能夠很方便的實現複用。
展示元件(Presentational Component)
- 關注頁面的展示效果(外觀)
- 內部可以包含展示元件和容器元件,通常會包含一些自己的DOM標記和樣式(style)
- 通常允許通過this.props.children方式來包含其他元件。
- 對應用程式的其他部分沒有依賴關係,例如Flux操作或store。
- 不用關心資料是怎麼載入和變動的。
- 只能通過props的方式接收資料和進行回撥(callback)操作。
- 很少擁有自己的狀態,即使有也是用於展示UI狀態的。
- 會被寫成函式式元件除非該元件需要自己的狀態,生命週期或者做一些效能優化。
Example:
Page,Header,Sidebar,UserInfo,List
容器元件(Container Component)
- 關注應用的是如何工作的
- 內部可以包含容器元件和展示元件,但通常沒有任何自己的DOM標記,除了一些包裝divs,並且從不具有任何樣式。
- 提供資料和行為給其他的展示元件或容器元件。
- 呼叫Flux操作並將它們作為回撥函式提供給展示元件。
- 往往是有狀態的,因為它們傾向於作為資料來源
- 通常使用高階元件生成,例如React Redux的connect(),Relay的createContainer()或Flux Utils的Container.create(),而不是手工編寫。
Example:
UserPage, FollowersSidebar, StoryContainer, FollowedUserList
優點(benifit)
- 展示和容器元件更好的分離,有助於更好的理解應用和UI
- 重用性高,展示元件可以用於多個不同資料來源。
- 展示元件就是你的調色盤,可以把他們放到單獨的頁面,在不影響應用程式的情況下,讓設計師調整UI。
- 這迫使您提取諸如側邊欄,頁面,上下文選單等“佈局元件”並使用this.props.children,而不是在多個容器元件中複製相同的標記和佈局。
何時引入容器元件
我建議你從開始建立元件時只使用展示元件,到最後會意識到你傳遞了很多props到中間元件,而這些中間元件根本不會用到他們接收到的這些props,僅僅是向下傳遞。而當這些子元件需要更多的資料時,你需要從新配置這些中間元件。這個時候就需要引入容器元件了。使用容器元件的方式,您可以將資料和行為通過props傳遞給葉子元件,而不必麻煩一些不相關的中間元件。
這是一個重構的過程,所以不比在第一次就做對。當你嘗試這種模式。在何時應抽取為容器元件你將會有一種直觀的感覺。就像您知道何時抽取函式一樣
二分法
重要的是你需要明白容器元件和展示元件之間不是技術上的區別,而是目的上的區別。
相比之下,這裡有幾個相關的(但不同的)技術區別:
-
有狀態【Stateful】和 無狀態【Stateless】:
容器元件傾向於有狀態,展示元件傾向於無狀態,這不是硬性規定,因為容器元件和展示元件都可以是有狀態的。
-
類【Classes】 和 函式【Functions】:
元件可以被申明成類或函式,函式元件定義簡單,但是他缺乏目前僅用於類的一些功能。雖然函式元件有很多限制,但是直到現在還有人使用,是因為函式元件容易理解,建議在不需要自己的state,lifecycle hooks,或效能優化的情況下使用函式元件。這些僅適用於類元件。
//我們將上邊的展示元件改寫成函式元件可以如下
function TodoList(props){
return (<div>
<ul>
{props.todos.map((item,index)=>{
return <li key={item.id}>{item.name}</li>
})}
</ul>
</div>)
}
複製程式碼
可能很多人不清楚函式元件和類元件的區別,可以去React的官網看一下函式元件和類元件
-
純粹【Pure】 和 不純粹 【Impure】:
純粹:輸入什麼就輸出什麼,不會再其中做相應的變動。可以被定義為類或函式,可以是無狀態或有狀態的,純元件的另一個重要方面是它們不依賴props或state中的深度變動,所以他們的渲染效能可以通過在shouldComponentUpdate()鉤子中進行淺層比較來優化,當前只有類可以定義shouldComponentUpdate()方法。
不管是展示元件還是容器元件都會有上面的二分特性。在我看來,展示元件往往是沒有狀態的純函式,而容器元件往往是有狀態的純類,這僅僅個人觀點並非規則。
當不重要或說很難分清時,不要把分離容器元件和展示元件當做教條,
如果你不確定該元件是容器元件還是展示元件是,就暫時不要分離,寫成展示元件吧。
譯文來源:https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0