从理念到LRU算法实现,起底未来React异步开发方式

公众号@魔术师卡颂 2021-10-13 17:53:04
算法 LRU 实现 理念 念到


大家好,我卡颂。

React源码内部在实现不同模块时用到了多种算法与数据机构(比如调度器使用了小顶堆)。

今天要聊的是数据缓存相关的LRU算法。内容包含四方面:

  • 介绍一个React特性
  • 这个特性和LRU算法的关系
  • LRU算法的原理
  • ReactLRU的实现

可以说是从入门到实现都会讲到,所以内容比较多,建议点个赞收藏慢慢食用。

一切的起点:Suspense

React16.6引入了SuspenseReact.lazy,用来分割组件代码。

对于如下代码:

import A from './A';
import B from './B';
function App() {
return (
<div>
<A/>
<B/>
</div>
)
}

经由打包工具打包后生成:

  • chunk.js(包含A、B、App组件代码)

对于首屏渲染,如果B组件不是必需的,可以将其代码分割出去。只需要做如下修改:

// 之前
import B from './B';
// 之后
const B = React.lazy(() => import('./B'));

经由打包工具打包后生成:

  • chunk.js(包含A、App组件代码)
  • b.js(包含B组件代码)

这样,B组件代码会在首屏渲染时以jsonp的形式被请求,请求返回后再渲染。

为了在B请求返回之前显示占位符,需要使用Suspense

// 之前,省略其余代码
return (
<div>
<A/>
<B/>
</div>
)
// 之后,省略其余代码
return (
<div>
<A/>
<Suspense fallback={<div>loading...</div>}>
<B/>
</Suspense>
</div>
)

B请求返回前会渲染<div>loading.。.</div>作为占位符。

可见,Suspense的作用是:

在异步内容返回前,显示占位符(fallback属性),返回后显示内容

再观察下使用Suspense后组件返回的JSX结构,会发现一个很厉害的细节:

return (
<div>
<A/>
<Suspense fallback={<div>loading...</div>}>
<B/>
</Suspense>
</div>
)

从这段JSX中完全看不出组件B是异步渲染的!

同步和异步的区别在于:

  • 同步:开始 -> 结果
  • 异步:开始 -> 中间态 -> 结果

Suspense可以将包裹在其中的子组件的中间态逻辑收敛到自己身上来处理(即Suspensefallback属性),所以子组件不需要区分同步、异步。

那么,能不能将Suspense的能力从React.lazy(异步请求组件代码)推广到所有异步操作呢?

答案是可以的。

resource的大作为

React仓库是个monorepo,包含多个库(比如reactreact-dom),其中有个和Suspense结合的缓存库 —— react-cache,让我们看看他的用处。

假设我们有个请求用户数据的方法fetchUser

const fetchUser = (id) => {
return fetch(`xxx/user/${id}`).then(
res => res.json()
)
};

经由react-cachecreateResource方法包裹,他就成为一个resource(资源):

import {unstable_createResource as createResource} from 'react-cache';
const userResource = createResource(fetchUser);

resource配合Suspense就能以同步的方式编写异步请求数据的逻辑:

function User({ userID }) {
const data = userResource.read(userID);
return (
<div>
<p>name: {data.name}</p>
<p>age: {data.age}</p>
</div>
)
}

可以看到,userResource.read完全是同步写法,其内部会调用fetchUser

背后的逻辑是:

  1. 首次调用userResource.read,会创建一个promise(即fetchUser的返回值)
  2. throw promise
  3. React内部catch promise后,离User组件最近的祖先Suspense组件渲染fallback
  4. promise resolve后,User组件重新render
  5. 此时再调用userResource.read会返回resolve的结果(即fetchUser请求的数据),使用该数据继续render

从步骤1和步骤5可以看出,对于一个请求,userResource.read可能会调用2次,即:

  • 第一次发送请求、返回promise
  • 第二次返回请求到的数据

所以userResource内部需要缓存该promise的值,缓存的key就是userID

const data = userResource.read(userID);

由于userIDUser组件的props,所以当User组件接收不同的userID时,userResource内部需要缓存不同userID对应的promise

如果切换100个userID,就会缓存100个promise。显然我们需要一个缓存清理算法,否则缓存占用会越来越多,直至溢出。

react-cache使用的缓存清理算法就是LRU算法。

LRU原理

LRU(Least recently used,最近最少使用)算法的核心思想是:

如果数据最近被访问过,那么将来被访问的几率也更高

所以,越常被使用的数据权重越高。当需要清理数据时,总是清理最不常使用的数据。

react-cache中LRU的实现

react-cache的实现包括两部分:

  • 数据的存取
  • LRU算法实现

数据的存取

每个通过createResource创建的resource都有一个对应map,其中:

  • mapkeyresource.read(key)执行时传入的key
  • mapvalueresource.read(key)执行后返回的promise

在我们的userResource例子中,createResource执行后会创建map

const userResource = createResource(fetchUser);

userResource.read首次执行后会在该map中设置一条userIDkeypromisevalue的数据(被称为一个entry):

const data = userResource.read(userID);

要获取某个entry,需要知道两样东西:

  • entry对应的key
  • entry所属的resource

LRU算法实现

react-cache使用「双向环状链表」实现LRU算法,包含三个操作:插入、更新、删除。

插入操作

首次执行userResource.read(userID),得到entry0(简称n0),他会和自己形成环状链表:

此时first(代表最高权重)指向n0

改变userID props后,执行userResource.read(userID),得到entry1(简称n1):

此时n0n1形成环状链表,first指向n1

如果再插入n2,则如下所示:

可以看到,每当加入一个新entryfirst总是指向他,暗含了LRU中新的总是高权重的思想。

更新操作

每当访问一个entry时,由于他被使用,他的权重会被更新为最高。

对于如下n0 n1 n2,其中n2权重最高(first指向他):

当再次访问n1时,即调用如下函数时:

userResource.read(n1对应userID);

n1会被赋予最高权重:

删除操作

当缓存数量超过设置的上限时,react-cache会清除权重较低的缓存。

对于如下n0 n1 n2,其中n2权重最高(first指向他):

如果缓存最大限制为1(即只缓存一个entry),则会迭代清理first.previous,直到缓存数量为1。

即首先清理n0

接着清理n1

每次清理后也会将map中对应的entry删掉。

完整LRU实现见react-cache LRU

总结

除了React.lazyreact-cache能结合Suspense,只要发挥想象力,任何异步流程都可以收敛到Suspense中,比如React Server Compontnt流式SSR

随着底层React18在年底稳定,相信未来这种同步写法的开发模式会逐渐成为主流。

不管未来React开发出多少新奇玩意儿,底层永远是这些基础算法与数据结构。

真是朴素无华且枯燥......

本文分享自微信公众号 - 魔术师卡颂(gh_bc4b6cbdf9ad) ,作者:卡颂

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间: 2021-10-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

版权声明
本文为[公众号@魔术师卡颂]所创,转载请带上原文链接,感谢
https://cloud.tencent.com/developer/article/1888617

  1. Error in spring source code compilation: the monoprocessor in reactor.core.publisher is outdated
  2. Buctf [geek Challenge 2019] http
  3. 20 个值得学习的 Vue 开源项目
  4. 20 projets open source à apprendre
  5. Scène et application de la manette des gaz anti - bavardage
  6. Qu'est - ce que j'ai gagné en abandonnant vue pour les six mois de React?
  7. À partir de [Bytecode cache] et de [http cache], intervieweur: « est - ce si mince? »
  8. [niveau intermédiaire et avancé] obligatoire, 30 + questions manuscrites à haute fréquence et réponses détaillées (dix mille caractères longs), voyez comment "vous" ne pouvez pas m'abattre
  9. Mise en œuvre d'un outil d'échafaudage universel pour l'ingénierie Web de 0 à 1
  10. 【中高级前端】必备,30+高频手写题及详细答案(万字长文),看“你”怎么难倒我
  11. In less than two days, the box office exceeded 400 million, and Changjin Lake broke out, breaking seven records in Chinese film history
  12. Tong Liya Jin Chen bumps her hair, Xie Na Zhao Liying bumps her shirt, and she sees EQ from the reaction
  13. react之组件生命周期
  14. L'équipe de vue dévoile un nouvel outil d'échafaudage rapide comme la foudre Create View, qui remplacera la vue CLI à l'avenir, avec seulement 300 lignes de code, apprenez - le!
  15. 20 dessins illustrant le fonctionnement du moteur de rendu du Navigateur
  16. In less than two days, the box office exceeded 400 million, and Changjin Lake broke out, breaking seven records in Chinese film history
  17. 千锋重庆web前端学习之理解CSS位置属性
  18. 什么是语义HTML标记以及如何使用它们?
  19. Si vous vous représentez avec un tableau, c'est le champ de Van Gogh.
  20. 前端面试手写代码
  21. 前端开发框架Vue中Vuex的使用原理分享
  22. vue-echarts初次体验
  23. 分享一些web前端开发好用的网站
  24. 每天读一点webpack-003
  25. react之组件生命周期
  26. Alibaba collection version of mybatis handwritten documents, Java front-end interview questions
  27. SpringBoot Java后端实现okhttp3超时设置
  28. react之組件生命周期
  29. Cycle de vie des composants de React
  30. 使用Reactor将阻塞调用变为异步非阻塞
  31. Baked cake wife sun photos, plain face on camera, beautiful appearance is still a beauty, watching children during the festival is a little helpless
  32. 亚洲知名插画师荒川(arakawa) 仅8件独版NFT作品系列《Can't Out》正式上架Element综合市场
  33. Taiyuan: singing, welcoming the national day, gathering to praise blessings
  34. Arakawa, un illustrateur Asiatique bien connu, n'a mis sur le marché que huit pièces de la collection NFT "can't out" en une seule édition.
  35. Résumé des questions d'entrevue Hadoop (II) - - hdfs
  36. 如何解决“Serverless”系统的冷启动问题
  37. BootstrapBlazor 模板安装
  38. BootstrapBlazor 模板安装
  39. Tong Liya Jin Chen bumps her hair, Xie Na Zhao Liying bumps her shirt, and she sees EQ from the reaction
  40. 使用ESLint+Prettier来统一前端代码风格
  41. 为什么说 Node.js 是实时应用程序开发的绝佳选择
  42. PaddlePaddle:在 Serverless 架构上十几行代码实现 OCR 能力
  43. 使用elementui在完成项目中遇到的未知知识点2
  44. On the mechanism of webpack loader
  45. 云原生体系下 Serverless 弹性探索与实践
  46. vue开发技巧
  47. Une fleur merveilleuse de l'histoire de l'industrie des nouveaux véhicules énergétiques, Zhongtai Jiangnan T11, une voiture vintage que vous n'avez jamais vue
  48. 致敬!再见了!LayUI !
  49. Vue安装和卸载
  50. Implement a flipped character with the transform attribute of CSS
  51. 你的第一个 Docker + React + Express 全栈应用
  52. [apprentissage de l'algorithme] 1486. Fonctionnement exclusif du tableau (Java / C / C + + / python / go / Rust)
  53. Zhang Daxian sends a blessing video on xYG relay, showing positive energy in details
  54. 前端技巧-JS元编程ES6 symbol公开符号
  55. Article de 37 ans seul à l'hôpital!Il boitait, soupçonnait d'être blessé, souriait avec douleur
  56. 前端推荐!10分钟带你了解Konva运行原理
  57. npm ERR! iview-project@3.0.0 init: `webpack --progress --config webpack.dev.config.js
  58. 零基础学习Web前端需要注意什么呢?
  59. The Youth League promotes Yiyang Qianxi new film, and the relationship between the two generation and the generation is good. Li Fei is blessed.
  60. Qu'est - ce qu'il faut remarquer à l'avant - plan Web de l'apprentissage de base zéro?