Skip to content

valito

Posted on:June 7, 2023 at 03:32 AM

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,
});