我的网站: 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同样写不少样板代码。
理解源码的过程当中受到了下面两篇文章的启发,衷心感谢: