![11dc499d6641e1ac480f334f1828da40.png](https://i-blog.csdnimg.cn/blog_migrate/7ad163c91ddf65908f06e0b64157a10b.png)
开源不易,感谢你的支持,❤ star me if you like concent ^_^
序言
之前发表了一篇文章 redux、mobx、concent特性大比拼, 看后生如何对局前辈,吸引了不少感兴趣的小伙伴入群开始了解和使用 concent,并获得了很多正向的反馈,实实在在的帮助他们提高了开发体验,群里人数虽然还很少,但大家热情高涨,技术讨论氛围浓厚,对很多新鲜技术都有保持一定的敏感度,如上个月开始逐渐被提及得越来越多的出自facebook的最新状态管理方案 recoil,虽然还处于实验状态,但是相必大家已经私底下开始欲欲跃试了,毕竟出生名门,有fb背书,一定会大放异彩。
不过当我体验完recoil后,我对其中标榜的精确更新保持了怀疑态度,有一些误导的嫌疑,这一点下文会单独分析,是否属于误导读者在读完本文后自然可以得出结论,总之本文主要是分析Concent
与Recoil
的代码风格差异性,并探讨它们对我们将来的开发模式有何新的影响,以及思维上需要做什么样的转变。
数据流方案之3大流派
目前主流的数据流方案按形态都可以划分以下这三类
- redux流派
redux、和基于redux衍生的其他作品,以及类似redux思路的作品,代表作有dva、rematch等等。 - mobx流派
借助definePerperty和Proxy完成数据劫持,从而达到响应式编程目的的代表,类mobx的作品也有不少,如dob等。 - Context流派
这里的Context指的是react自带的Context api,基于Context api打造的数据流方案通常主打轻量、易用、概览少,代表作品有unstated、constate等,大多数作品的核心代码可能不超过500行。
到此我们看看Recoil
应该属于哪一类?很显然按其特征属于Context流派,那么我们上面说的主打轻量对 Recoil
并不适用了,打开其源码库发现代码并不是几百行完事的,所以基于Context api
做得好用且强大就未必轻量,由此看出facebook
对Recoil
是有野心并给予厚望的。
我们同时也看看Concent
属于哪一类呢?Concent
在v2
版本之后,重构数据追踪机制,启用了defineProperty和Proxy特性,得以让react应用既保留了不可变的追求,又享受到了运行时依赖收集和ui精确更新的性能提升福利,既然启用了defineProperty和Proxy,那么看起来Concent
应该属于mobx流派?
事实上Concent
属于一种全新的流派,不依赖react的Context api,不破坏react组件本身的形态,保持追求不可变的哲学,仅在react自身的渲染调度机制之上建立一层逻辑层状态分发调度机制,defineProperty和Proxy只是用于辅助收集实例和衍生数据对模块数据的依赖,而修改数据入口还是setState(或基于setState封装的dispatch, invoke, sync),让Concent
可以0入侵的接入react应用,真正的即插即用和无感知接入。
即插即用的核心原理是,Concent
自建了一个平行于react运行时的全局上下文,精心维护这模块与实例之间的归属关系,同时接管了组件实例的更新入口setState,保留原始的setState为reactSetState,所有当用户调用setState时,concent除了调用reactSetState更新当前实例ui,同时智能判断提交的状态是否也还有别的实例关心其变化,然后一并拿出来依次执行这些实例的reactSetState,进而达到了状态全部同步的目的。
![0d36b14fd2ae72f2f6a57fdd8d4bce8a.png](https://i-blog.csdnimg.cn/blog_migrate/ed07539253abe6bc37491c23ceea5b7e.jpeg)
Recoil初体验
我们以常用的counter来举例,熟悉一下Recoil
暴露的四个高频使用的api - atom,定义状态 - selector, 定义派生数据 - useRecoilState,消费状态 - useRecoilValue,消费派生数据
定义状态
外部使用atom
接口,定义一个key为num
,初始值为0
的状态
const numState = atom({
key: "num",
default: 0
});
定义派生数据
外部使用selector
接口,定义一个key为numx10
,初始值是依赖numState
再次计算而得到
const numx10Val = selector({
key: "numx10",
get: ({
get }) => {
const num = get(numState);
return num * 10;
}
});
定义异步的派生数据
selector
的get
支持定义异步函数
需要注意的点是,如果有依赖,必需先书写好依赖在开始执行异步逻辑
const delay = () => new Promise(r => setTimeout(r, 1000));
const asyncNumx10Val = selector({
key: "asyncNumx10",
get: async ({
get }) => {
// !!!这句话不能放在delay之下, selector需要同步的确定依赖
const num = get(numState);
await delay();
return num * 10;
}
});
消费状态
组件里使用useRecoilState
接口,传入想要获去的状态(由atom
创建而得)
const NumView = () => {
const [num, setNum] = useRecoilState(numState);
const add = ()=>setNum(num+1);
return (
<div>
{num}<br/>
<button onClick={add}>add</button>
</div>
);
}
消费派生数据
组件里使用useRecoilValue
接口,传入想要获去的派生数据(由selector
创建而得),同步派生数据和异步派生数据,皆可通过此接口获得
const NumValView = () => {
const numx10 = useRecoilValue(numx10V