简介

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. [PyTorch 学习笔记] 3.3 池化层、线性层和激活函数层

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

  2. json对象遍历顺序问题

    对json对象遍历我们一般使用for in循环,或者Object.keys + 数组方法.在接触js以来听到过一种说法: for in 遍历顺序是不可靠的 但是在实际开发中for in 循环也是按照其 ...

  3. [ASP.NET Core开发实战]基础篇01 Startup

    Startup,顾名思义,就是启动类,用于配置ASP.NET Core应用的服务和请求管道. Startup有两个主要作用: 通过ConfigureServices方法配置应用的服务.服务是一个提供应 ...

  4. 【趣味设计模式系列】之【代理模式4--ASM框架解析】

    1. 简介 ASM是assemble英文的简称,中文名为汇编,官方地址https://asm.ow2.io/,下面是官方的一段英文简介: ASM is an all purpose Java byte ...

  5. [工作积累] shadowmap 改进

    前面几篇阴影相关的: https://www.cnblogs.com/crazii/p/5443534.html 这个是在做bh3 MMD角色自阴影时的笔记 https://www.cnblogs.c ...

  6. HDU - 5775-Bubble Sort(权值线段树)

    P is a permutation of the integers from 1 to N(index starting from 1). Here is the code of Bubble So ...

  7. Codeforces 1324F Maximum White Subtree DFS

    题意 给你无根一颗树,每个节点是黑色或白色.对于每一个节点,问包含该节点的权值最大的子树. 子树的权值等于子树中白点的个数减去黑点的个数. 注意,这里的子树指的是树的联通子图. 解题思路 这场就这题卡 ...

  8. Jmeter逻辑控制器,简单操作

    1. 2. 循环控制器可以设置请求的循环次数或永久循环, .  作用:改控制器下的取样器请求可以循环运行. 3. 请求需要拖拽到循环控制器里, 4.循环次数乘以线程数 得到如下图: 成功了 二. 事务 ...

  9. 使用Json-lib将对象和Json互转

    工程下载地址: https://files.cnblogs.com/files/xiandedanteng/jsonSample20200308.rar Depenency: <!-- 使用js ...

  10. Redis Sentinel结构 及相关文档

    Redis Sentinel是一个用来监控redis集群中节点的状态,不用来存储数据.当集群中的某个节点有故障时,可以自动的进行故障转移的操作.通常为了保证sentinel的高可用,sentinel也 ...