github 的地址 歡迎 star
前言
在我為一次會議準備技術演講的過程中,我想花點時間和大家分享我在設計 React 元件模式上一些感悟。元件是 React 的核心,理解它對於設計一個良好的專案架構是十分重要的。
文中的圖表是從一個非常棒的演講(來自 Michael Chan)得到的。我強烈推薦去看他的視訊
什麼是元件
根據 React 官網的介紹,元件可以讓你把 UI 劃分為獨立,可複用的部件,你只需獨立的考慮每個部件的構建設計。
當你第一次執行 npm install react
,在本地就可以獲取到 React 原始碼(路徑./node_modules/react/umd/react.development.js
),它可以看做一個大的元件,提供了一系列的介面。React 元件和 JavaScript 函式是類似,元件接受一個稱為 “props” 的輸入,返回描述(宣告)使用者介面的 React 元素。你只需要告訴 React 你使用者介面的樣子,React 就會幫你把剩下的事情完成(保持 DOM 和資料同步),渲染出介面。這也是 React 被稱為宣告式庫的原因。
宣告式就是假如你要去一個地方的時候,你選擇了打的,只需要告訴司機你的目的地,司機就會自己帶你到目的地。而命令式是相反的,是需要你自己駕車去目的地的。
元件的 API
那麼,當你下載 React,得到了哪些 API 呢?它們有5個:
- render
- state
- props
- context
- lifecycle events
雖然元件提供了一份完整,方便利用的 API,但很自然的一些元件,你會使用一部分 API,另外的元件使用和之前不完全相同的API。一般就把元件劃分為有狀態 (Stateful) 元件和無狀態 (stateless) 元件。有狀態元件通常用到了 render, state
以及( lifecycle events)生命週期鉤子,無狀態元件通常使用了 render, props以及context
。
元件設計模式
通常的設計模式有:
- 容器 (Container) 元件
- 展示 (Presentational) 元件
- 高階 (Higher-Order) 元件
- 渲染回撥(Render Callbacks)
容器 (Container) 元件
藍色的代表容器元件,其裡面灰的表示展示元件容器元件是同外部資料進行互動(通訊),然後渲染其相應的子元件 --Jason Bonta
容器元件是資料或邏輯層,你能夠使用上面提到的有狀態的 API。使用生命週期鉤子,能直接連線到狀態管理 store,例如 Redux 或 Flux
,能通過 props 傳遞資料和回撥給其相應的子元件。
容器元件的 render
方法中返回的是由多個展示子元件組成的 React 元素。為了能訪問所有的有狀態的 API,容器元件必須用 ES6 的 class
宣告元件,而不是用函式宣告。
如下,宣告瞭叫 Greeting
的元件,它有 state, 一個生命週期鉤子 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
元件變成容器元件,可以將使用者介面拆分為展示元件,將在下面說明。
展示(Presentational)元件
展示元件能夠使用props, render以及 context
(無狀態的 API),它其實就是可以用函式宣告的無狀態元件:
const GreetingCard = (props) => {
return (
<div>
<h1>Hello! {props.name}</h1>
</div>
)
}
複製程式碼
展示元件僅僅從 props 中接受資料和回撥,props 是由容器元件或者它的父元件產生的。
藍色的代表展示元件,灰色的表示容器元件 用容器和展示元件分別同時地封裝了邏輯與 UI 展示,這樣才能得到理想的元件:
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>
);
}
}
複製程式碼
如上面所見,我把UI展示的部分從 Greeting
移動到了一個函式式無狀態元件。當然這只是一個簡單的例子,但在更復雜的應用中基本上也是這麼處理的。
高階元件(HOC)
高階元件就是一個函式,且該函式接受一個元件作為引數,並返回一個新的元件。
這是一種為任意元件複用某個元件邏輯而提供的強大模式。就比如react-router-v4 和 Redux
。在react-router-v4
中,使用withRouter()
,你的元件就能通過 props 繼承react-router
中的一些方法。在 redux
中也是一樣的,connect({})()
方法就能把 actions
和 reducer
傳入元件中。
來看這個例子:
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
。我的元件就通過 props 獲取到了 react-router-v4 的方法。還有很多其他的例子。
Render callbacks(渲染回撥)
與 HOC 類似的,render callbacks 或 render props
也是共享或複用元件邏輯的強大模式。儘管更多的開發者傾向於通過 HOC 複用邏輯,使用 render callbacks 還是有一定原因和優勢的--在一次 Michael Jackson “絕不再寫另一個的HOC”極好的解釋了。其中涉及到一些關鍵的地方,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>
);
}
}
複製程式碼
在 Count
元件的 render
中巢狀了 this.props.children
方法,並把 this.state
作為引數傳給它。在 App
元件中,我用 Counter
包裹了它,在 App
中就可以獲取到 Counter
的資料方法等邏輯。{state => ()}
就是 render callback。我自動地獲取到了 Counter
中的 state。
謝謝你的閱讀
歡迎大家留言建議,以上就是我對 React 元件設計模式的看法!
上面沒有提到 render props,可以檢視官網介紹例子
當然 React V16.8.0 新增了 hooks新的API,用函式也能實現有狀態的元件了,大家可以檢視官網瞭解
最後,推薦大家關注 React 作者之一 Dan 的部落格,編寫有彈性的元件的4個原則。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!