# React
# 对react的理解
react是什么? 是JavaScript库,遵循组件设计模式,具有声明式编程范式和函数式编程理念, 使用虚拟DOM来操作真实DOM,遵循从高阶组件到低阶组件的单向数据流。 通过组件化思想,用来组合,嵌套,构成整体页面。
react的特点? JSX语法 单向数据流 虚拟DOM 声明式编程 Component
组件的特点? 可组合,可重用,可维护
react的优势? 声明式编程:简单易用 组件式开发:提高代码复用 单向数据流:比双向数据绑定更安全,速度更快
# state和props的区别?
state: 组件的内部状态,通过在constructor中初始化,并通过调用setState进行修改,从而触发render方法进行更新。
props: react的核心思想是组件化,将页面切分成一些独立的,可复用的组件,这些组件可以看作是函数接收一个参数,这个参数就是props,因此,可以理解为组件外部传入到组件内部的参数。
区别: 相同点: 都是JavaScript对象 都是用来保存数据的 都会触发渲染更新
不同点: state是组件内部的数据,可以在组件内部进行修改。 而props是组件外部传入的数据,因此只能在组件外部进行修改
# react的事件机制
react基于浏览器的事件机制自身实现了一套事件机制,包括事件注册,事件的合成,事件冒泡,事件派发等。
虽然onclick看似绑定到DOM元素上,但实际并不会把事件代理函数直接绑定到真实的节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件去监听。
这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或者卸载时,只是在这个统一的事件监听器上插入或删除一些对象。
当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。这样做的意义是简化了事件处理和回收机制,效率也有很大提升。
react事件机制和原生事件机制的执行顺序
本质区别
- 原生事件:由浏览器直接触发,遵循 DOM 事件流(捕获阶段 → 目标阶段 → 冒泡阶段)。
- React 合成事件:React 将原生事件封装为
SyntheticEvent对象,通过一个统一的事件代理(document或根节点)处理,而非直接绑定在 DOM 元素上。
执行顺序规律
当同一元素同时绑定了React 合成事件和原生事件时,执行顺序为:
- 原生事件的捕获阶段(若有)
- 原生事件的目标阶段(事件触发在具体元素上)
- React 合成事件(处于冒泡阶段,因为 React 默认使用冒泡)
- 原生事件的冒泡阶段(若有,且未被阻止)
# react项目中如何捕获错误?
错误边界
以下错误不能捕获:
- 事件处理
- 异步代码
- 服务端渲染
- 自身抛出来的错误
try...catch
# render的执行时机?
组件首次挂载:当组件第一次被渲染到 DOM 中时,
render会被执行,这是初始化渲染的必然过程。组件自身状态改变:
- 类组件:只要调用setState()就会触发更新。
- 函数组件:如果修改的值未发生改变,则不会触发更新。
父组件的重新渲染:当父组件的
render执行时,无论子组件是否接收props,默认情况下子组件都会重新渲染(除非做了优化)。props发生改变:React 会通过浅比较(
shallow comparison)判断props是否变化,从而触发render更新。强制更新:
类组件:可通过
this.forceUpdate()强制触发render,忽略state和props的变化检查。函数组件:没有内置的
forceUpdate,但可通过修改一个 “无效” 状态(如空对象)模拟。const [, forceUpdate] = useState({}); forceUpdate({}); // 引用变化,触发render
# 类组件和函数组件之间的区别(√)
语法定义:
- 类组件:基于 ES6 类(
class)语法,继承React.Component,必须实现render()方法返回 JSX。 - 函数组件:以 JavaScript 函数形式定义,直接返回 JSX(或
null)。
- 类组件:基于 ES6 类(
生命周期:
- 类组件:拥有完整的生命周期。
- 函数组件:无生命周期方法,通过
useEffectHook 统一处理副作用,一个useEffect可模拟多个生命周期的组合。
内部状态:
- 类组件:使用
this.state存储状态,通过this.setState()方法更新状态(触发重渲染)。 - 函数组件:16.8版本以前,函数组件只能接收props渲染到页面,称为无状态组件。16.8以后,需通过 React Hooks 中的
useState管理状态。
- 类组件:使用
this关键字:
- 类组件:有
- 函数组件:无
性能优化:
- 类组件:通过
shouldComponentUpdate方法或继承React.PureComponent(自动浅比较props和state)优化重渲染。 - 函数组件:通过
React.memo(缓存组件,浅比较props)、useMemo(缓存计算结果)、useCallback(缓存函数引用)优化性能,比类组件的优化方式更灵活。
- 类组件:通过
# 生命周期(√)
组件创建阶段
constructor(props)实例过程中自动调用的方法,在方法内部通过super关键字获取来自父组件的props,在该方法中,通常的操作是初始化state状态或者在this上挂载方法。
static getDerivedStateFromProps(props, state)实例化组件后以及重新渲染
作用: 该组件类似于componentWillReceiveProps, 用来控制props去更新state.
应用场景: 父组件中数据发生变化,要求子组件可以监听到数据发生变化,并且将数据重新渲染。
render()(常用)类组件中必须实现的方法,用于渲染DOM结构,可以访问组件state和props属性
注意:不要在render里面setState,会触发死循环导致内存崩溃
componentDidMount()(常用)组件挂载到真实DOM节点后执行,即在render方法之后执行
多用于一些数据获取,事件监听等操作
执行顺序:constructor -> getDerivedStateFromProps / componentWillMount(已废弃,重命名为 UNSAFE_componentWillMount) -> render -> componentDidMount
组件更新阶段
static getDerivedStateFromProps(props, state)shouldComponentUpdate(nextProps, nextState)render()GetSnapshotBeforeUpdate()componentDidUpdate()
组件卸载阶段
componentWillUnmount()用于组件卸载前,清理一些注册的监听事件,或者取消订阅的网络请求等。

# ShouldComponentUpdate(√)
由于diff工作量比较大,如果setState的值没有改变,其实是不需要进行diff的。如何让state没有改变的时候,不进行diff呢?可以使用shouldComponentUpdate这个生命周期钩子。
在调用setState更新数据后,React会判断是否需要进行更新操作,如果没有shouldComponentUpdate这个生命周期钩子,则默认进行对比和更新工作,如果有shouldComponentUpdate,则调用之,如果返回true则更新,返回false则不更新。利用这个钩子,我们可以对新的state和当前的state进行对比,如果有变化,返回true,如果没有变化,返回false。
React.PureComponent实现了浅比较的shouldComponentUpdate,因此我们的组件如果继承了 React.PureComponent就会有了对比state决定是否更新的特性。
但是使用React.PureComponent时候需要注意一个问题,浅比较是比较值是否相同,因此当state中的数据是一个对象,其中属性变化但引用不变、或者state中的数据是一个数组,数组中的元素变化但数组引用不变,这时候比较结果是两者相同,因此不会触发更新。如果希望在对象属性变化、数组元素变化时候触发更新,应该setState时候传入新的对象或数组。
# 组件通信(√)
父组件向子组件通讯:
父组件可以向子组件通过传 props 的方式,向子组件进行通讯
子组件向父组件通讯:
父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值
useImperativeHandle
ref
兄弟组件通信:
找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信
跨层级通信:
Context设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过Context通信再适合不过发布订阅模式:
发布者发布事件,订阅者监听事件并做出反应,我们可以通过引入event模块进行通信
全局状态管理工具:
借助Redux或者Mobx等全局状态管理工具进行通信,这种工具会维护一个全局状态中心Store,并根据不同的事件产生新的状态
# 虚拟DOM(√)
虚拟DOM是真实DOM在内存中的表示,ul的表示形式保存在内存中,并且与实际的DOM同步,这是一个发生在渲染函数被调用和元素在屏幕上显示的步骤,整个过程被称为调和
<div className='Index'>
<div>我是小杜杜</div>
<ul>
<li>React</li>
<li>Vue</li>
</ul>
</div>
{
type: 'div',
props: { class: 'Index' },
children: [
{
type: 'div',
children: '我是小杜杜'
},
{
type: 'ul',
children: [
{
type: 'li',
children: 'React'
},
{
type: 'li',
children: 'Vue'
},
]
}
]
}
「React深入」一文吃透虚拟DOM和diff算法 - 掘金 (juejin.cn) (opens new window)
# setState到底是异步还是同步(×加粗字段)
先给出答案: 有时表现出异步,有时表现出同步
setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout中都是同步的。setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数setState(partialState, callback)中的callback拿到更新后的结果。setState的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。
# 高阶组件
高阶组件(HOC)是接受一个组件并返回一个新组件的函数。基本上,这是一个模式,是从 React 的组合特性中衍生出来的,称其为纯组件,因为它们可以接受任何动态提供的子组件,但不会修改或复制输入组件中的任何行为
HOC 可以用于以下许多用例
代码重用、逻辑和引导抽象 渲染劫持 state 抽象和操作 props 处理
# 在构造函数调用super并将props作为参数传入的作用是啥?
在调用 super() 方法之前,子类构造函数无法使用this引用,ES6 子类也是如此。将 props 参数传递给 super() 调用的主要原因是在子构造函数中能够通过this.props来获取传入的 props。
props 的行为只有在构造函数中是不同的,在构造函数之外也是一样的
# 受控组件
定义:表单元素的值由 React 组件的 state 控制,用户输入会触发状态更新,进而同步到表单元素上。
import { useState } from 'react';
function ControlledInput() {
// 用 state 存储输入框的值
const [value, setValue] = useState('');
// 处理输入变化:更新 state
const handleChange = (e) => {
setValue(e.target.value); // 实时同步输入到 state
};
return (
<div>
<input
type="text"
value={value} // 表单值由 state 控制
onChange={handleChange} // 输入变化时更新 state
/>
<p>当前输入:{value}</p> {/* 实时显示 state 中的值 */}
</div>
);
}
# 非受控组件
定义:表单元素的值由 DOM 自身控制,React 不直接管理其状态,而是通过 ref 访问 DOM 元素来获取或设置值。
import { useRef } from 'react';
function UncontrolledInput() {
// 创建 ref 关联到输入框 DOM 元素
const inputRef = useRef(null);
// 提交时通过 ref 获取输入值
const handleSubmit = () => {
const value = inputRef.current.value;
alert(`提交的值:${value}`);
};
return (
<div>
<input
type="text"
ref={inputRef} // 关联 ref
defaultValue="初始值" // 初始值(仅第一次渲染生效)
/>
<button onClick={handleSubmit}>提交</button>
</div>
);
}
# React 中的StrictMode(严格模式)是什么?(×)
React 的StrictMode是一种辅助组件,可以帮助咱们编写更好的 react 组件,可以使用
验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告。 验证是否使用的已经废弃的方法,如果有,会在控制台给出警告。 通过识别潜在的风险预防一些副作用。
# 什么是prop drilling,如何避免?(×)
在构建 React 应用程序时,在多层嵌套组件来使用另一个嵌套组件提供的数据。最简单的方法是将一个 prop 从每个组件一层层的传递下去,从源组件传递到深层嵌套组件,这叫做prop drilling。
prop drilling的主要缺点是原本不需要数据的组件变得不必要地复杂,并且难以维护。
为了避免prop drilling,一种常用的方法是使用React Context。通过定义提供数据的Provider组件,并允许嵌套的组件通过Consumer组件或useContext Hook 使用上下文数据
# React Context(√)
Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性
# 如何避免组件的重新渲染(×)
React 中最常见的问题之一是组件不必要地重新渲染。React 提供了两个方法,在这些情况下非常有用:
React.memo():这可以防止不必要地重新渲染函数组件
PureComponent:这可以防止不必要地重新渲染类组件 这两种方法都依赖于对传递给组件的props的浅比较,如果 props 没有改变,那么组件将不会重新渲染。虽然这两种工具都非常有用,但是浅比较会带来额外的性能损失,因此如果使用不当,这两种方法都会对性能产生负面影响。
通过使用 React Profiler,可以在使用这些方法前后对性能进行测量,从而确保通过进行给定的更改来实际改进性能。
# useMemo是怎么做性能优化的(√)
当父组件向子组件组件通信的时候,父组件中数据发生改变,更新父组件导致子组件的更新渲染,但是如果修改的数据跟子组件无关的话,更新子组件会导致子组件不必要的DOM渲染,是比较消耗性能的,这个时候我们可以使用useMemo或者memo做组件的缓存,减少子组件不必要的DOM渲染
# useCallback是怎么做性能优化的(√)
useCallback 的核心作用是缓存函数引用,避免函数在组件每次渲染时被重新创建,从而优化依赖该函数的子组件性能(尤其配合 React.memo 使用时)。
# 性能优化(√)
运行时:
避免不必要的渲染(shouldComponentUpdate,PureComponent)
注:函数组件采用
React.memoFragment(<></>)
不要在render中setState。
注:如果在render方法进行setState,可能导致循环地进行diff工作。
优化条件渲染
缓存计算属性
长列表:虚拟列表优化
concurrent mode注:启用concurrent mode之后,React会采取可中断渲染,让大规模的diff计算不会影响到界面的渲染,保证渲染和交互的流畅性。
使用Suspense组件可以在加载局部组件时候有更好的切换加载体验。
列表中使用key
fiber:diff可终端渲染
加载时:
- 懒加载(lazy)使用lazy实现按需加载组件和按需加载路由。
- 服务端渲染,使用服务端渲染提升首屏渲染性能。
- 缓存cdn cdn缓存提升React资源的加载速度。
- 使用 prerender-spa-plugin 渲染首屏,基本原理是启动一个服务,用pupetter离屏渲染。这个原理和服务端渲染类似,但对代码改造更小,不过要求服务器有node环境。
# 函数副作用(×)
函数的副作用就是函数除了返回值外对外界环境造成的其它影响。
举个例子,假如我们每次执行一个函数,该函数都会操作全局的一个变量,那么对全局变量的操作就是这个函数的副作用。
而在React的世界里,我们的副作用大体可以分为两类,一类是调用浏览器的API,例如使用addEventListener来添加事件监听函数等,另外一类是发起获取服务器数据的请求,例如当用户组件挂载的时候去异步获取用户的信息等。
例如修改DOM、发送网络请求、访问本地存储等。
# 如何避免在React重新绑定实例?
1.将事件处理程序定义为内联箭头函数
class SubmitButton extends React.Component {
constructor(props) {
super(props);
this.state = {
isFormSubmitted: false
};
}
render() {
return (
<button onClick={() => {
this.setState({ isFormSubmitted: true });
}}>Submit</button>
)
}
}
2.使用箭头函数来定义方法:
class SubmitButton extends React.Component {
state = {
isFormSubmitted: false
}
handleSubmit = () => {
this.setState({
isFormSubmitted: true
});
}
render() {
return (
<button onClick={this.handleSubmit}>Submit</button>
)
}
}
3.使用带有 Hooks 的函数组件
const SubmitButton = () => {
const [isFormSubmitted, setIsFormSubmitted] = useState(false);
return (
<button onClick={() => {
setIsFormSubmitted(true);
}}>Submit</button>
)
};
# React有哪些限制?
- React 只是一个库,而不是一个完整的框架
- 它的库非常庞大,需要时间来理解
- 新手程序员可能很难理解
- 编码变得复杂,因为它使用内联模板和 JSX
# 为什么浏览器无法读取JSX?
浏览器只能处理 JavaScript 对象,而不能读取常规 JavaScript 对象中的 JSX。所以为了使浏览器能够读取 JSX,首先,需要用像 Babel 这样的 JSX 转换器将 JSX 文件转换为 JavaScript 对象,然后再将其传给浏览器
# 你怎样理解“在React中,一切都是组件”这句话
组件是 React 应用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的部分。每个组件彼此独立,而不会影响 UI 的其余部分。
# 什么是 Props?
Props 是 React 中属性的简写。它们是只读组件,必须保持纯,即不可变。它们总是在整个应用中从父组件传递到子组件。子组件永远不能将 prop 送回父组件。这有助于维护单向数据流,通常用于呈现动态生成的数据
# React 中 key 的重要性
key 用于识别唯一的 Virtual DOM 元素及其驱动 UI 的相应数据。它们通过回收 DOM 中当前所有的元素来帮助 React 优化渲染。这些 key 必须是唯一的数字或字符串,React 只是重新排序元素而不是重新渲染它们。这可以提高应用程序的性能
# key
不加key或者key使用index赋值,都会导致列表变动后React无法辨别item前后的对应关系。
这可能带来两种问题:
① 性能损耗,因为无法正确识别列表中元素变化,可能做一些多余的更新属性的操作,造成性能损失。
② 非受控的表单的值不符合预期,因为无法正确识别列表中元素变化,对于非受控的表单,React无法知道表单的变化,也就没法正确地更新其值。
# Redux遵循的三个原则
单一数据源:整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store 中:Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护;单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改;
状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。
使用纯函数进行更改:通过reducer将旧state和actions联系在一起,并且返回一个新的State:
随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分;
但是所有的reducer都应该是纯函数,不能产生任何的副作用;
# 你对“单一事实来源”有什么理解?
Redux 使用 “Store” 将程序的整个状态存储在同一个地方。因此所有组件的状态都存储在 Store 中,并且它们从 Store 本身接收更新。单一状态树可以更容易地跟踪随时间的变化,并调试或检查程序
# 列出 Redux 的组件
Action – 这是一个用来描述发生了什么事情的对象。 Reducer – 这是一个确定状态将如何变化的地方。 Store – 整个程序的状态/对象树保存在Store中。 View – 只显示 Store 提供的数据。
# 如何在 Redux 中定义 Action
React 中的 Action 必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,并且还可以向其添加更多的属性。在 Redux 中,action 被名为 Action Creators 的函数所创建
# 解释 Reducer 的作用
Reducers 是纯函数,它规定应用程序的状态怎样因响应 ACTION 而改变。Reducers 通过接受先前的状态和 action 来工作,然后它返回一个新的状态。它根据操作的类型确定需要执行哪种更新,然后返回新的值。如果不需要完成任务,它会返回原来的状态
# Store 在 Redux 中的意义是什么
Store 是一个 JavaScript 对象,它可以保存程序的状态,并提供一些方法来访问状态、调度操作和注册侦听器。应用程序的整个状态/对象树保存在单一存储中。因此,Redux 非常简单且是可预测的。我们可以将中间件传递到 store 来处理数据,并记录改变存储状态的各种操作。所有操作都通过 reducer 返回一个新状态。
# redux api
applyMiddleware方法,应用中间件
createStore方法,创建store
store.dispatch方法,触发action
store.getState,获取当前状态
store.subscribe,监听状态改变
# 常用的hooks
hook是react提供的一类API,它给函数式组件增加状态和逻辑,并将功能代码聚合在一起。
useState:定义state的数据,参数是初始化的数据,返回值两个值1. 初始化值,2. 修改的方法
useEffect:副作用函数,顾名思义,副作用即只有使用过后才会产生副作用
useMemo:用来计算数据,返回一个结果,监听数据的变化,第二个参数就是监听的数据,具有缓存性
- useMemo和useEffect 相比较来说,useMemo 是组件更新的时候触发生命周期
useCallback:缓存组件(函数)
useRef:相当于createRef的使用,创建组件的属性信息
useContext:跨组件共享数据
useReducer:useReducer是用来弥补useState的不足, 可以把数据进行集中式的管理,单独处理数据的逻辑信息
useImperativeHandle
好处:是能够让不同功能代码更聚合,更好维护。
限制:
只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中)
# 使用 React Hooks 好处(×)
1.Hooks使得函数组件成为编写React组件的首选方式,而不再需要使用类组件。函数式组件具有更简洁、易读和易维护的特点,减少了模板代码和类组件的样板代码。
2.代码复用和组合:Hooks使得代码复用和组合更加容易。通过自定义Hooks,可以将逻辑抽象为可重用的函数,并在多个组件之间共享。
3.状态管理:Hooks提供了useState和useReducer等状态管理Hooks,使得在函数组件中管理组件的状态变得更加简单和直观。它们遵循函数式编程的原则,让状态变化更可预测且易于追踪。
4.副作用管理:Hooks中的useEffect和useLayoutEffect等Hooks使得管理副作用操作(如数据获取、订阅、DOM操作等)更加便捷和一致。通过指定依赖数组,可以控制副作用的触发时机,以避免不必要的执行。
5.更好的性能:由于Hooks的设计,可以更好地进行性能优化。Hooks的灵活性和可组合性使得组件的渲染过程更可控,可以精确地决定何时重新渲染以及何时进行副作用操作,从而提高了React应用的性能。React Hooks引入了useMemo和useCallback等Hook,可以帮助优化性能,避免不必要的重渲染。
6.更好的测试性:由于函数式组件的特点,使用Hooks编写的组件更容易进行单元测试。因为它们是纯函数,只关注输入和输出,无需担心类组件中的生命周期方法和实例状态的测试问题。
hooks解决了什么问题?
函数组件中可以使用类组件中的特性问题
# useEffect 的参数(√)
useEffect 的参数,主要分为两个,第一个固定是一个回调函数,第二个是一个数组(可选)。
参数传递的不同,useEffect 实现的效果也不同。
没有第二个参数
当 useEffect 只有第一个参数时,其作用就是在模拟 componentDidMount 和 componentDidUpdate 生命周期函数。
也就是说,useEffect 的第一个回调函数,会在组件首次挂载完成执行一次,同时,后续组件每次更新完成时也会执行。
useEffect(() => {
// ...
})
第二个参数是空数组
当 useEffect 的第二个参数是一个空数组时,其作用就是在模拟 componentDidMount 和componentWillUnmount 生命周期函数。
也就是说,useEffect 的第一个回调函数,会在组件首次挂载完成执行一次。
useEffect(() => {
// ...
}, [])
第二个参数是非空数组
当 useEffect 的第二个参数是一个非空数组时,其作用就是在模拟 componentDidMount 生命周期函数,同时还可以模拟 Vue 中 watch 的作用。
也就是说,useEffect 的第一个回调函数,会在组件首次挂载完成执行一次。后续,只要当第二个参数的数组中,任意一条数据发生改变,useEffect 的第一个回调函数又会再次执行。
useEffect(() => {
// ...
}, [数据1, 数据2, ...])
第一个参数中返回函数
当 useEffect 的第一个参数中,返回了一个函数。那么,返回的这个函数就是在模拟 componentWillUnmount 生命周期函数。
useEffect(() => {
return () => {
}
})
# react fiber
fiber架构是为了支持react进行可中断渲染,降低卡顿,提升流畅度。
react16之前的版本,diff虚拟dom时候是一口气完成的。这可能造成卡顿,因为人眼可识别的帧率是1s 60帧,即16ms一帧,如果diff时间超过16ms,阻塞渲染,就会感觉卡顿。
为了避免这种情况,需要让diff操作不超过16ms,如果超过16ms,就先暂停,让给浏览器进行渲染操作,后续渲染间隙再继续diff。
fiber架构就是为了支持这种“可中断渲染”而涉及的。fiber tree是一种数据结构,它把虚拟dom tree连接成一个链表,从而可以让遍历操作可以支持断点重启。
# diff算法
React的diff算法基于两个假设:
1、不同元素的类型会产生不同的树
2、开发者可以通过key prop标识一个元素在不同的渲染下可以保持稳定(及相同key的元素不需要更新整个元素,不同的key需要更新整个元素)
基于上述假设,React认为在视图更新前后,如果两个节点的Tag名(对于原生标签)或者组件(对于自定义组件)相同,则它们是同一个节点,而且如果是不同的根节点,那么子节点也不需要对比,直接用新树替换掉旧的树即可。
对比的过程:
1、当一个组件触发更新(setState),则React会diff以这个组件为根节点的整个虚拟DOM树,对比更新后的虚拟DOM和更新前的虚拟DOM。
2、如果两个节点不同,则用新的节点替换掉旧的节点;如果两个节点相同(假设为oldNode和newNode),则对比它们的属性、innerText和子节点。
3、如果oldNode有某个子节点someOldChild,而newNode没有这个子节点(即newNode没有一个子节点,和someOldNode有相同的tag名、自定义组件引用或者key值),则删掉someOldChild;如果newNode有某个子节点somNewChild,而oldNode没有,则添加someNewChild;如果oldNode和newNode都有某个节点someChild,则将其移动到正确的位置,并递归地进行对比工作,即以someChild为根节点对比新旧两棵虚拟DOM树。
上一篇: 下一篇: