Recoil介绍
recoil是Facebook公司出的react数据流管理方案,采用分散管理原子状态的设计模式,最近公司新项目选了这个,踩了一些坑来和大家分享一下。
提供给直接看结论的同学,完整Demo地址
Recoil的使用
atom
一个 atom 代表一个状态。Atom 可在任意组件中进行读写。读取 atom 值的组件隐式订阅了该 atom,因此任何 atom 的更新都将致使使用对应 atom 的组件重新渲染
import { atom } from "recoil";
const rootState = atom({
key: "rootState",
default: "rootState"
});
在组件中使用atom
import { useRecoilState } from "useRecoilState";
function Root() {
const [root, change] = useRecoilState(rootState);
return (
<>
<h3>Root State: {root}</h3>
<br />
<button onClick={() => change(Math.random().toString())}>
change root
</button>
</>
)}
</div>
);
}
RecoilRoot
如果在组件内使用recoil,则需要在其父组件使用RecoilRoot包裹
import { RecoilRoot } from "recoil";
const App = () => {
return (
<RecoilRoot>
<Root />
</RecoilRoot>
)
}
RecoilRoot的一个优点是,当RecoilRoot组件被卸载时,内部的atom状态全都会被清除,在项目中组件的切换可以很好的重置数据,不需要做额外的操作。
而且RecoilRoot是可以多个共存的,可以嵌套使用,但彼此数据是隔离的,可以做到每个atom的状态独立,互不干扰。
import { RecoilRoot, useRecoilState } from "recoil";
const App = () => {
const [root, change] = useRecoilState(rootState);
return (
<>
<h3>Root State: {root}</h3>
// 此处rootState的变动不会影响到Root内的状态
<button onClick={() => change(Math.random().toString())}>
change root
</button>
<RecoilRoot>
<Root />
</RecoilRoot>
</>
)
}
然而,在实际的开发工作中,不太会有这么理想的碎片化状态管理,状态之间一定会有共享的情况,官方为RecoilRoot提供了一个属性override?: boolean,只要为RecoilRoot设置override: false,那当前RecoilRoot就会与离该层最近的RecoilRoot合并,完成数据共享。
import { RecoilRoot, useRecoilState } from "recoil";
const App = () => {
const [root, change] = useRecoilState(rootState);
return (
<>
<h3>Root State: {root}</h3>
// 此处rootState会与Root内的状态共享值
<button onClick={() => change(Math.random().toString())}>
change root
</button>
<RecoilRoot override={false}>
<Root />
</RecoilRoot>
</>
)
}
但是这样处理,嵌套RecoilRoot内所有使用到的atom都会被上一层级共享,失去了作用域的功能,即使子组件被卸载,内部atom状态也会被缓存住,除非上一级RecoilRoot被卸载。
有什么办法既能跨RecoilRoot共享状态又能享受到卸载组件清除数据的便利性呢?
跨RecoilRoot数据共享实践
在查阅了官方github仓库的issue、stackoverflow等之后,都没发现有遇到与我类似情况的场景,尝试了一些清除数据的方式未果,最后在翻阅Recoil官方文档的时候发现通过Atom Effects与第三方的事件库就可以解决这个问题,实现如下:
import { atom } from "recoil";
import { v4 as uuidv4 } from "uuid";
import { BehaviorSubject } from "rxjs";
export const crossAtom = (key, defaultVal = "") => {
const myObservable = new BehaviorSubject({ val: defaultVal });
const reset = () => {
myObservable.next({ val: defaultVal });
};
const atomState = atom({
key: key,
default: defaultVal,
effects_UNSTABLE: [
({ onSet, setSelf, resetSelf }) => {
const uuid = uuidv4();
const selfObservable = myObservable.subscribe({
next: ({ val, uuid: uid }) => {
if (uuid !== uid) setSelf(val);
}
});
onSet((newVal, oldVal) => {
if (newVal === oldVal) return;
myObservable.next({ val: newVal, uuid });
});
return () => {
selfObservable.unsubscribe();
resetSelf();
};
}
]
});
return {
atomState,
reset
};
};
最后实现的效果:
demo中有3层RecoilRoot
- globalState 全局共享状态,子组件被卸载不会被重置,路由切换会被重置
- rootState 局部状态,该组件被卸载就会重置
- childState 局部状态,该组件被卸载就会重置
实现原理是每一个atom在组件内使用时,都会生成新的实例,相同的atom都订阅同一个事件源,在触发onSet事件时发布变动事件通知其他相同atom改变,因为会发生自己通知自己变动的情况导致无限循环,所以为每个atom effect生成一个唯一的uuid以作区分,此demo使用了rxjs来处理事件订阅发布,理论上任何其他方案都可替代。
完整Demo地址