目前的前端開發(fā)主流技術(shù)都已經(jīng)往組件化方向發(fā)展了,而每學(xué)一種新的框架的時候祥国,最基礎(chǔ)的部分一定是學(xué)習(xí)其組件的編寫方式昵观。這就好像學(xué)習(xí)一門新的編程語言的時候,總是要從hello world
開始一樣舌稀。而在React中啊犬,我們常用的組件編寫方式又有哪些呢?或者說各種不同的組件又可以分為幾類呢壁查?
無狀態(tài)組件
無狀態(tài)組件(Stateless Component)是最基礎(chǔ)的組件形式觉至,由于沒有狀態(tài)的影響所以就是純靜態(tài)展示的作用。一般來說睡腿,各種UI庫里也是最開始會開發(fā)的組件類別语御。如按鈕峻贮、標(biāo)簽、輸入框等应闯。它的基本組成結(jié)構(gòu)就是屬性(props)加上一個渲染函數(shù)(render)纤控。由于不涉及到狀態(tài)的更新,所以這種組件的復(fù)用性也最強(qiáng)碉纺。
const PureComponent = (props) => (
<div>
//use props
</div>
)
無狀態(tài)組件的寫法十分簡單嚼黔,比起使用傳統(tǒng)的組件定義方式,我通常就直接使用ES6語法中提供的箭頭函數(shù)來聲明這種組件形式惜辑。當(dāng)然唬涧,如果碰到稍微復(fù)雜點的,可能還會帶有生命周期的hook函數(shù)盛撑。這時候就需要用到Class Component
的寫法了碎节。
有狀態(tài)組件
在無狀態(tài)組件的基礎(chǔ)上,如果組件內(nèi)部包含狀態(tài)(state)且狀態(tài)隨著事件或者外部的消息而發(fā)生改變的時候抵卫,這就構(gòu)成了有狀態(tài)組件(Stateful Component)狮荔。有狀態(tài)組件通常會帶有生命周期(lifecycle),用以在不同的時刻觸發(fā)狀態(tài)的更新介粘。這種組件也是通常在寫業(yè)務(wù)邏輯中最經(jīng)常使用到的殖氏,根據(jù)不同的業(yè)務(wù)場景組件的狀態(tài)數(shù)量以及生命周期機(jī)制也不盡相同。
class StatefulComponent extends Component {
constructor(props) {
super(props);
this.state = {
//定義狀態(tài)
}
}
componentWillMount() {
//do something
}
componentDidMount() {
//do something
}
... //其他生命周期
render() {
return (
//render
);
}
}
容器組件
在具體的項目實踐中姻采,我們通常的前端數(shù)據(jù)都是通過Ajax請求獲取的雅采,而且獲取的后端數(shù)據(jù)也需要進(jìn)一步的做處理。為了使組件的職責(zé)更加單一慨亲,引入了容器組件(Container Component)的概念婚瓜。我們將數(shù)據(jù)獲取以及處理的邏輯放在容器組件中,使得組件的耦合性進(jìn)一步地降低刑棵。
var UserListContainer = React.createClass({
getInitialState: function() {
return {
users: []
}
},
componentDidMount: function() {
var _this = this;
axios.get('/path/to/user-api').then(function(response) {
_this.setState({users: response.data});
});
},
render: function() {
return (<UserList users={this.state.users} />);
}
});
如上面這個容器組件巴刻,就是負(fù)責(zé)獲取用戶數(shù)據(jù),然后以props
的形式傳遞給UserList
組件來渲染蛉签。容器組件也不會在頁面中渲染出具體的DOM節(jié)點胡陪,因此,它通常就充當(dāng)數(shù)據(jù)源的角色碍舍。目前很多常用的框架柠座,也都采用這種組件形式。如:React Redux的connect(), Relay的createContainer(), Flux Utils的Container.create()等乒验。
高階組件
其實對于一般的中小項目來說愚隧,你只需要用到以上的這三種組件方式就可以很好地構(gòu)造出所需的應(yīng)用了。但是當(dāng)面對復(fù)雜的需求的時候,我們往往可以利用高階組件(Higher-Order Component)編寫出可重用性更強(qiáng)的組件狂塘。那么什么是高階組件呢录煤?其實它和高階函數(shù)的概念類似,就是一個會返回組件的組件荞胡÷栌唬或者更確切地說,它其實是一個會返回組件的函數(shù)泪漂。就像這樣:
const HigherOrderComponent = (WrappedComponent) => {
return class WrapperComponent extends Component {
render() {
//do something with WrappedComponent
}
}
}
做為一個高階組件廊营,可以在原有組件的基礎(chǔ)上,對其增加新的功能和行為萝勤。我們一般希望編寫的組件盡量純凈或者說其中的業(yè)務(wù)邏輯盡量單一露筒。但是如果各種組件間又需要增加新功能,如打印日志敌卓,獲取數(shù)據(jù)和校驗數(shù)據(jù)等和展示無關(guān)的邏輯的時候慎式,這些公共的代碼就會被重復(fù)寫很多遍。因此趟径,我們可以抽象出一個高階組件瘪吏,用以給基礎(chǔ)的組件增加這些功能,類似于插件的效果蜗巧。
一個比較常見的例子是表單的校驗掌眠。
//檢驗規(guī)則,表格組件
const FormValidator = (WrappedComponent, validator, trigger) => {
getTrigger(trigger, validator) {
var originTrigger = this.props[trigger];
return function(event) {
//觸發(fā)驗證機(jī)制,更新狀態(tài)
// do something ...
originTrigger(event);
}
}
var newProps = {
...this.props,
[trigger]: this.getTrigger(trigger, validator) //觸發(fā)時機(jī),重新綁定原有觸發(fā)機(jī)制
};
return <WrappedComponent {...newProps} />
}
值得提一句幕屹,同樣是給組件增加新功能的方法蓝丙,相比于使用mixins這種方式高階組件則更加簡潔和職責(zé)更加單一。你如果使用過多個mixins的時候香嗓,狀態(tài)污染就十分容易發(fā)生迅腔,以及你很難從組件的定義上看出隱含在mixins中的邏輯。而高階組件的處理方式則更加容易維護(hù)靠娱。
另一方面,ES7中新的語法Decorator
也可以用來實現(xiàn)和上面寫法一樣的效果掠兄。
function LogDecorator(msg) {
return (WrappedComponent) => {
return class LogHoc extends Component {
render() {
// do something with this component
console.log(msg);
<WrappedComponent {...this.props} />
}
}
}
}
@LogDecorator('hello world')
class HelloComponent extends Component {
render() {
//...
}
}
Render Callback組件
還有一種組件模式是在組件中使用渲染回調(diào)的方式像云,將組件中的渲染邏輯委托給其子組件。就像這樣:
import { Component } from "react";
class RenderCallbackCmp extends Component {
constructor(props) {
super(props);
this.state = {
msg: "hello"
};
}
render() {
return this.props.children(this.state.msg);
}
}
const ParentComponent = () =>
(<RenderCallbackCmp>
{msg =>
//use the msg
<div>
{msg}
</div>}
</RenderCallbackCmp>);
父組件獲取了內(nèi)部的渲染邏輯蚂夕,因此在需要控制渲染機(jī)制時可以使用這種組件形式迅诬。
總結(jié)
以上這些組件編寫模式基本上可以覆蓋目前工作中所需要的模式。在寫一些復(fù)雜的框架組件的時候婿牍,仔細(xì)設(shè)計和研究組件間的解耦和組合方式侈贷,能夠使后續(xù)的項目可維護(hù)性大大增強(qiáng)。
參考文檔
React Patterns - Render Callback
React Higher Order Components in depth