作者:zxg_神说要有光
React 的 hooks 是在 fiber 之后出现的特性,所以很多人误以为 hooks 是必须依赖 fiber 才能实现的,其实并不是,它们俩没啥必然联系。
现在,不止 react 中实现了 hooks,在 preact、react ssr、midway 等框架中也实现了这个特性,它们的实现就是不依赖 fiber 的。
我们分别来看一下这些不同框架中的 hooks 都是怎么实现的:
react 是通过 jsx 描述界面的,它会被 babel 或 tsc 等编译工具编译成 render function,然后执行产生 vdom:
这里的 render function 在 React17 之前是 React.createElement:
在 React 17 之后换成了 jsx:
这个 jsx-runtime 会自动引入,不用像之前那样每个组件都要保留一个 React 的 import 才行。
render function 执行产生 vdom:
vdom 的结构是这样的:
在 React16 之前,会递归渲染这个 vdom,增删改真实 dom。
而在 React16 引入了 fiber 架构之后就多了一步:首先把 vdom 转成 fiber,之后再渲染 fiber。
vdom 转 fiber 的过程叫做 reconcile,最后增删改真实 dom 的过程叫做 commit。
为什么要做这样的转换呢?
因为 vdom 只有子节点 children 的引用,没有父节点 parent 和其他兄弟节点 sibling 的引用,这导致了要一次性递归把所有 vdom 节点渲染到 dom 才行,不可打断。
万一打断了会怎么样呢?因为没有记录父节点和兄弟节点,那只能继续处理子节点,却不能处理 vdom 的其他部分了。
所以 React 才引入了这种 fiber 的结构,也就是有父节点 return、子节点 child、兄弟节点 sibling 等引用,可以打断,因为断了再恢复也能找到后面所有没处理过的节点。
fiber 节点的结构是这样的:
这个过程可以打断,自然也就可以调度,也就是 schdule 的过程。
所以 fiber 架构就分为了 schdule、reconcile(vdom 转 fiber)、commit(更新到 dom)三个阶段。
函数组件内可以用 hooks 来存取一些值,这些值就是存在 fiber 节点上的。
比如这个函数组件内用到了 6 个 hook:
那么对应的 fiber 节点上就有个 6 个元素的 memorizedState 链表:
通过 next 串联起来:
不同的 hook 在 memorizedState 链表不同的元素上存取值,这就是 react hooks 的原理。
这个链表有创建阶段和更新阶段,所以你会发现 useXxx 的最终实现都分为了 mountXxx 和 updateXxx:
这里的 mount 阶段就是创建 hook 节点并组装成链表的:
会把创建好的 hook 链表挂到 fiber 节点的 memorizedState 属性上。
那更新的时候自然也就能从 fiber 节点上取出这个 hook 链表:
这样在多次渲染中,useXxx 的 api 都能在 fiber 节点上找到对应的 memorizedState。
这就是 react hooks 的原理,可以看到它是把 hook 存在 fiber 节点上的。
那 preact 有什么不同呢?
preact 是兼容 react 代码的更轻量级的框架,它支持 class 组件和 function 组件,也支持了 hooks 等 react 特性。不过它没有实现 fiber 架构。
因为它主要考虑的是体积的极致(只有 3kb),而不是性能的极致。
刚才我们了解了 react 是把 hook 链表存放在 fiber 节点上的,那 preact 没有 fiber 节点,会把 hook 链表存在哪呢?
其实也很容易想到,fiber 只是对 vdom 做了下改造用于提升性能的,和 vdom 没啥本质的区别,那就把 hook 存在 vdom 上不就行了?
确实,preact 就是把 hook 链表放在了 vdom 上。
比如这个有 4 个 hooks 的函数组件:
它的实现就是在 vdom 上存取对应的 hook:
它没有像 react 那样把 hook 分为 mount 和 update 两个阶段,而是合并到一起处理了。
如图,它把 hooks 存在了 component.__hooks 的数组上,通过下标访问。
这个 component 就是 vdom 上的一个属性:
也就是把 hooks 的值存在了 vnode._component._hooks 的数组上。
对比下 react 和 preact 实现 hooks 的差异:
所以说,hooks 的实现并不依赖 fiber,它只不过是找个地方存放组件对应的 hook 的数据,渲染时能取到就行,存放在哪里是无所谓的。
因为 vdom、fiber 和组件渲染强相关,所以存放在了这些结构上。
像 react ssr 实现 hooks,就既没有存在 fiber 上,也没有存在 vdom 上:
其实 react-dom 包除了可以做 csr 外,也可以做 ssr:
csr 时使用 react-dom 的 render 方法:
ssr 的时候使用 react-dom/server 的 renderToString 方法或 renderToStream 方法:
大家觉得 ssr 的时候会做 vdom 到 fiber 的转换么?
肯定不会呀,fiber 是为了提高在浏览器中运行时的渲染性能,把计算变成可打断的,在空闲时做计算,才引入的一种结构。
服务端渲染自然就不需要 fiber。
不需要 fiber 的话,它把 hook 链表存放在哪里呢?vdom 么?
确实可以放在 vdom,但是其实并没有。
比如 useRef 这个 hooks:
它是从 firstWorkInProgressHook 开始的用 next 串联的一个链表。
而 firstWorkInProgressHook 最开始用 createHook 创建的第一个 hook 节点:
并没有挂载到 vdom 上。
为什么呢?
因为 ssr 只需要渲染一次呀,又不需要更新,自然没必要挂到 vdom 上。
只要每次处理完每个组件的 hooks 就清空一下这个 hook 链表就行:
所以,react ssr 时,hooks 是存在全局变量上的。
对比下 react csr 和 ssr 时的 hooks 实现原理的区别:
hooks 的实现原理其实不复杂,就是在某个上下文中存放一个链表,然后 hooks api 从链表不同的元素上访问对应的数据来完成各自的逻辑。这个上下文可以是 vdom、fiber 甚至是全局变量。
不过 hooks 这个思想还是挺火的,淘宝出的服务端框架 midway 就在引入了 hooks 的思想:
midway 是一个 Node.js 框架:
服务端框架自然就没有 vdom、fiber 这种结构,不过 hooks 的思想并不依赖这些,实现 hooks 的 api 只需要在某个上下文放一个链表就行。
midway 就实现了类似 react hooks 的 api:
具体它这个 hook 链表存在哪我还没看,不过我们已经掌握 hooks 的实现原理了,只要有个上下文存放 hook 链表就行,在哪都可以。
react hooks 是在 react fiber 架构之后出现的特性,很多人误以为 hooks 必须配合 fiber 才能实现,我们分别看了 react、preact、react ssr、midway 中的 hooks 的实现,发现并不是这样的:
所以,react hooks 必须依赖 fiber 才能实现么?
明显不是,搭配 fiber、搭配 vdom、搭配全局变量,甚至任何一个上下文都可以。
近年来,在AIOps领域快速发展的背景下,IT工具、平台能力、解决方案、AI场景及可用数据集的迫切需求在各行业迸发。基于此,云智慧在2021年8月发布了AIOps社区, 旨在树起一面开源旗帜,为各行业客户、用户、研究者和开发者们构建活跃的用户及开发者社区,共同贡献及解决行业难题、促进该领域技术发展。
社区先后 开源 了数据可视化编排平台-FlyFish、运维管理平台 OMP 、云服务管理平台-摩尔平台、 Hours 算法等产品。
可视化编排平台-FlyFish:
项目介绍:https://www.cloudwise.ai/flyFish.html
Github地址: https://github.com/CloudWise-OpenSource/FlyFish
Gitee地址: https://gitee.com/CloudWise/fly-fish
行业案例:https://www.bilibili.com/video/BV1z44y1n77Y/
部分大屏案例:
我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定
有什么方法可以告诉sidekiq一项工作依赖于另一项工作,并且在后者完成之前无法开始? 最佳答案 仅使用Sidekiq;答案是否定的。正如DickieBoy所建议的那样,您应该能够在依赖作业完成时将其启动。像这样。#app/workers/hard_worker.rbclassHardWorkerincludeSidekiq::Workerdefperform()puts'Doinghardwork'LazyWorker.perform_async()endend#app/workers/lazy_worker.rbclassLaz