大家好,我卡颂。
前几天写的一篇介绍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
链,传参的稳固对应了链表的稳固,并最终对应了返回值的稳固。