Skip to main content
React Fiber

提出背景

浏览器渲染限制

浏览器的帧

页面是一帧一帧绘制出来的,当每秒绘制的帧数(FPS)达到 60 时,页面是流畅的,小于这个值时,用户会感觉到卡顿。

上图是一帧内需要完成的任务,浏览器在一帧内可完成如下任务:

  • 输入事件捕捉

    • 浏览器首先会接受用户的输入事件,如点击、滚动、触摸等,并准备将其转换为相应的处理逻辑。
      • Blocking input events(阻塞输入事件):例如 touchwheel
      • Non-blocking input events(非阻塞输入事件):例如 clickkeypress
  • 事件回调执行

    • 对于已注册的事件监听器,浏览器会执行相应的事件回调函数,处理用户输入或页面状态的变化。
  • 帧开始(Begin frame)

    • 每一帧事件(Per frame events),例如 window resizescrollmedia query change
  • 样式计算和布局

    • rAF(requestAnimationFrame)
    • 浏览器会解析和计算CSS样式,确定页面中每个元素的尺寸和位置。这个过程称为布局或重排。
  • 绘制渲染(Paint):合成更新(Compositing update)、重绘部分节点(Paint invalidation)和 Record

    • 绘制(渲染):

      • 在确定了元素的样式和位置后,浏览器会开始绘制页面。
      • 这包括将文本、图像和其他内容绘制到屏幕上。绘制操作可能涉及多个层级的渲染,最终合成到屏幕上显示。
    • 合成:

      • 如果页面使用了硬件加速(如CSS3D转换或WebGL),浏览器会将不同层级的渲染结果合成到一起,形成一个完整的页面图像。
  • 空闲回调执行:如果在一帧的剩余时间内还有空闲时间,并且存在待执行的空闲回调(如RequestIdleCallback),浏览器会尝试执行这些回调。这些回调通常用于执行非关键性的、可延迟的任务。

浏览器的渲染过程受到设备刷新率的限制。一般来说,设备的刷新率是60Hz,意味着每秒钟有60帧的渲染机会。因此,浏览器需要在大约16.67毫秒(1000毫秒/60帧)内完成上述所有任务,以确保流畅的动画和用户交互。


Sewen4/9/2024About 28 minReactReact Fiber
React 性能优化

如何提高组件的渲染效率的?在React中如何避免不必要的render?

原理

React 组件 render的触发时机:

  1. 类组件通过调用 setState 方法触发 render
  2. 父组件一旦发生render渲染,子组件一定也会执行render渲染;

父组件渲染导致子组件渲染,但子组件并没有发生任何改变,此时可以从避免多余的渲染。

例如:当我们想要更新一个子组件的时候,如下图绿色部分:


Sewen4/28/2024About 7 minReactReact 性能优化
React组件通信

单向数据流

当前组件的 state 以 props 的形式流动时,组件不会改变接收的数据,只会监听数据的变化,当数据发生变化时它们会使用接收到的新值,而不是去修改已有的值。

通信组件 方法 介绍
父->子 组件通信 父组件通过传递 Props 给子组件
子->父 组件通信 子组件调用父组件函数参数
父组件传递给子组件的是一个绑定了自身上下文的函数
那么子组件在调用该函数时就可以将想要交给父组件的数据以函数入参的形式给出去
兄弟组件通信 状态提升:将两个兄弟组件公共状态提升到公共父组件中,通过父组件 props 和 事件传递
跨层级的组件通信 Context API
任意组件通信 发布订阅机制、Redux

Sewen4/18/2024About 3 minReactReact
生命周期

React15 的生命周期

  • constructor()
  • componentWillReceiveProps()
  • shouldComponentUpdate()
  • componentWillMount()
  • componentWillUpdate()
  • componentDidUpdate()
  • componentDidMount()
  • render()
  • componentWillUnmount()

Sewen4/14/2024About 11 minReactReact
编译 JSX

Babel 编译结果

Babel 可以通过特定的插件(如 @babel/plugin-transform-react-jsx)将 JSX 语法转换为 React.createElement 调用,例如:

<div>
  < img src="avatar.png" className="profile" />
  <Hello />
</div>

Sewen4/11/2024About 3 minReactReact组件渲染
DOM 渲染

React DOM 的渲染采用 render 方法

虚拟 DOM 转成 真实 DOM 流程

创建虚拟 DOM

假设需要渲染如下真实 DOM:

<ul id="list">
  <li class="item">Item1</li>
  <li class="item">Item2</li>
  <li class="item">Item3</li>
</ul>

Sewen4/11/2024About 6 minReactReact组件渲染
组件渲染流程

React 的渲染工作流是值从组件数据改变组件实际更新发生的过程。

  1. 开发者编写 JSX 代码

    • 开发者使用 JSX 语法编写用户界面的代码。
    • JSX 允许开发者在 JavaScript 代码中编写类似 HTML 的结构,从而创建 React 元素和组件。
  2. Babel 编译

    • JSX 代码需要被转换成浏览器可以理解的 JavaScript 代码。
    • Babel 是一个 JavaScript 编译器,它可以将现代 JavaScript 代码和 JSX 转换为向后兼容的 JavaScript 代码。
    • Babel 会将 JSX 标签转换为 React.createElement 调用。例如,<div>Hello, world!</div> 会被转换成 React.createElement('div', null, 'Hello, world!')
  3. React.createElement 调用

    • 转换后的 JavaScript 代码会使用 React.createElement 方法来创建 React 元素。这些元素是描述 UI 树的 JavaScript 对象。
  4. ReactElement 调用

    • React.createElement 方法返回一个 ReactElement 实例,它是 React 元素的内部表示。
    • ReactElement 包含了创建真实 DOM 节点所需的所有信息,如标签名、属性和子元素。
  5. 作为参数处理

    • ReactElement 实例会被作为参数传递给 React 的渲染系统。React 会使用这些元素来构建虚拟 DOM。
  6. “虚拟 DOM”

    • 虚拟 DOM 是一个轻量级的 JavaScript 对象,它表示真实 DOM 的状态。
    • React 使用虚拟 DOM 来提高性能,因为它允许 React 在内存中进行 UI 更新,而不是直接操作真实 DOM。
  7. ReactDoM.render()

    • ReactDOM.render() 方法是将虚拟 DOM 渲染到真实 DOM 的入口点。这个方法接收一个 ReactElement 和一个 DOM 节点的选择器或引用作为参数。
    • React 会使用 Diff 算法比较新旧虚拟 DOM 树的差异,并计算出最小的更新操作。
  8. 真实 DOM 传入

    • 根据计算出的更新操作,React 会更新传入的 DOM 节点。这个过程可能包括创建新的 DOM 节点、修改现有节点的属性或样式、删除不再需要的节点等。
    • 最终,更新后的真实 DOM 会反映在浏览器中,用户可以看到更新后的界面。

Sewen4/11/2024About 4 minReactReact组件渲染
Virtual DOM

virtual dom 是单独存在的一个库, github: https://github.com/Matt-Esch/virtual-dom

Real DOM

在认识 virtual dom 之前,先认识一下 Real DOM:

Real DOM,真实 DOM,意思为文档对象模型,是一个结构化文本的抽象,在页面渲染出的每一个结点都是一个真实 DOM 结构,如下:

Real DOM(真实DOM)是浏览器原生提供的,它表示当前页面的实际HTML结构。当页面中的数据发生变化时,Real DOM 会进行重新渲染和更新。它是我们与网页进行交互的基础,通过操作Real DOM,我们可以改变网页的内容、样式和行为。


Sewen4/10/2024About 20 minReactReact组件渲染
Vue Vs React

总览

Vue React
组件 单文件组件:使用template、script和style来分别定义组件的结构、逻辑和样式。 在React中,组件可以通过函数式组件或类组件来定义。
思想 数据的响应式:当数据发生变化时,Vue会自动更新相关的DOM React 的核心思想是每次对于变更 propsstate,触发新旧 Virtual DOM 进行 diff(协调算法),对比出变化的地方,然后通过 render 重新渲染界面。
状态或变量响应式渲染 Vue使用响应式系统来实现状态或变量的响应式渲染。
当数据发生变化时,Vue会自动更新DOM。
这是通过Vue的响应式原理实现的,即Vue在初始化时将数据转换为getter/setter形式的对象,当数据变化时,setter会触发组件的重新渲染。
React通过setState方法来更新组件的状态,并触发组件的重新渲染。
React使用一种名为“调和”(Reconciliation)的过程来比较新旧虚拟DOM树,并计算出最小的DOM更新。
当组件的状态或属性发生变化时,React会重新渲染该组件及其子组件。
数据流 单向数据流 单向数据流
组件通信(父子组件通信) 通过propsemit进行:父组件通过props向子组件传递数据或回调,子组件则通过emit触发事件来向父组件发送消息。 通过props和回调函数实现:父组件通过props将数据或函数传递给子组件,子组件则可以通过这些props来接收数据和触发回调函数
Prop逐级透传问题(跨组件通信) Vue通过provideinject机制解决Prop逐级透传问题。
父组件使用provide提供数据或方法,子组件通过inject接收。
React没有内置的Prop逐级透传机制,通常通过高阶组件或Context API来实现
高阶组件是一种函数,它接收一个组件并返回一个新组件,新组件可以访问传递的props。
Context API提供了一种在组件树中共享值的方式,无需显式地通过每一层组件。
子组件渲染 Vue插槽是一种组件间内容分发机制,允许父组件将内容传递给子组件的指定位置。
通过<slot>标签定义插槽的位置,父组件使用<template>标签包裹内容,并通过具名插槽或作用域插槽将内容传递给子组件。
React通过 JSX children 属性直接将组件标签内的内容作为prop传递给子组件。
组件标签内的内容会自动成为children prop,父组件可以将内容直接传递给子组件,并在子组件内部通过props.children或函数组件的props参数访问。
模板引用 Vue2中:模板中的元素或组件上使用ref属性,在Vue实例的方法或生命周期钩子中通过this.$refs来访问。
Vue3中:模板中使用ref函数来创建引用,在 setup 函数中通过 import { ref } from 'vue'; 创建 ref 模板同名 ref 变量引用模板。
useRef钩子来创建引用,通用 JSX 使用 ref = ref变量名 进行引用
传送门 Teleport:引入了 <Teleport> 组件,将子组件渲染到 DOM 树中不同于其父组件位置的能力 Portals:通过createPortal 允许你将 JSX 作为 children 渲染至 DOM 的不同部分
生命周期 Vue 2.x有beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedbeforeDestroydestroyed等生命周期钩子。
Vue 3.x引入了Composition API,提供了onMountedonUpdatedonUnmounted等函数式API来替代部分生命周期钩子。
React有constructorcomponentDidMountcomponentDidUpdatecomponentWillUnmount等生命周期方法。
在React 16.8及以后的版本中,引入了Hooks API(如useEffectuseState等),使得在没有类的情况下使用状态以及其他React特性成为可能。
逻辑复用 Vue3中采用组合式 API 逻辑组织能力 React 中采用 React Hooks
虚拟DOM 带编译时信息的虚拟 DOM 纯运行时虚拟 DOM
JSX 语法 通过 @vue/babel-plugin-jsx 插件支持,语法与React JSX 不同,参考以下比较 默认支持
函数式组件

Sewen3/23/2024About 29 minReactVue Vs React