原文連接:React Component Patternshtml
github 的地址 歡迎 starnode
在我爲一次會議準備技術演講的過程當中,我想花點時間和你們分享我在設計 React 組件模式上一些感悟。組件是 React 的核心,理解它對於設計一個良好的項目架構是十分重要的。react
文中的圖表是從一個很是棒的演講(來自 Michael Chan)獲得的。我強烈推薦去看他的視頻git
根據 React 官網的介紹,組件可讓你把 UI 劃分爲獨立,可複用的部件,你只需獨立的考慮每一個部件的構建設計。github
當你第一次運行 npm install react
,在本地就能夠獲取到 React 源碼(路徑./node_modules/react/umd/react.development.js
),它能夠看作一個大的組件,提供了一系列的接口。React 組件和 JavaScript 函數是相似,組件接受一個稱爲 「props」 的輸入,返回描述(聲明)用戶界面的 React 元素。你只須要告訴 React 你用戶界面的樣子,React 就會幫你把剩下的事情完成(保持 DOM 和數據同步),渲染出界面。這也是 React 被稱爲聲明式庫的緣由。npm
聲明式就是假如你要去一個地方的時候,你選擇了打的,只須要告訴司機你的目的地,司機就會本身帶你到目的地。而命令式是相反的,是須要你本身駕車去目的地的。redux
那麼,當你下載 React,獲得了哪些 API 呢?它們有5個:設計模式
雖然組件提供了一份完整,方便利用的 API,但很天然的一些組件,你會使用一部分 API,另外的組件使用和以前不徹底相同的API。通常就把組件劃分爲有狀態 (Stateful) 組件和無狀態 (stateless) 組件。有狀態組件一般用到了 render, state
以及( lifecycle events)生命週期鉤子,無狀態組件一般使用了 render, props以及context
。bash
一般的設計模式有:react-router
藍色的表明容器組件,其裏面灰的表示展現組件容器組件是同外部數據進行交互(通訊),而後渲染其相應的子組件 --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
組件變成容器組件,能夠將用戶界面拆分爲展現組件,將在下面說明。
展現組件可以使用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
移動到了一個函數式無狀態組件。固然這只是一個簡單的例子,但在更復雜的應用中基本上也是這麼處理的。
高階組件就是一個函數,且該函數接受一個組件做爲參數,並返回一個新的組件。
這是一種爲任意組件複用某個組件邏輯而提供的強大模式。就好比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 的方法。還有不少其餘的例子。
與 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個原則。
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!