受到我正爲Meetup準備的技術分享啓發,我打算花些時間分享下我所瞭解的React組件模式。組件是React的核心,因此瞭解如何利用它們對於構建優秀的設計結構至關重要。
什麼是組件
根據React官網的介紹,“組件機制允許你將UI切分成獨立、可複用的部分,並針對每個部分單獨考慮一些事情”
當你第一次運行npm install react時,你得到了一個東西:組件以及它的API。就像JavaScript中的函數一樣,組件接收被叫做Props的輸入並返回React元素,它描述(聲明)了用戶界面(UI)應該長什麼樣。這就是爲什麼React被稱爲具有聲明式API的原因,你只需要告訴它你想讓UI長什麼樣,剩下的事情React會幫你處理。
可以將聲明式想象成你坐出租車去某個地方,你只需告訴司機你想去哪兒,然後他/她會開車帶你去那兒。而命令式編程恰好相反,你要親自開車才能到達目的地。
組件的API
所以,當你安裝React時得到的API到底是什麼呢?它們總共有5個,分別是
render
state
props
context
lifecycle events
雖然每個組件都有完整使用上述API的能力,但你自然會發現,一些組件傾向於只使用其中的一些API,而另一些組件則只會使用另一些API。使用API種類的不同將組件們劃分爲兩類,有狀態組件和無狀態組件。有狀態組件通常使用一些狀態API:如render, state和lifecycle events,而無狀態組件則使用render, props和context。
從這裏開始,我們就要介紹組件模式了。組件模式是使用React時的最佳實踐,最初被引入時是爲了解決將數據(邏輯)層和UI(展示)層分離的問題。通過劃分組件職責,你能夠創造出複用性更強,內聚性更高的組件,這些組件可以被用來組成複雜的UI。這對於構建可擴展應用是極其重要的。
組件模式
常見的組件模式有:
容器組件
展示組件
高階組件
渲染回調
容器組件
“容器組件就是取數據,然後渲染子組件而已” —— Jason Bonta
藍色代表容器組件,灰色代表展示組件
容器組件是應用的數據(邏輯)層,它一般使用狀態API。使用生命週期函數,你可以連接到諸如Redux,Flux等狀態管理容器,並將數據和回調函數作爲props向下傳遞給子組件。容器組件的render方法是你將展示型子組件組合成UI的地方。爲了能夠訪問到所有狀態API,容器組件必須使用class的方式聲明,而不是使用函數式方法聲明。
在下面這個例子中,我們使用class的方式聲明瞭一個組件,叫做Greeting,它具有狀態,聲明周期函數componentDidMount和render方法。
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>
);
}
}
此時,該組件是有狀態組件。爲了讓Greeting成爲一個容器組件,我們可以將UI分離到一個展示組件中,我將在下面進行說明。
展示組件
展示組件使用props, render和context(無狀態API),並且可以被寫成語法純粹的函數式無狀態組件。
const GreetingCard = (props) => {
return (
<div>
<h1>{props.name}</h1>
</div>
)
}
展示組件僅能從props中獲取數據和回調函數,這些props由容器組件(父組件)提供。
藍色是展示組件,灰色是容器組件
容器組件和展示組件一起將邏輯和表現封裝在各自的組件中。
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>
);
}
}
如你所見,我已經將Greeting組件中展示相關的部分移動到了它自己的函數式展示組件中。當然,這是非常簡單的應用,對於複雜應用,這種手法是相當基礎的。
高階組件
高階組件是一種函數,它接受一個組件作爲參數,然後返回一個新的組件。
這對於爲多個組件獲取數據和複用組件邏輯是一種強有力的模式。想想react-router-v4和redux。用了react-router-v4後,你可以使用withRouter()來繼承以props形式傳遞給組件的各種方法。同樣,用了redux,你就可以使用connect({})()來訪問以props形式傳遞給組件的各種action。
高階組件有虛線表示,它是一種返回組件的函數
以下面的代碼爲例
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);
導出組件時,我將組件用react-router-v4的withRouter()方法包裹。在上方的componentDidMount()生命週期函數中,我用this.props.location.pathname中提供的值更新state。組件被withRouter()包裹之後能夠通過props訪問react-router-v4提供的方法,因此就有了this.props.location.pathname。這只是衆多例子中的一個。
渲染回調
和高階組件類似,渲染回調或渲染props被用於共享或複用組件組件邏輯。雖然很多開發者更傾向於使用高階組件來複用邏輯,但使用渲染回調依然有很多不錯的理由和優勢——Michael Jackson的《別再另寫一個高階組件》就是最好的解釋。簡而言之,渲染回調爲我們難能可貴地減少了命名空間衝突,並更好的說明了邏輯來源。
虛線表示渲染回調
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>
);
}
}
在上面的Counter這個類中,我將this.props.children嵌套在render方法中,它接收this.state作爲參數。在下面的App類中,我能夠將其他組件包裹在Counter組件內,因此也就有機會接觸Counter組件內的邏輯。渲染回調的手法在第28行,在那裏我寫了{state => ()},bam!我自動獲得了Counter組件邏輯的操作權。