【重识前端】React源码阅读(二)2000字告诉你Fiber设计动机


title: 【重识前端】React源码阅读(二)2000字告诉你Fiber设计动机
date: 2021-04-05 10:08:31
tags: [react]
category: [重拾前端]
cover: /image/cover/react.png

前言

上一篇挖的坑几乎都是Fiber有关的,本来想先学习一下Hooks,发现也和FIber密切相关了,决定先搞定一下Fiber。不了解的可以查看我的主页。Fiber架构React写了几年时间,一篇文章肯定是讲不完的,我决定分为四个阶段介绍。

  • 动机(或者说初衷
  • 初始化
  • render
  • 更新

看了网上的文章大都浅尝辄止,就写了一些简单的介绍,我也照猫画虎然后去面试,被打回了。比如几个问题,如果你能答的不错说明你的Fiber知识还不错。

  1. 都说Fiber是有优先级,有打断任务的机制,但是JavaScript他是单线程的,在计算的时候如何打断呢?
  2. 数据结构为什么选择了链表作为数据结构?别的不可以吗?比如,数组?
  3. 为什么有的时候render会执行多次?
  4. Fiber架构一定是异步的吗?
  5. Fiber树和虚拟DOM树有什么区别?

这些都是我面试遇到的问题…太深了,网上的文章几乎没有提到,所以我决定自己来学习一下,然后分享并记录我的学习过程。如果有错误的地方,还请一起讨论。有任何问题都可以添加我的v:「DerrickTel」 和我讨论。

麻烦写一下备注,谢谢。

前置小知识

进程和线程

多线程可以并行处理任务,但是线程是不能单独存在的,它是由进程来启动和管理的。那什么又是进程呢?

一个进程就是一个程序的运行实例。详细解释就是,启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程。

因为不是核心,所以我这里给一个简单的例子帮助理解:

进程是火车头,线程是火车的车厢。没有火车头,车厢无法自己运行。一个火车头可以拉多个车厢。

单线程的JavaScript和多线程的浏览器

大家第一次接触到JavaScript听到最多的恐怕就是JavaScript 是单线程的,更有甚者会知道为什么JavaScript要是单线程的。

试想如果渲染线程和 JavaScript 线程同时在工作,那么渲染结果必然是难以预测的:比如渲染线程刚绘制好的画面,可能转头就会被一段 JavaScript 给改得面目全非。这就决定了JavaScript 线程和渲染线程必须是互斥的:这两个线程不能够穿插执行,必须串行。当其中一个线程执行时,另一个线程只能挂起等待。

所以JavaScript是单线程的。那为什么浏览器又是多线程的?因为在早期的单线程浏览器会碰到几个问题:

  1. 不稳定
    1. 比如一个Web视频或Web游戏,这些可能都需要强大的插件来帮助解决问题。但是插件最容易出现BUG,一个BUG崩溃了,那就会导致整个浏览器的崩溃
  2. 不流畅
    1. 就一个线程,如果被一个任务占据了大量的事情,甚至是死循环,也会让浏览器处于停滞状态
  3. 不安全
    1. 如果单线程的插线在运行时,是一个病毒或者恶意程序,你的电脑将面临巨大的风险

所以浏览器就需要多线程来处理这些遗留问题,并且可以保证速度的同时保证安全。比如网络线程,可以把数据都弄清楚了,等待JavaScript来提取,就好像洗衣服,我放洗衣店洗,我吃完饭来拿就好了。不需要我自己手动的去洗。但是也会带来几个问题

  1. 更多的系统资源占用。
  2. 更复杂的浏览器架构。(不是本文重点

单线程的JavaScript有自己的运行机制「事件循环」。可以在我的主业查看文章进行阅读。

React的卡顿

React15及之前的版本。都是进行两颗树的比较,然后找到需要更新的地方,最后更新到真实的DOM树上。而这个算法是在数据结构中的两棵树的比较中优化而来。也从原本 O(n3) 的 Diff 时间复杂度优化到了前无古人的 O(n)。我们称之为「 Stack Reconciler 」。在很长的一段时间风光无限。

但是,随着时间的推移和业务复杂度的提升,React 曾经被人们津津乐道的 Stack Reconciler 也渐渐在体验方面显出疲态。

为什么会出现乏力的感觉?

因为从本质上来说,栈调和机制下的 Diff 算法,其实是树的深度优先遍历的过程。而树的深度优先遍历,总是和递归脱不了关系。

递归就是除非碰到了他的返回条件,否则,将无线的循环下去。这个问题最致命的就是,无法停止。加入足够大,将会出现「页面卡死」。

FIber的出现

首先Fiber的翻译是:「纤维」。

维基百科:「纤维(英语:fiber)是指由连续或不连续的细丝组成的物质。」

简单来说就是非常的细的东西,也就是对整个渲染过程进行纤维级别的控制。

Fiber如何解决问题

Fiber是对整个React的调度算法的重写,又是一个React自创的数据结构,里面保存了一个虚拟DOM的节点,也保存了需要更新的状态,以及副作用。

我们知道,用户在使用的时候最怕的是什么?没反应,也就是「页面卡死」。而我们玩游戏觉得卡是因为他一秒钟的帧数少于60帧,所以我们觉得卡,而浏览器的页面也一样。React只需要一秒钟能够保证60帧的流畅出现就可以了,用户就不觉得「页面卡死」了。

所以Fiber的任务肯定是,在保证至少60帧每秒的情况下,才去做别的事情。

Fiber架构核心:“可中断”“可恢复”“优先级”

Fiber所做的这一系列操作都是为了用户的丝滑体验。

想象一下,打断现在非常冗长的计算,通过优先级的判断,优先渲染页面或者别的什么。然后再恢复冗长的计算。是不是脑中基本有一个模糊的概念了?

在进一步,React其实为很多任务进行了切片,这些每一个细小的片,我们可以成为「Fiber」。在执行完一个「Fiber」的时候,会检查队列中最高优先级的任务。如果有比目前的这个优先级高的,先把数据和状态保存在「Fiber」节点上,然后进行那个「更高优先级任务」。之后再检查,如果没有就恢复任务。

在 render 阶段,一个庞大的更新任务被分解为了一个个的工作单元,这些工作单元有着不同的优先级,React 可以根据优先级的高低去实现工作单元的打断和恢复。由于 render 阶段的操作对用户来说其实是“不可见”的,所以就算打断再重启,对用户来说也是 0 感知。但是,工作单元(也就是任务)的重启将会伴随着对部分生命周期的重复执行,这些生命周期是:

  • componentWillMount

  • componentWillUpdate

  • shouldComponentUpdate

  • componentWillReceiveProps

其中 shouldComponentUpdate 的作用是通过返回 true 或者 false,来帮助我们判断更新的必要性,一般在这个函数中不会进行副作用操作,因此风险不大。

而 “componentWill” 开头的三个生命周期,则常年被开发者以各种各样的姿势滥用,是副作用的“重灾区”。

思考

之前的问题,现在是否有的已经有答案了?有的还没有?欢迎讨论

  1. 都说Fiber是有优先级,有打断任务的机制,但是JavaScript他是单线程的,在计算的时候如何打断呢?
  2. 数据结构为什么选择了链表作为数据结构?别的不可以吗?比如,数组?
  3. 为什么有的时候render会执行多次?
  4. Fiber架构一定是异步的吗?
  5. Fiber树和虚拟DOM树有什么区别?

索引

https://zh-hans.reactjs.org/docs

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值