大家好,我卡颂。

前几天写的一篇介绍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组件重复renderfetch(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保留。

WeakMapMap的区别在于 —— 在WeakMap中,key到他对应的value是弱援用。这意味着当没有其余数据援用这个key时,他能够被垃圾回收。而在Map中,keyvalue是强援用,即便没有其余数据援用这个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链,传参的稳固对应了链表的稳固,并最终对应了返回值的稳固。