我的網站: https://www.neroht.comreact
在React寫應用的時候,不免遇到跨組件通訊的問題。如今已經有不少的解決方案。redux
React 的新的Context api本質上並非React或者Mbox這種狀態管理工具的替代品,充其量只是對React
自身狀態管理短板的補充。而Redux和Mbox這兩個庫自己並非爲React設計的,對於一些小型的React應用
比較重。segmentfault
Unstated是基於context API,也就是使用React.createContext()來建立一個StateContext來傳遞狀態的庫api
咱們拿最通用的計數器的例子來看unstated如何使用,先明確一下結構:Parent做爲父組件包含兩個子組件:Child1和Child2。
Child1展現數字,Child2操做數字的加減。而後,Parent組件的外層會包裹一個根組件。數組
首先,共享狀態須要有個狀態管理的地方,與Redux的Reducer不一樣的是,Unstated是經過一個繼承自Container實例:promise
import { Container } from 'unstated'; class CounterContainer extends Container { constructor(initCount) { super(...arguments); this.state = {count: initCount || 0}; } increment = () => { this.setState({ count: this.state.count + 1 }); } decrement = () => { this.setState({ count: this.state.count - 1 }); } } export default CounterContainer
看上去是否是很熟悉?像一個React組件類。CounterContainer繼承自Unstated暴露出來的Container類,利用state存儲數據,setState維護狀態,
而且setState與React的setState用法一致,可傳入函數。返回的是一個promise。antd
來看一下要顯示數字的Child1組件,利用Subscribe與CounterContainer創建聯繫。app
import React from 'react' import { Subscribe } from 'unstated' import CounterContainer from './store/Counter' class Child1 extends React.Component { render() { return <Subscribe to={[CounterContainer]}> { counter => { return <div>{counter.state.count}</div> } } </Subscribe> } } export default Child1
再來看一下要控制數字加減的Child2組件:ide
import React from 'react' import { Button } from 'antd' import { Subscribe } from 'unstated' import CounterContainer from './store/Counter' class Child2 extends React.Component { render() { return <Subscribe to={[CounterContainer]}> { counter => { return <div> <button onClick={counter.increment}>增長</button> <button onClick={counter.decrement}>減小</button> </div> } } </Subscribe> } } export default Child2
Subscribe內部返回的是StateContext.Consumer,經過to這個prop關聯到CounterContainer實例,
使用renderProps模式渲染視圖,Subscribe以內調用的函數的參數就是訂閱的那個狀態管理實例。
Child1與Child2經過Subscribe訂閱共同的狀態管理實例CounterContainer,因此Child2能夠調用
CounterContainer以內的increment和decrement方法來更新狀態,而Child1會根據更新來顯示數據。函數
看一下父組件Parent
import React from 'react' import { Provider } from 'unstated' import Child1 from './Child1' import Child2 from './Child2' import CounterContainer from './store/Counter' const counter = new CounterContainer(123) class Parent extends React.Component { render() { return <Provider inject={[counter]}> 父組件 <Child1/> <Child2/> </Provider> } } export default Parent
Provider返回的是StateContext.Provider,Parent經過Provider向組件的上下文中注入狀態管理實例。
這裏,能夠不注入實例。不注入的話,Subscribe內部就不能拿到注入的實例去初始化數據,也就是給狀態一個默認值,好比上邊我給的是123。
也能夠注入多個實例:
<Provider inject={[count1, count2]}> {/*Components*} </Provide>
那麼,在Subscribe的時候能夠拿到多個實例。
<Subscribe to={[CounterContainer1, CounterContainer2]}> {count1, count2) => {} </Subscribe>
弄明白原理以前須要先明白Unstated提供的三個API之間的關係。我根據本身的理解,畫了一張圖:
來梳理一下整個流程:
那麼就用to中傳入的狀態管理類來初始化實例。
用來實現一個狀態管理類。能夠理解爲redux中action和reducer的結合。概念類似,但實現不一樣。來看一下Container的源碼
export class Container { constructor() { CONTAINER_DEBUG_CALLBACKS.forEach(cb => cb(this)); this.state = null; this.listeners = []; } setState(updater, callback) { return Promise.resolve().then(() => { let nextState = null; if (typeof updater === 'function') { nextState = updater(this.state); } else { nextState = updater; } if (nextState === null) { callback && callback(); } // 返回一個新的state this.state = Object.assign({}, this.state, nextState); // 執行listener,也就是Subscribe的onUpdate函數,用來強制刷新視圖 const promises = this.listeners.map(listener => listener()); return Promise.all(promises).then(() => { if (callback) { return callback(); } }); }); } subscribe(fn) { this.listeners.push(fn); } unsubscribe(fn) { this.listeners = this.listeners.filter(f => f !== fn); } }
Container包含了state、listeners,以及setState、subscribe、unsubscribe這三個方法。
同時循環listeners調用其中的更新函數。達到更新頁面的效果。
Provider本質上返回的是StateContext.Provider。
export function Provider(ProviderProps) { return ( <StateContext.Consumer> {parentMap => { let childMap = new Map(parentMap); if (props.inject) { props.inject.forEach(instance => { childMap.set(instance.constructor, instance); }); } return ( <StateContext.Provider value={childMap}> {props.children} </StateContext.Provider> ); }} </StateContext.Consumer> ); }
它本身接收一個inject屬性,通過處理後,將它做爲context的值傳入到上下文環境中。
能夠看出,傳入的值爲一個map,使用Container類做爲鍵,Container類的實例做爲值。
Subscribe會接收這個map,優先使用它來實例化Container類,初始化數據。
可能有人注意到了Provider不是直接返回的StateContext.Provider,而是套了一層
StateContext.Consumer。這樣作的目的是Provider以內還能夠嵌套Provider。
內層Provider的value能夠繼承自外層。
簡單來講就是鏈接組件與狀態管理類的一座橋樑,能夠想象成react-redux中connect的做用
class Subscribe extends React.Component { constructor(props) { super(props); this.state = {}; this.instances = []; this.unmounted = false; } componentWillUnmount() { this.unmounted = true; this.unsubscribe(); } unsubscribe() { this.instances.forEach((container) => { container.unsubscribe(this.onUpdate); }); } onUpdate = () => new Promise((resolve) => { if (!this.unmounted) { this.setState(DUMMY_STATE, resolve); } else { resolve(); } }) _createInstances(map, containers) { this.unsubscribe(); if (map === null) { throw new Error('You must wrap your <Subscribe> components with a <Provider>'); } const safeMap = map; const instances = containers.map((ContainerItem) => { let instance; if ( typeof ContainerItem === 'object' && ContainerItem instanceof Container ) { instance = ContainerItem; } else { instance = safeMap.get(ContainerItem); if (!instance) { instance = new ContainerItem(); safeMap.set(ContainerItem, instance); } } instance.unsubscribe(this.onUpdate); instance.subscribe(this.onUpdate); return instance; }); this.instances = instances; return instances; } render() { return ( <StateContext.Consumer> { map => this.props.children.apply( null, this._createInstances(map, this.props.to), ) } </StateContext.Consumer> ); } }
這裏比較重要的是_createInstances與onUpdate兩個方法。StateContext.Consumer接收Provider傳遞過來的map,
與props接收的to一併傳給_createInstances。
onUpdate:沒有作什麼其餘事情,只是利用setState更新視圖,返回一個promise。它存在的意義是在訂閱的時候,
做爲參數傳入Container類的subscribe,擴充Container類的listeners數組,隨後在Container類setState改變狀態之後,
循環listeners的每一項就是這個onUpdate方法,它執行,就會更新視圖。
_createInstances: map爲provider中inject的狀態管理實例數據。若是inject了,那麼就用map來實例化數據,
不然用this.props.to的狀態管理類來實例化。以後調用instance.subscribe方法(也就是Container中的subscribe),
傳入自身的onUpdate,實現訂閱。它存在的意義是實例化Container類並將自身的onUpdate訂閱到Container類實例,
最終返回這個Container類的實例,做爲this.props.children的參數並進行調用,因此在組件內部能夠進行相似這樣的操做:
<Subscribe to={[CounterContainer]}> { counter => { return <div> <Button onClick={counter.increment}>增長</Button> <Button onClick={counter.decrement}>減小</Button> </div> } } </Subscribe>
Unstated上手很容易,理解源碼也不難。重點在於理解發布(Container類),Subscribe組件實現訂閱的思路。
其API的設計貼合React的設計理念。也就是想要改變UI必須setState。另外能夠不用像Redux同樣寫不少樣板代碼。
理解源碼的過程當中受到了下面兩篇文章的啓發,衷心感謝: