React18 的新特性 Concurrent 可以让 reconcile 过程变得可中断,不会因为长时间占用主线程而阻塞渲染进程,从而提升性能。但是,当使用第三方库的时候,该模式会出现状态不一致的情形,所以 React18 提出 useSyncExternalStore,已解决该类问题。
官网对其介绍为:
useSyncExternalStore is a hook recommended for reading and subscribing from external data sources in a way that’s compatible with concurrent rendering features like selective hydration and time slicing.
作为一个从外部数据源读取和订阅的 hook,用法如下:
js 复制代码//subscribe:函数注册一个回调,该回调在存储更改时被调用
//getSnapshot:返回存储的当前值的函数
//getServerSnapshot:回服务器渲染过程中使用的快照的函数
const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);
因为兼容性的原因,Valtio 通过 shim 引入 useSyncExternalStore。
useSnapshot 源码如下:
js 复制代码//proxyObject:
//options:可选参数,type Options = {sync?: boolean}
export function useSnapshot(
proxyObject: T,
options?: Options
): Snapshot {
//通过 sync 设置 notifyInSync,是否同步通知
const notifyInSync = options?.sync
//存储上一个快照
const lastSnapshot = useRef<Snapshot>()
//存粗上一个受影响的对象
const lastAffected = useRef<WeakMap<object, unknown>>()
//true 表示组件正在渲染
let inRender = true
/使用 useSyncExternalStore,
const currSnapshot = useSyncExternalStore(
//定义一个 useCallback,在其中调用 subScribe
useCallback(
(callback) => {
const unsub = subscribe(proxyObject, callback, notifyInSync)
callback() // Note: do we really need this?
return unsub
},
[proxyObject, notifyInSync]
),
//创建一个函数来获取快照,若快照没有变换,则返回上一个快照
() => {
const nextSnapshot = snapshot(proxyObject, use)
try {
if (
!inRender &&
lastSnapshot.current &&
lastAffected.current &&
//proxy-compare
!isChanged(
lastSnapshot.current,
nextSnapshot,
lastAffected.current,
new WeakMap()
)
) {
// not changed
return lastSnapshot.current
}
} catch (e) {
// ignore if a promise or something is thrown
}
return nextSnapshot
},
() => snapshot(proxyObject, use)
)
//false 表示渲染结束
inRender = false
//创建一个 WeakMap 用来存储当前受影响的对象
const currAffected = new WeakMap()
//设置 lastSnapshot,lastAffected
useEffect(() => {
lastSnapshot.current = currSnapshot
lastAffected.current = currAffected
})
//非生产环境下,使用 useAffectedDebugValue 调试受影响的部分
if (import.meta.env?.MODE !== ‘production’) {
// eslint-disable-next-line react-hooks/rules-of-hooks
useAffectedDebugValue(currSnapshot, currAffected)
}
//通过 useMemo 创建一个 WeakMap,用于每个 hook 的代理缓存
const proxyCache = useMemo(() => new WeakMap(), []) // per-hook proxyCache
//proxy-compare,使用 createProxyToCompare 创建一个包裹在代理中的快照,并 return 出去
return createProxyToCompare(
currSnapshot,
currAffected,
proxyCache,
targetCache
)
}
所以当从快照中读取数据并改变数据源的时候,组件只会在访问的状态部分发生改变时重新渲染。
至此,Valtio 核心源码逻辑都被一一分析,当然还会有其他的 API,可以在源码中很快了解它
作者:巧克力脑袋
链接:https://juejin.cn/post/7223243191654645815
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
从上面的源码也可以看出来,Redux 存在一个很明显的问题,那就是需要通过遍历 reducer 来匹配到对应的 action.type。
那么这里有没有优化空间呢?为什么 action 和 reducer 必须手写 switch...case 来匹配呢?如果将 action.type 作为函数名,这样是否就能减少心智负担呢?
这些很多人都想到了,所以 Rematch 和 Dva 就在这之上做了一系列优化,Redux 也吸取了他们的经验,重新造了 @reduxjs/toolkit。
关于 Redux 更详细的原理和解释,可以参考我的这篇文章:从零实现 redux 和 react-redux
6.2 Atom
从上图可以看出来,相比 Redux 维护的全局 Store,Recoil 则是使用了分散式的 Atom 来管理,方便进行代码分割。
定义一个 Atom 很简单,使用 atom 函数可以返回一个可写可订阅的 RecoilState 对象。它接收一个唯一标致的 key,和一个默认值 default。
import { atom } from 'recoil';
const counterState = atom({
key: 'counter',
default: 0,
});