unstated
是一個極簡的狀態管理組件react
看它的簡介:State so simple, it goes without saying
redux
的狀態是存放在一棵樹內,採用嚴格的單向流git
unstated
的狀態是用戶本身定義,說白了就是object
,能夠放在一個組件的內,也能夠放在多個組件內github
React
,一致的API
redux
必須編寫reducer
和action
,經過dispatch(action)
改變狀態,它不限框架redux
unstated
改變狀態的API
徹底與React
一致,使用this.setState
,固然和React
的setState
不一樣,
可是它的底層也是用到了setState
去更新視圖segmentfault
unstated
沒有中間件功能,每次狀態改變(不論是否相等),都會從新渲染(V2.1.1
)數組
能夠自定義listener
,每次更新狀態時都會執行。promise
Container(狀態管理)
和Component(視圖管理)
promise
快速瞭解請直接跳到 總結
3大板塊和幾個關鍵變量app
Provider: 注入狀態實例,傳遞map,本質是Context.Provider,可嵌套達成鏈式傳遞 Container: 狀態管理類,遵循React的API,發佈訂閱模式,經過new生成狀態管理實例 Subscribe: 訂閱狀態組件,本質是Context.Consumer,接收Provider提供的map,視圖渲染組件 map: new Map(),經過類查找當前類建立的狀態管理實例
這裏引入官方例子框架
// @flow import React from 'react'; import { render } from 'react-dom'; import { Provider, Subscribe, Container } from 'unstated'; type CounterState = { count: number }; // 定義一個狀態管理類 class CounterContainer extends Container<CounterState> { state = { count: 0 }; increment() { this.setState({ count: this.state.count + 1 }); } decrement() { this.setState({ count: this.state.count - 1 }); } } // 渲染視圖組件(Context.Consumer的模式) function Counter() { return ( <Subscribe to={[CounterContainer]}> {counter => ( <div> <button onClick={() => counter.decrement()}>-</button> <span>{counter.state.count}</span> <button onClick={() => counter.increment()}>+</button> </div> )} </Subscribe> ); } render( <Provider> <Counter /> </Provider>, document.getElementById('root') );
這裏Counter
是咱們自定義的視圖組件,首先使用<Provider>
包裹,接着在Counter
內部,調用<Subscribe>
組件,
傳遞一個數組給props.to
,這個數組內存放了Counter
組件須要使用的狀態管理類
(此處也可傳遞狀態管理實例
)。dom
export function Provider(props: ProviderProps) { return ( <StateContext.Consumer> {parentMap => { let childMap = new Map(parentMap); // 外部注入的狀態管理實例 if (props.inject) { props.inject.forEach(instance => { childMap.set(instance.constructor, instance); }); } // 負責將childMap傳遞,初始爲null return ( <StateContext.Provider value={childMap}> {props.children} </StateContext.Provider> ); }} </StateContext.Consumer> ); }
這裏的模式是
<Consumer> ()=>{ /* ... */ return <Provider>{props.children}<Provider /> } </Consumer>
有3個注意點:
外層嵌套<Consumer>
能夠嵌套調用。
<Provider value={...}> /* ... */ <Provider value={此處繼承了上面的value}> /* ... */ </Provider>
props.inject
能夠注入現成的狀態管理實例
,添加到map
之中。props.children
。簡單一句話歸納,這麼寫能夠避免React.Context
改變致使子組件的重複渲染。
具體看這裏:避免React Context致使的重複渲染
export class Container<State: {}> { // 保存狀態 默認爲{} state: State; // 保存監聽函數,默認爲[] _listeners: Array<Listener> = []; setState( updater: $Shape<State> | ((prevState: $Shape<State>) => $Shape<State>), callback?: () => void ): Promise<void> { return Promise.resolve().then(() => { let nextState; /* 利用Object.assign改變state */ // 執行listener(promise) let promises = this._listeners.map(listener => listener()); // 全部Promise執行完畢 return Promise.all(promises).then(() => { // 所有listener執行完畢,執行回調 if (callback) { return callback(); } }); }); } // 增長訂閱(這裏默認的訂閱就是React的setState空值(爲了從新渲染),也能夠添加自定義監聽函數) subscribe(fn: Listener) { this._listeners.push(fn); } // 取消訂閱 unsubscribe(fn: Listener) { this._listeners = this._listeners.filter(f => f !== fn); } }
Container
內部邏輯很簡單,改變state
,執行監聽函數。
其中有一個_listeners
,是用於存放監聽函數的。
每一個狀態管理實例
存在一個默認監聽函數onUpdate
,
這個默認的監聽函數的做用就是調用React的setState強制視圖從新渲染
。
這裏的監聽函數內部返回Promise
,最後經過Promise.all
確保執行完畢,而後執行回調參數
。
所以setState
在外面使用也可使用then
。
例如,在官方例子中:
increment() { this.setState({ count: this.state.count + 1 },()=>console.log('2')) .then(()=>console.log('3') ) console.log('1') } // 執行順序是 1 -> 2 ->3
2個注意點:
setState
和React API
一致,第一個參數傳入object或者function,第二個傳入回調Promise.resolve().then
模擬this.setState
的異步執行簡單的說二者都是異步調用,Promise
更快執行。
setTimeout(()=>{},0)
會放入下一個新的任務隊列
Promise.resolve().then({})
會放入微任務
,在調用棧爲空時馬上補充調用棧並執行(簡單理解爲當前任務隊列
尾部)更多詳細能夠看這裏提供的2個視頻:https://stackoverflow.com/a/38752743
export class Subscribe<Containers: ContainersType> extends React.Component< SubscribeProps<Containers>, SubscribeState > { state = {}; // 存放傳入的狀態組件 instances: Array<ContainerType> = []; unmounted = false; componentWillUnmount() { this.unmounted = true; this._unsubscribe(); } _unsubscribe() { this.instances.forEach(container => { // container爲當前組件的每個狀態管理實例 // 刪除listeners中的this.onUpdate container.unsubscribe(this.onUpdate); }); } onUpdate: Listener = () => { return new Promise(resolve => { // 組件未被卸載 if (!this.unmounted) { // 純粹是爲了讓React更新組件 this.setState(DUMMY_STATE, resolve); } else { // 已經被卸載則直接返回 resolve(); } }); }; /* ... */ }
這裏的關鍵就是instances
,用於存放當前組件的狀態管理實例
。
當組件unmount
的時候,會unsubscribe
當前狀態管理實例
的默認監聽函數,那麼若是當前的狀態管理實例
是共享的,會不會有影響呢?
不會的。日後看能夠知道,當state
每次更新,都會從新建立新的狀態管理實例
(由於props.to
的值可能會發生變化,例如取消某一個狀態管理實例
),
而每次建立時,都會先unsubscribe
再subscribe
,確保不會重複添加監聽函數。
onUpdate
就是建立狀態管理組件
時默認傳遞的監聽函數,用的是React
的setState
更新一個DUMMY_STATE
(空對象{}
)。
export class Subscribe<Containers: ContainersType> extends React.Component< SubscribeProps<Containers>, SubscribeState > { /* 上面已講 */ _createInstances( map: ContainerMapType | null, containers: ContainersType ): Array<ContainerType> { // 首先所有instances解除訂閱 this._unsubscribe(); // 必須存在map 必須被Provider包裹纔會有map if (map === null) { throw new Error( 'You must wrap your <Subscribe> components with a <Provider>' ); } let safeMap = map; // 從新定義當前組件的狀態管理組件(根據to傳入的數組) let instances = containers.map(ContainerItem => { let instance; // 傳入的是Container組件,則使用 if ( typeof ContainerItem === 'object' && ContainerItem instanceof Container ) { instance = ContainerItem; } else { // 傳入的不是Container,多是其餘自定義組件等等(須要用new執行),嘗試獲取 instance = safeMap.get(ContainerItem); // 不存在則以它爲key,value是新的Container組件 if (!instance) { instance = new ContainerItem(); safeMap.set(ContainerItem, instance); } } // 先解綁再綁定,避免重複訂閱 instance.unsubscribe(this.onUpdate); instance.subscribe(this.onUpdate); return instance; }); this.instances = instances; return instances; } /* ... */ }
在_createInstances
內部,若是檢查到傳入的props.to
的值已是狀態管理實例
(私有狀態組件),那麼直接使用便可,
若是傳入的是類class
(共享狀態組件),會嘗試經過查詢map
,不存在的則經過new
建立。
export class Subscribe<Containers: ContainersType> extends React.Component< SubscribeProps<Containers>, SubscribeState > { /* 上面已講 */ render() { return ( <StateContext.Consumer> /* Provider傳遞的map */ {map => // children是函數 this.props.children.apply( null, // 傳給子函數的參數(傳進當前組件的狀態管理實例) this._createInstances(map, this.props.to) ) } </StateContext.Consumer> ); } }
每一次render
都會建立新的狀態管理實例
。
到此,3大板塊已經閱讀完畢。
React
一致的API
,一致的書寫模式,讓使用者很快上手。狀態管理類
,很是靈活。咱們能夠學redux
將全部狀態放到一個共享狀態管理實例
內部,
例如經過Provider
的inject
屬性注入,
或者針對每個組件建立單獨的狀態管理實例
(可共享可獨立)(unstated
做者推薦),
一切能夠按照本身的想法,但同時也要求使用者本身定義一些規則去約束寫法。
instance
集合,並無作任何對比,須要咱們在視圖層本身實現。props.children
的意義。Promise.resolve().then({})
和setTimeout(()=>{},0)
的區別。
源碼閱讀專欄對一些中小型熱門項目進行源碼閱讀和分析,對其總體作出導圖,以便快速瞭解內部關係及執行順序。
當前源碼(帶註釋),以及更多源碼閱讀內容: https://github.com/stonehank/sourcecode-analysis,歡迎fork
,求