纯粹极简的react状态管理组件unstated
简介
unstated
是一个极简的状态管理组件
看它的简介:State so simple, it goes without saying
对比
对比redux:
- 更加灵活(相对的缺点是缺少规则,需要使用者的自觉)
redux
的状态是存放在一棵树内,采用严格的单向流unstated
的状态是用户自己定义,说白了就是object
,可以放在一个组件的内,也可以放在多个组件内 - 针对
React
,一致的API
redux
必须编写reducer
和action
,通过dispatch(action)
改变状态,它不限框架unstated
改变状态的API
完全与React
一致,使用this.setState
,当然和React
的setState
不同,
但是它的底层也是用到了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 (
<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
。
返回值写成props.children的意义
简单一句话概括,这么写可以避免React.Context
改变导致子组件的重复渲染。
具体看这里:避免React Context导致的重复渲染
Container
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.resolve和setTimeout的区别
简单的说两者都是异步调用,Promise
更快执行。
setTimeout(()=>{},0)
会放入下一个新的任务队列
Promise.resolve().then({})
会放入微任务
,在调用栈为空时立刻补充调用栈并执行(简单理解为当前任务队列
尾部)
更多详细可以看这里提供的2个视频:https://stackoverflow.com/a/38752743
Subscribe
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
,求
来源:https://segmentfault.com/a/1190000017305209
纯粹极简的react状态管理组件unstated的更多相关文章
- 借鉴redux,实现一个react状态管理方案
react状态管理方案有很多,其中最简单的最常用的是redux. redux实现 redux做状态管理,是利用reducer和action实现的state的更新. 如果想要用redux,需要几个步骤 ...
- React状态管理相关
关于React状态管理的一些想法 我最开始使用React的时候,那个时候版本还比较低(16版本以前),所以状态管理都是靠React自身API去进行管理,但当时最大的问题就是跨组件通信以及状态同步和状态 ...
- react状态管理器之mobx
react有几种状态管理器,今天先来整理一下mobx状态管理器,首先了解一下什么是mobx 1.mobx成员: observable action 可以干嘛: MobX 的理念是通过观察者模式对数据做 ...
- 极简触感反馈Button组件
一个简单的React触感反馈的button组件 import React from 'react'; import './index.scss'; class Button extends React ...
- 极简版 react+webpack 脚手架
目录结构 asset/ css/ img/ src/ entry.js ------------------------ 入口文件 .babelrc index.html package.json w ...
- React状态管理之redux
其实和vue对应的vuex都是差不多的东西,这里稍微提一下(安装Redux略过): import { createStore, combineReducers, applyMiddleware } f ...
- 你再也不用使用 Redux、Mobx、Flux 等状态管理了
Unstated Next readme 的中文翻译 前言 这个库的作者希望使用 React 内置 API ,直接实现状态管理的功能.看完这个库的说明后,没有想到代码可以这个玩.短短几行代码,仅仅使用 ...
- Ansible状态管理
转载自:http://xdays.me/ansible状态管理.html 简介 就像所有服务器批量管理工具(puppet有DSL,salt有state)一样,ansible也有自己的状态管理组件 ...
- React 新 Context API 在前端状态管理的实践
本文转载至:今日头条技术博客 众所周知,React的单向数据流模式导致状态只能一级一级的由父组件传递到子组件,在大中型应用中较为繁琐不好管理,通常我们需要使用Redux来帮助我们进行管理,然而随着Re ...
随机推荐
- 区块链入门到实战(34)之Solidity – 变量
Solidity 支持三种类型的变量: 状态变量 – 变量值永久保存在合约存储空间中的变量. 局部变量 – 变量值仅在函数执行过程中有效的变量,函数退出后,变量无效. 全局变量 – 保存在全局命名空间 ...
- 《spring源码解读》 - ioc之验证模型获取
我们上一篇文章最后调用到 `org.springframework.beans.factory.xml. XmlBeanDefinitionReader#doLoadDocument(...) ` 方 ...
- 分享一个操作pdf文件的js文件-pdfObject.js(文件预览、下载、打印等操作都具备)
获取相关资料或者源码的朋友可以关注下公众号,回复关键字pdf20200518即可
- PAT-B1009 说反话 - 字符串反转
1009 说反话 (20分) 给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出. 输入格式: 测试输入包含一个测试用例,在一行内给出总长度不超过 80 的字符串.字符串由若干单词和若干空格组 ...
- 大型Kubernetes集群的资源编排优化
背景 云原生这个词想必大家应该不陌生了,容器是云原生的重要基石,而Kubernetes经过这几年的快速迭代发展已经成为容器编排的事实标准了.越来越多的公司不论是大公司还是中小公司已经在他们的生产环境中 ...
- Kafka与RabbitMQ区别
RabbitMQ 和 Kafka 的显著差异 RabbitMQ 是一个消息代理,但是 Apache Kafka 是一个分布式流式系统.好像从语义上就可以看出差异,但是它们内部的一些特性会影响到我们是否 ...
- Android Studio相关目录
Android Studio的Android SDK的安装路径 /Users/XXX/Library/Android/sdk/ nox_adb.exe connect 127.0.0.1:62001 ...
- 单元测试框架 python
1.为什么要做单元测试 单元测试更细致.更有针对性 单元测试能测试到很多系统测试无法达到的测试 单元测试是在编码中做的测试,发现问题方便修改,而系统测试的问题修改成本可能变高 单元测试是自动化测试 2 ...
- 时间选择器 element
<el-date-picker type="datetime" placeholder="选择上线日期" :picker-options="st ...
- 新三种求数列中值SQL之效率再比拼
在 https://www.cnblogs.com/xiandedanteng/p/12677688.html 中我列举了三种求中值方案,其中日本人MICK的做法因为不适用于二百万结果集而放弃,取而 ...