書籍完整目錄html
修改 Propsreact
Immutable data representationgit
肯定性es6
在 getInitialState 中使用 propsgithub
私有狀態和全局事件redux
render 包含 side effectssegmentfault
jQuery 修改 DOM設計模式
使用無狀態組件框架
內存管理ide
componentWillUnmount 取消訂閱事件
判斷 isMounted
上層設計
使用 container component
使用 Composition 替代 mixins
Composability - Presenter Pattern
Composability - Decorator Pattern
Context 數據傳遞
React 的框架設計是趨於函數式的,其中最主要的兩點也是爲何會選擇 React 的兩點:
單向性:數據的流動是單向的
肯定性:React(storeData) = view 相同數據老是渲染出相同的 view
這兩點便是特性也是設計 React 應用的基本原則,圍繞這兩個原則社區裏邊出現了一些 React 設計模式,即有好的設計模式也有應該要避免的反模式,理解這些設計模式可以幫助咱們寫出更優質的 React 應用,本節將圍繞 單向性、肯定性、內存管理、上層設計 來討論這些設計模式。
anti 表示反模式,good 表示好模式
數據的流動是單向的
描述: 組件任何地方修改 props 的值
解釋:
React 的數據流動是單向性的,流動的方式是經過 props 傳遞到組件中,而在 Javascript 中對象是經過引用傳遞的,修改 props 等於直接修改了 store 中的數據,致使破壞數據的單向流動特性
描述: store data 使用不可變數據
解釋: Javascript 對象的特性是能夠任意修改,而這個特性很容易破壞數據的單向性,由於人工沒法永遠確保數據沒有被修改過,惟一的作法是使用不可變數據,用代碼邏輯確保數據不能被任意修改,後面會有一個完整的小節介紹不可變數據在 React 中的應用
React(storeData) = view 相同數據老是渲染出相同的 view
描述: getInitialState 經過 props 來生成 state 數據
解釋:
官方文檔 https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html
在 getInitialState 中經過 props 來計算 state 破壞了肯定性原則,「source of truth」 應該只是來自於一個地方,經過計算 state 事後增長了 truth source。這種作法的另一個壞處是在組件更新的時候,還須要計算從新計算這部分 state。
舉例:
var MessageBox = React.createClass({ getInitialState: function() { return {nameWithQualifier: 'Mr. ' + this.props.name}; }, render: function() { return <div>{this.state.nameWithQualifier}</div>; } }); ReactDOM.render(<MessageBox name="Rogers"/>, mountNode);
優化方式:
var MessageBox = React.createClass({ render: function() { return <div>{'Mr. ' + this.props.name}</div>; } }); ReactDOM.render(<MessageBox name="Rogers"/>, mountNode);
須要注意的是如下這種作法並不會影響肯定性
var Counter = React.createClass({ getInitialState: function() { // naming it initialX clearly indicates that the only purpose // of the passed down prop is to initialize something internally return {count: this.props.initialCount}; }, handleClick: function() { this.setState({count: this.state.count + 1}); }, render: function() { return <div onClick={this.handleClick}>{this.state.count}</div>; } }); ReactDOM.render(<Counter initialCount={7}/>, mountNode);
描述: 在組件中定義私有的狀態或者使用全局事件
介紹: 組件中定義了私有狀態和全局事件事後,組件的渲染可能會出現不一致,由於全局事件和私有狀態均可以控制組件的狀態,這樣外部使用組件沒法保證組件的渲染結果,影響了組件的肯定性。另一點是組件應該儘可能保證獨立性,避免和外部的耦合,使用全局事件形成了和外部事件的耦合。
side effect 解釋: https://en.wikipedia.org/wiki/Side_effect_(computer_science)
描述: render 函數包含一些 side effects 的代碼邏輯,這些邏輯包括如
修改 state 數據
修改 props 數據
修改全局變量
調用其餘致使 side effect 的函數
解釋: render 函數若是包含了 side effect ,渲染的結果再也不可信,因此確保 render 函數爲純函數
描述: 使用外部 DOM 框架修改或刪除了 DOM 節點、屬性、樣式
解釋: React 中 DOM 的結構和屬性都是由渲染函數肯定的,若是使用了 Jquery 修改 DOM,那麼可能形成衝突,視圖的修改源頭增長,直接影響組件的肯定性
描述: 優先使用無狀態組件
解釋: 無狀態組件更符合函數式的特性,若是組件不須要額外的控制,只是渲染結構,那麼應該優先選擇無狀態組件
描述: 若是組件須要註冊訂閱事件,能夠在 componentDidMount 中註冊,且必須在 ComponentWillUnmount 中取消訂閱
解釋: 在組件 unmount 後若是沒有取消訂閱事件,訂閱事件可能仍然擁有組件實例的引用,這樣第一是組件內存沒法釋放,第二是引發沒必要要的錯誤
描述: 在組件中使用 isMounted 方法判斷組件是否未被註銷
解釋:
React 中在一個組件 ummount 事後使用 setState 會出現warning提示(一般出如今一些事件註冊回調函數中) ,避免 warning 的解決辦法是:
if(this.isMounted()) { // This is bad. this.setState({...}); }
但這是個掩耳盜鈴的作法,由於若是出現了錯誤提示就表示在組件 unmount 的時候還有組件的引用,這個時候應該是已經致使了內存溢出。因此解決錯誤的正確方法是在 componentWillUnmount 函數中取消監聽:
class MyComponent extends React.Component { componentDidMount() { mydatastore.subscribe(this); } render() { ... } componentWillUnmount() { mydatastore.unsubscribe(this); } }
描述: 將 React 組件分爲兩類 container 、normal ,container 組件負責獲取狀態數據,而後傳遞給與之對應的 normal component,對應表示兩個組件的名稱對應,舉例:
TodoListContainer => TodoList FooterContainer => Footer
解釋: 參看 redux 設計中的 container 組件,container 組件是 smart 組件,normal 組件是 dummy 組件,這樣的責任分離讓 normal 組件更加獨立,不須要知道狀態數據。明確的職責分配也增長了應用的肯定性(明確只有 container 組件可以知道狀態數據,且是對應部分的數據)。
描述: 使用組件的組合的方式(高階組件)替代 mixins 實現爲組件增長附加功能
解釋:
mixins 的設計主要目的是給組件提供插件機制,大多數狀況使用 mixin 是爲了給組件增長額外的狀態。可是使用 mixins 會帶來一些額外的壞處:
mixins 一般須要依賴組件定義特定的方法,如 getSomeMixinState ,而這個是隱式的約束
多個 mixins 可能會致使衝突
mixins 一般增長了額外的狀態數據,而 react 的設計應該是要避免過多的內部狀態
mixins 可能會影響 shouldComponentUpdate 的邏輯, mixins 作了不少數據合併的邏輯
另一點是在新版本的 React 中,mixins 將會是廢棄的 feature,在 es6 class 定義組件也不會支持 mixins。
舉個例子,一個訂閱 fluxstore 的 mixin 爲:
function StoreMixin(store) { var Mixin = { getInitialState() { return this.getStateFromStore(this.props); }, componentDidMount() { store.addChangeListener(this.handleStoreChanged) this.setState(this.getStateFromStore(this.props)); }, componentWillUnmount() { store.removeChangeListener(this.handleStoreChanged) }, handleStoreChanged() { if (this.isMounted()) { this.setState(this.getStateFromStore(this.props)); } } }; return Mixin; }
使用
const TodolistContainer = React.createClass({ mixins: [StoreMixin(AppStore)], getStateFromStore(props) { return { todos: AppStore.get('todos'); } } })
轉換爲組件的組合方式爲:
function connectToStores(Component, store, getStateFromStore) { const StoreConnection = React.createClass({ getInitialState() { return getStateFromStore(this.props); }, componentDidMount() { store.addChangeListener(this.handleStoreChanged) }, componentWillUnmount() { store.removeChangeListener(this.handleStoreChanged) }, handleStoreChanged() { if (this.isMounted()) { this.setState(getStateFromStore(this.props)); } }, render() { return <Component {...this.props} {...this.state} />; } }); return StoreConnection; };
使用方式:
class Todolist extends React.Component { render() { // .... } } TodolistContainer = connectToStore(Todolist, AppStore, props => { todos: AppStore.get('todos') })
描述: 利用 children 能夠做爲函數的特性,將數據獲取和數據表現分離成爲兩個不一樣的組件
以下例子:
class DataGetter extends React.Component { render() { const { children } = this.props const data = [ 1,2,3,4,5 ] return children(data) } } class DataPresenter extends React.Component { render() { return ( <DataGetter> {data => <ul> {data.map((datum) => ( <li key={datum}>{datum}</li> ))} </ul> } </DataGetter> ) } } const App = React.createClass({ render() { return ( <DataPresenter /> ) } })
解釋: 將數據獲取和數據展示分離,同時利用組件的 children 能夠做爲函數的特性,讓數據獲取和數據展示均可以做爲組件使用
描述: 父組件經過 cloneElement 方法給子組件添加方法和屬性
cloneElement 方法:
ReactElement cloneElement( ReactElement element, [object props], [children ...] )
以下例子:
const CleverParent = React.createClass({ render() { const children = React.Children.map(this.props.children, (child) => { return React.cloneElement(child, { // 新增 onClick 屬性 onClick: () => alert(JSON.stringify(child.props, 0, 2)) }) }) return <div>{children}</div> } }) const SimpleChild = React.createClass({ render() { return ( <div onClick={this.props.onClick}> {this.props.children} </div> ) } }) const App = React.createClass({ render() { return ( <CleverParent> <SimpleChild>1</SimpleChild> <SimpleChild>2</SimpleChild> </CleverParent> ) } })
解釋: 經過這種設計模式,能夠應用到一些自定義的組件設計,提供更簡潔的 API 給第三方使用,如 facebook 的 FixedDataTable 也是應用了這種設計模式
描述: 經過 Context 可讓全部組件共享相同的上下文,避免數據的逐級傳遞, Context 是大多數 flux 庫共享 store 的基本方法。
使用方法:
/** * 初始化定義 Context 的組件 */ class Chan extends React.Component { getChildContext() { return { environment: "grandma's house" } } } // 設置 context 類型 Chan.childContextTypes = { environment: React.PropTypes.string }; /** * 子組件獲取 context */ class ChildChan extends React.Component { render() { const ev = this.context.environment; } } /** * 須要設置 contextTypes 才能獲取 */ ChildChan.contextTypes = { environment: React.PropTypes.string };
解釋: 一般狀況下 Context 是爲基礎組件提供的功能,通常狀況應該避免使用,不然濫用 Context 會影響應用的肯定性。