共计 2305 个字符,预计需要花费 6 分钟才能阅读完成。
大家好,我卡颂。
前几天写的一篇介绍 use
这个新 hook
的文章中聊到 React
原生实现了一个缓存函数的办法 —— cache
。
对于如下代码,被 cache
包裹的函数,当屡次调用时,如果传参不变,会始终返回缓存值:
const cacheFn = cache(fn);
cacheFn(1, 2, 3);
// 不会执行 fn,间接返回缓存值
cacheFn(1, 2, 3);
React
内为什么须要 cache
办法呢?思考如下组件:
const fetch = cache(fetchUserData);
function User({id}) {const {name} = use(fetch(id));
return <p>{name}</p>;
}
User
组件会依据用户 id
申请用户数据,并渲染用户名。
如果 id
扭转,那么 fetch
办法从新发动申请是失常逻辑。
然而,React
组件常常 render
,如果在id
不变的状况下,因为 User
组件 render
导致一直发动申请,显然是不合理的。
所以,这种状况下就须要 cache
办法。当 id
不变时,即便 User
组件重复 render
,fetch(id)
都返回同一个值。
本文来聊聊 cache
的源码实现。
欢送退出人类高质量前端框架群,带飞
剖析实现思路
整个办法实现一共有 64 行代码,首先咱们来剖析下实现要点。
如果参数不变,则应用缓存的值。这意味着咱们须要解决:
参数的程序
举个例子,当参数程序变了,不应用缓存值:
const cacheFn = cache(fn);
cacheFn(1, 2, 3);
// 不应用缓存值
cacheFn(3, 2, 1);
区别解决援用类型、原始类型参数
举个例子,当同一地位的参数传递了同一个援用类型值,则返回缓存值:
const cacheFn = cache(fn);
const obj = {};
cacheFn(1, obj, 3);
// 返回缓存值
cacheFn(1, obj, 3);
当同一地位的参数传递了不同援用类型值,则不返回缓存值:
const cacheFn = cache(fn);
const obj = {};
cacheFn(1, obj, 3);
// 不返回缓存值
cacheFn(1, {}, 3);
缓存的垃圾回收
缓存数据时,要留神 缓存生效然而援用的数据没有开释 造成的内存透露问题。
所以,对于援用类型数据,能够应用 WeakMap
保留。
对于原始类型数据,能够应用 Map
保留。
WeakMap
与 Map
的区别在于 —— 在 WeakMap
中,key
到他对应的 value
是弱援用。这意味着当没有其余数据援用这个 key
时,他能够被垃圾回收。而在 Map
中,key
到 value
是强援用,即便没有其余数据援用这个key
,他也不会被垃圾回收。
实现原理
本文不会介绍具体的代码实现(大段贴代码让人看起来头疼)。
我会用示例图解说实现原理。理解原理后,如果你对实现细节感兴趣,能够参考:
- cache 的源码实现 PR
- cache 的在线示例
对于如下代码:
const cacheFn = cache(fn);
const obj = {};
cacheFn(1, obj, 3);
cacheFn
的每个传参,对应 cache
外部的一个 cacheNode
节点:
// CacheNode 构造函数
function createCacheNode<T>(): CacheNode<T> {
return {
s: UNTERMINATED,
v: undefined,
o: null,
p: null
};
}
字段的意义如下:
- s:
cacheNode
的缓存状态,有 未停止 / 停止 / 产生谬误 3 种状态 - v:
cacheNode
缓存的值 - o:缓存的援用类型值
- p:缓存的原始类型值
上述 cacheFn
执行后会生成如下 cacheNode
链式构造:
让咱们看看这个链式构造如何解决文章开篇提到的 3 个问题。
如何解决参数的程序?
能够看到,上图中最初一个 cacheNode
节点的状态(cacheNode.s
)为 停止。
如果后续执行 cacheFn
传入雷同的参数,则会复用缓存的 cacheNode
节点。
如果所有传参都雷同,那么会复用残缺的 cacheNode
链,此时最初一个 cacheNode
节点为 停止 状态,则不须要从新执行 cacheFn
办法计算返回值,而是间接返回缓存的值(cacheNode.v
)。
如果后续执行 cacheFn
,传入新的参数,则前后的cacheNode
链不会统一。
比方:
// 第一次
cacheFn(1, obj, 3);
// 第二次
cacheFn(1, 3, obj);
则第二次生成的 cacheNode
链中,第二个节点就与之前不同(之前 obj,之后 3),则后续 cacheNode
节点也不会雷同。
通过这种链式构造,保障了只有当所有参数保持一致,能力返回缓存的值。否则将从新执行函数,并缓存新的返回值与 cacheNode
链。
如何解决援用类型值
能够从图中发现,对于援用类型参数(比方示例中的 obj
),对应一个weakMap
节点。
这不仅意味着当没有其余数据援用他时,这个 cacheNode
节点可能开释内存,同时也意味着这个 cacheNode
之后的 cacheNode
链会断掉,他们占用的内存也会开释。
而原始类型值不存在这样的问题,从图中能够发现,原始类型值对应一个 map
节点。
总结
cache
办法是 React
外部实现,将来会裸露给开发者应用的缓存办法,能够缓存任意函数。
当屡次执行并传递雷同的参数给 cache
包裹的函数时,后续执行会返回缓存的值。
这是为了应答 某些函数须要在 React 组件屡次 render 间返回稳固的值 的场景。
比方:对于雷同的传参,申请数据的函数返回同一个promise
。
cache
的实现形式是 —— 基于传参,结构一条 cacheNode
链,传参的稳固对应了链表的稳固,并最终对应了返回值的稳固。