今日头条
PLC程序解锁解码【电;I7I54833762】方法和思路分享,首先是设置 subscription 的 onStateChange(它初始是个空方法,需要注入实现),它会在触发更新时调用,它这里希望将来调用的是subscription.notifyNestedSubs
,subscription.notifyNestedSubs
会触发这个 subscription 收集的所有子订阅。也就是说这里的更新回调和『更新』没有直接关系,而是触发子节点们的更新方法。
然后调用了subscription.trySubscribe()
,它会将自己的 onStateChange 交给父级 subscription 或者 redux 去订阅,将来由它们触发 onStateChange
最后它会判断之前的 state 和最新的是否一致,如果不一致会调用subscription.notifyNestedSubs()
,它会触发这个 subscription 收集的所有子订阅从而更新它们。
返回了注销相关的函数,它会注销在父级的订阅,将subscription.onStateChange
重新置为空方法。这个函数会在组件卸载或 re-render (仅 store 变化时)时被调用(react useEffect 的特性)。
Provider 有很多地方都涉及到了 subscription,subscription 的那些方法只是讲了大概功能,关于 subscription 的细节会在后面 subscription 的部分讲到。
这里会讲到 Provider 中出镜率很高的 subscription 部分,它是 react-redux 能够嵌套收集订阅的关键。其实这个部分的标题叫做 Subscription
已经不太合适了,在 8.0.0 版本之前,react-redux 确实是通过 Subscription class 实现它的,你可以通过new Subscription()
使用创建 subscription 实例。但在 8.0.0 之后,已经变成了createSubscription
函数创建 subscription 对象,内部用闭包替代原先的属性。
用函数替代 class 有一个好处是,不需要关心 this 的指向,函数返回的方法修改的永远是内部的闭包,不会出现 class 方法被赋值给其他变量后出现 this 指向变化的问题,降低了开发时的心智负担。闭包也更加私有化,增加了变量安全。同时在一个支持 hooks 的库里,用函数实现也更符合开发范式。
addNestedSub
非常巧妙的运用了递归,它里面又调用了trySubscribe
。于是它们就会达到这样的目的,当最底层subscription
发起trySubscribe
想被父级收集订阅时,它会首先触发父级的trySubscribe
并继续递归直到根subscription
,如果我们把这样的层级结构想象成树的话(其实 subscription.trySubscribe 也确实发生在组件树中),那么就相当于从根节点到叶子节点依次会被父级收集订阅。因为这是由叶子节点先发起的,这时除了叶子节点,其他节点的订阅回调还没有被设置,所以才设计了handleChangeWrapper
这个回调外壳,注册的只是这个回调外壳,在将来非叶子节点设置好回调后,能被外壳触发。
在『递』过程结束后,从根节点开始到这个叶子节点的订阅回调handleChangeWrapper
都正在被父级收集了,『归』的过程回溯做它的本职工作return listeners.subscribe(listener)
,将子subscription
的订阅回调收集到收集器listeners
中(将来更新发生时会触发相关的handleChangeWrapper
,而它会间接的调用收集到所有的 listener)。
所以每个subscription
的addNestedSub
都做了两件事:1. 让自己的订阅回调先被父级收集;2. 收集子subscription
的订阅回调。
结合addNestedSub
的解释再回过头来看trySubscribe
,它想让自己的订阅回调被父级收集,于是当它被传入父级subscription
时,就会调用它的addNestedSub
,这会导致从根subscription
开始每一层subscription
都被父级收集了回调,于是每个subscription
都嵌套收集了它们子subscription
,从而父级更新后子级才更新成为了可能。同时,因为unsubscribe
这个锁的存在,如果某个父级subscription
的trySubscribe
被调用了,并不会重复的触发这个『嵌套注册』。
useIsomorphicLayoutEffectWithArgs
是一个工具函数,内部是useIsomorphicLayoutEffect
,这个函数前面也讲过。它们最终做的是:将第 2 个数组参数的每项作为参数给第一个参数调用,第 3 个参数是useIsomorphicLayoutEffect
的缓存依赖。
被执行的第一个参数captureWrapperProps
,它主要功能是判断如果是来自 store 的更新,则在更新完成后(比如 useEffect)触发subscription.notifyNestedSubs
,通知子订阅更新。
接着它想生成actualChildProps
,也就是 select 出来的业务组件需要的 props,其中主要使用了useSyncExternalStore
,如果你追到useSyncExternalStore
的代码里看,会发现它是一个空方法,直接调用会抛出错误,所以它是由外部注入的。在入口index.ts
里,initializeConnect(useSyncExternalStore)
对它进行初始化了,useSyncExternalStore
来自 React 。所以actualChildProps
实际是React.useSyncExternalStore( subscribeForReact, actualChildPropsSelector, getServerState ? () => childPropsSelector(getServerState(), wrapperProps) : actualChildPropsSelector)
的结果。
useSyncExternalStore是 react18 的新 API,前身是useMutableSource,为了防止在 concurrent 模式下,任务中断后第三方 store 被修改,恢复任务时出现tearing
从而数据不一致。外部 store 的更新可以通过它引起组件的更新。在react-redux8
之前,是由useReducer
手动实现的,这是react-redux8
首次使用新 API。这也意味着你必须跟着使用 React18+。但我认为其实 react-redux8
可以用 shim: import { useSyncExternalStore } from 'use-syncexternal-store/shim';
来做到向下兼容。
审核编辑:符乾江
全部0条评论
快来发表一下你的评论吧 !