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
,求