简介

unstated是一个极简的状态管理组件

看它的简介:State so simple, it goes without saying

对比

对比redux:

  • 更加灵活(相对的缺点是缺少规则,需要使用者的自觉)

    redux的状态是存放在一棵树内,采用严格的单向流

    unstated的状态是用户自己定义,说白了就是object,可以放在一个组件的内,也可以放在多个组件内

  • 针对React,一致的API

    redux必须编写reduceraction,通过dispatch(action)改变状态,它不限框架

    unstated改变状态的API完全与React一致,使用this.setState,当然和ReactsetState不同,
    但是它的底层也是用到了setState去更新视图

  • 功能相对简单

    unstated没有中间件功能,每次状态改变(不管是否相等),都会重新渲染(V2.1.1)

    可以自定义listener,每次更新状态时都会执行。

对比React的自带state:

  • 天生将组件分割为Container(状态管理)Component(视图管理)
  • 灵活配置共享状态或者私有状态
  • 支持promise

快速了解请直接跳到总结

初识

3大板块和几个关键变量


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组件需要使用的状态管理类(此处也可传递状态管理实例)。

Provider


export function Provider(props: ProviderProps) {
return (
&lt;StateContext.Consumer&gt;
{parentMap =&gt; {
let childMap = new Map(parentMap);
// 外部注入的状态管理实例
if (props.inject) {
props.inject.forEach(instance =&gt; {
childMap.set(instance.constructor, instance);
});
} // 负责将childMap传递,初始为null
return (
&lt;StateContext.Provider value={childMap}&gt;
{props.children}
&lt;/StateContext.Provider&gt;
);
}}
&lt;/StateContext.Consumer&gt;
);
}

这里的模式是


&lt;Consumer&gt;
()=&gt;{
/* ... */
return &lt;Provider&gt;{props.children}&lt;Provider /&gt;
}
&lt;/Consumer&gt;

有3个注意点:

  1. 外层嵌套<Consumer>可以嵌套调用。


    &lt;Provider value={...}&gt;
    /* ... */
    &lt;Provider value={此处继承了上面的value}&gt;
    /* ... */
    &lt;/Provider&gt;
  2. props.inject可以注入现成的状态管理实例,添加到map之中。
  3. 返回值写成props.children

返回值写成props.children的意义

简单一句话概括,这么写可以避免React.Context改变导致子组件的重复渲染。

具体看这里:避免React Context导致的重复渲染

Container


export class Container&lt;State: {}&gt; {
// 保存状态 默认为{}
state: State;
// 保存监听函数,默认为[]
_listeners: Array&lt;Listener&gt; = []; setState(
updater: $Shape&lt;State&gt; | ((prevState: $Shape&lt;State&gt;) =&gt; $Shape&lt;State&gt;),
callback?: () =&gt; void
): Promise&lt;void&gt; {
return Promise.resolve().then(() =&gt; {
let nextState; /* 利用Object.assign改变state */ // 执行listener(promise)
let promises = this._listeners.map(listener =&gt; listener()); // 所有Promise执行完毕
return Promise.all(promises).then(() =&gt; {
// 全部listener执行完毕,执行回调
if (callback) {
return callback();
}
});
});
} // 增加订阅(这里默认的订阅就是React的setState空值(为了重新渲染),也可以添加自定义监听函数)
subscribe(fn: Listener) {
this._listeners.push(fn);
} // 取消订阅
unsubscribe(fn: Listener) {
this._listeners = this._listeners.filter(f =&gt; f !== fn);
}
}

Container内部逻辑很简单,改变state,执行监听函数。

其中有一个_listeners,是用于存放监听函数的。

每个状态管理实例存在一个默认监听函数onUpdate
这个默认的监听函数的作用就是调用React的setState强制视图重新渲染

这里的监听函数内部返回Promise,最后通过Promise.all确保执行完毕,然后执行回调参数

因此setState在外面使用也可以使用then

例如,在官方例子中:


increment() {
this.setState({ count: this.state.count + 1 },()=&gt;console.log('2'))
.then(()=&gt;console.log('3') )
console.log('1')
}
// 执行顺序是 1 -&gt; 2 -&gt;3

2个注意点:

  1. setStateReact API一致,第一个参数传入object或者function,第二个传入回调
  2. 这里通过Promise.resolve().then模拟this.setState的异步执行

关于Promise.resolve和setTimeout的区别

简单的说两者都是异步调用,Promise更快执行。

  • setTimeout(()=>{},0)会放入下一个新的任务队列
  • Promise.resolve().then({})会放入微任务,在调用栈为空时立刻补充调用栈并执行(简单理解为当前任务队列尾部)

更多详细可以看这里提供的2个视频:https://stackoverflow.com/a/38752743

Subscribe


export class Subscribe&lt;Containers: ContainersType&gt; extends React.Component&lt;
SubscribeProps&lt;Containers&gt;,
SubscribeState
&gt; {
state = {};
// 存放传入的状态组件
instances: Array&lt;ContainerType&gt; = [];
unmounted = false; componentWillUnmount() {
this.unmounted = true;
this._unsubscribe();
} _unsubscribe() {
this.instances.forEach(container =&gt; {
// container为当前组件的每一个状态管理实例
// 删除listeners中的this.onUpdate
container.unsubscribe(this.onUpdate);
});
} onUpdate: Listener = () =&gt; {
return new Promise(resolve =&gt; {
// 组件未被卸载
if (!this.unmounted) {
// 纯粹是为了让React更新组件
this.setState(DUMMY_STATE, resolve);
} else {
// 已经被卸载则直接返回
resolve();
}
});
}; /* ... */
}

这里的关键就是instances,用于存放当前组件的状态管理实例

当组件unmount的时候,会unsubscribe当前状态管理实例的默认监听函数,那么如果当前的状态管理实例是共享的,会不会有影响呢?

不会的。往后看可以知道,当state每次更新,都会重新创建新的状态管理实例(因为props.to的值可能会发生变化,例如取消某一个状态管理实例),
而每次创建时,都会先unsubscribesubscribe,确保不会重复添加监听函数。

onUpdate就是创建状态管理组件时默认传递的监听函数,用的是ReactsetState更新一个DUMMY_STATE(空对象{})。


export class Subscribe&lt;Containers: ContainersType&gt; extends React.Component&lt;
SubscribeProps&lt;Containers&gt;,
SubscribeState
&gt; {
/* 上面已讲 */ _createInstances(
map: ContainerMapType | null,
containers: ContainersType
): Array&lt;ContainerType&gt; {
// 首先全部instances解除订阅
this._unsubscribe(); // 必须存在map 必须被Provider包裹才会有map
if (map === null) {
throw new Error(
'You must wrap your &lt;Subscribe&gt; components with a &lt;Provider&gt;'
);
} let safeMap = map;
// 重新定义当前组件的状态管理组件(根据to传入的数组)
let instances = containers.map(ContainerItem =&gt; {
let instance; // 传入的是Container组件,则使用
if (
typeof ContainerItem === 'object' &amp;&amp;
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&lt;Containers: ContainersType&gt; extends React.Component&lt;
SubscribeProps&lt;Containers&gt;,
SubscribeState
&gt; { /* 上面已讲 */ render() {
return (
&lt;StateContext.Consumer&gt;
/* Provider传递的map */
{map =&gt;
// children是函数
this.props.children.apply(
null,
// 传给子函数的参数(传进当前组件的状态管理实例)
this._createInstances(map, this.props.to)
)
}
&lt;/StateContext.Consumer&gt;
);
}
}

每一次render都会创建新的状态管理实例

到此,3大板块已经阅读完毕。

总结

  1. 简单易用,与React一致的API,一致的书写模式,让使用者很快上手。
  2. 并没有规定如何管理这些状态管理类,非常灵活。

    我们可以学redux将所有状态放到一个共享状态管理实例内部,
    例如通过Providerinject属性注入,

    或者针对每一个组件创建单独的状态管理实例(可共享可独立)(unstated作者推荐),

    一切可以按照自己的想法,但同时也要求使用者自己定义一些规则去约束写法。

  3. 仅仅是管理了状态,每次更新都是一个全新的instance集合,并没有做任何对比,需要我们在视图层自己实现。
  4. 返回值写成props.children意义
  5. 关于Promise.resolve().then({})setTimeout(()=>{},0)区别

导图


源码阅读专栏对一些中小型热门项目进行源码阅读和分析,对其整体做出导图,以便快速了解内部关系及执行顺序。
当前源码(带注释),以及更多源码阅读内容:https://github.com/stonehank/sourcecode-analysis,欢迎fork,求

来源:https://segmentfault.com/a/1190000017305209

纯粹极简的react状态管理组件unstated的更多相关文章

  1. 借鉴redux,实现一个react状态管理方案

    react状态管理方案有很多,其中最简单的最常用的是redux. redux实现 redux做状态管理,是利用reducer和action实现的state的更新. 如果想要用redux,需要几个步骤 ...

  2. React状态管理相关

    关于React状态管理的一些想法 我最开始使用React的时候,那个时候版本还比较低(16版本以前),所以状态管理都是靠React自身API去进行管理,但当时最大的问题就是跨组件通信以及状态同步和状态 ...

  3. react状态管理器之mobx

    react有几种状态管理器,今天先来整理一下mobx状态管理器,首先了解一下什么是mobx 1.mobx成员: observable action 可以干嘛: MobX 的理念是通过观察者模式对数据做 ...

  4. 极简触感反馈Button组件

    一个简单的React触感反馈的button组件 import React from 'react'; import './index.scss'; class Button extends React ...

  5. 极简版 react+webpack 脚手架

    目录结构 asset/ css/ img/ src/ entry.js ------------------------ 入口文件 .babelrc index.html package.json w ...

  6. React状态管理之redux

    其实和vue对应的vuex都是差不多的东西,这里稍微提一下(安装Redux略过): import { createStore, combineReducers, applyMiddleware } f ...

  7. 你再也不用使用 Redux、Mobx、Flux 等状态管理了

    Unstated Next readme 的中文翻译 前言 这个库的作者希望使用 React 内置 API ,直接实现状态管理的功能.看完这个库的说明后,没有想到代码可以这个玩.短短几行代码,仅仅使用 ...

  8. Ansible状态管理

     转载自:http://xdays.me/ansible状态管理.html 简介 就像所有服务器批量管理工具(puppet有DSL,salt有state)一样,ansible也有自己的状态管理组件 ...

  9. React 新 Context API 在前端状态管理的实践

    本文转载至:今日头条技术博客 众所周知,React的单向数据流模式导致状态只能一级一级的由父组件传递到子组件,在大中型应用中较为繁琐不好管理,通常我们需要使用Redux来帮助我们进行管理,然而随着Re ...

随机推荐

  1. edge 修改链接打开方式

    我目前的edge版本是 Version 84.0.522.63 (Official build) (64-bit) 每次点击链接, 都是默认在原页面打开新标签, 不符合过往习惯. 修改方式 打开控制面 ...

  2. WebApis中DOM操作的基本案例

    1.1. 排他操作 1.1.1 排他思想 如果有同一组元素,我们想要某一个元素实现某种样式, 需要用到循环的排他思想算法: 所有元素全部清除样式(干掉其他人) 给当前元素设置样式 (留下我自己) 注意 ...

  3. [PyTorch 学习笔记] 3.3 池化层、线性层和激活函数层

    本章代码:https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson3/nn_layers_others.py 这篇文章主要介绍 ...

  4. IDEA 左侧出现对勾,该如何去掉对勾呢?

    如下面 解决办法如下 单击按F11 或者ctrl +鼠标左键点击那个对串就可以决你的问题 有对勾是因为你把他添加进去了书签,方便下次自己看 我们可以在这个地方看到自己的书签也就是打对勾的地方

  5. Codeforces Round #668 (Div. 2)A-C题解

    A. Permutation Forgery 题目:http://codeforces.com/contest/1405/problem/A 题解:这道题初看有点吓人,一开始居然想到要用全排序,没错我 ...

  6. js+canvas画随机4位验证码

    啥都不说了,复制代码吧!!! <!DOCTYPE html> <html lang="en"> <head> <meta charset= ...

  7. C008:输入显示日期

    代码: #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { int day,month,year; do{ pri ...

  8. Q200510-02: 重复的DNA序列 程序解法

    问题:  重复的DNA序列 所有 DNA 都由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”.在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助. 编 ...

  9. Zookeeper协议篇-Paxos算法与ZAB协议

    前言 可以自行去学习一下Zookeeper中的系统模型,节点特性,权限认证以及事件通知Watcher机制相关知识,本篇主要学习Zookeeper一致性算法和满足分布式协调的Zab协议 Paxos算法 ...

  10. 如何PJ IDEA

    1.打开:http://idea.medeming.com/jets/,点击下载PJ码 2.下载完成后解压,会得到2个文件 3.启动IDEA时,将PJ码复制到: 点击ok,就可进入idea中.接着PJ ...