useLayoutEffect
useLayoutEffect
用于在浏览器重新绘制屏幕之前同步执行代码。它与 useEffect
相同,但执行时机不同。
主要特点
- 执行时机:
useLayoutEffect
在 DOM 更新完成后同步执行,但在浏览器绘制之前。这使得它可以在浏览器渲染之前读取和修改 DOM,避免视觉上的闪烁或不一致。 - 适用场景:主要用于需要同步调整布局的副作用操作,例如测量 DOM 元素的尺寸(如高度、宽度、滚动位置等)并根据这些值进行渲染。
- 性能影响:由于
useLayoutEffect
阻塞了浏览器的绘制,如果执行复杂或耗时的操作,可能会导致性能问题或视觉卡顿。
与 useEffect
的区别
- 执行时间:
useEffect
:在浏览器绘制之后异步执行。useLayoutEffect
:在浏览器绘制之前同步执行。
注意事项
useLayoutEffect
在服务器端渲染(SSR)中不会执行,因此在 SSR 场景下需要谨慎使用。- 尽量优先使用
useEffect
,因为useLayoutEffect
可能会影响性能。
总之,useLayoutEffect
是一个强大的工具,但应仅在需要同步处理 DOM 布局时使用。
useEffect
useEffect
可以用来实现类似类组件生命周期方法的功能。通过合理配置 ,可以模拟类组件中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
等生命周期方法。
总结
但需要注意的是,useEffect
的执行时机是异步的,如果需要在浏览器绘制之前同步操作 DOM,可以使用 useLayoutEffect
。
useCallback和useMemo的区别
1. useMemo
useMemo
用于缓存计算结果,避免在组件重新渲染时重复执行复杂的计算逻辑。
特点
- 用途:缓存计算结果。
2. useCallback
useCallback
用于缓存函数,避免在组件重新渲染时创建新的函数引用。
特点
- 用途:缓存函数引用。
- 执行时机:每次组件渲染时都会执行,但如果依赖项没有变化,则返回缓存的函数引用。
使用场景
- 当需要将函数作为依赖项传递给子组件时,为了避免子组件因函数引用变化而重新渲染。
示例
import React, { useState, useCallback } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
// 缓存函数引用
const handleClick = useCallback(() => {
console.log("点击按钮,当前计数:", count);
}, [count]); // 只有 count 变化时重新创建函数
return (
<div>
<p>计数: {count}</p>
<button onClick={handleClick}>点击</button>
</div>
);
}
在这个例子中,handleClick
函数被缓存,只有当 count
发生变化时才会重新创建一个新的函数引用。
3. 使用建议
useMemo
:- 仅在计算逻辑复杂且对性能有明显影响时使用。
- 如果计算逻辑简单,直接在渲染逻辑中执行即可,避免过度优化。
useCallback
:- 适用于需要将函数传递给子组件的场景。
- 如果子组件使用了
React.memo
或shouldComponentUpdate
,useCallback
可以避免因函数引用变化导致的子组件不必要的重新渲染。
总结
useMemo
和 useCallback
都是用于优化性能的 Hook,但它们的用途不同:
useMemo
用于缓存计算结果,避免重复计算。useCallback
用于缓存函数引用,避免因函数引用变化导致的子组件重新渲染。
Fiber架构出现之前 react 存在的问题
在 React Fiber 架构出现之前,React 主要存在以下问题:
-
同步渲染导致的卡顿
React 15 及之前的版本采用同步的 Stack 架构进行渲染。当组件状态更新时,React 会立即重新构建整个虚拟 DOM 树,并与之前的虚拟 DOM 树进行对比(diff),然后一次性将所有差异应用到真实 DOM 上。如果组件树较大或更新频繁,这个过程会阻塞主线程,导致用户交互和动画任务无法及时响应,页面出现卡顿。 -
递归调用无法中断
旧版本的渲染过程是基于递归实现的,一旦开始渲染,整个过程无法中断,必须等到所有任务完成。这使得浏览器无法在渲染过程中处理其他高优先级任务,例如用户的点击或输入事件。 -
缺乏任务优先级调度
在 React 15 及之前的版本中,所有更新任务的优先级是相同的,无法根据任务的紧急程度(如用户交互、动画等)进行动态调度。这导致即使某些任务对用户体验至关重要,也会被同步渲染过程阻塞。 -
错误处理能力有限
在旧版本中,如果渲染过程中发生错误,整个应用可能会崩溃,因为错误无法被有效隔离。
为了解决这些问题,React 16 引入了 Fiber 架构,其核心改进包括:
- 可中断的 异步 渲染:Fiber 将渲染任务分解为多个小任务单元,允许在浏览器空闲时暂停和恢复渲染。
- 优先级调度:引入了任务优先级机制,高优先级任务(如用户交互)可以优先执行。
- 增量渲染:支持分批次将更新应用到真实 DOM,减少重排和重绘的开销。
- 错误边界:引入了错误边界机制,允许捕获组件树中的错误并显示备用 UI,而不会导致整个应用崩溃。
双缓冲技术
React Fiber使用了类似于图形渲染中的双缓冲技术。这意味着在构建新的UI树时,React会同时在内存中维护两棵树:当前屏幕上显示的树(current tree)和正在构建的树(work-in-progress tree)。只有当新的树完全构建完成后,它才会被一次性地渲染到屏幕上,从而实现更加流畅的用户体验。
虚拟DOM是什么
虚拟 DOM 的工作原理
-
构建虚拟 DOM
当组件首次渲染时,框架会根据组件的 JSX 或模板生成一个虚拟 DOM 树。这个虚拟 DOM 树是一个 JavaScript 对象,它包含了组件的结构、属性和内容。const virtualDOM = <div className="container"> <h1>Hello, World!</h1> <p>This is a paragraph.</p> </div>;
在内存中,虚拟 DOM 可能是一个类似这样的对象:
{ type: "div", props: { className: "container" }, children: [ { type: "h1", props: {}, children: ["Hello, World!"] }, { type: "p", props: {}, children: ["This is a paragraph."] } ] }
虚拟 DOM的数量
在 React 应用中,虚拟 DOM 的数量并不是固定的:
- 虚拟 DOM 的数量
当状态发生变化时,React 会重新生成一个新的虚拟 DOM 树,并通过 Diff 算法比较新旧虚拟 DOM 树的差异。因此,虚拟 DOM 的数量取决于组件的状态变化频率和组件树的大小。
Hook 必须在函数组件或另一个 Hook 中调用吗
这是 React 的设计规则,也是 Hooks 能够正常工作的前提条件。
总结
React Hooks 必须在函数组件或自定义 Hook 中调用,这是为了确保 Hooks 的调用顺序一致,并且能够正确管理状态和副作用。违反这些规则会导致错误或不可预测的行为。
受控组件和非受控组件
在 React 中,表单元素(如 <input>
、<textarea>
、<select>
等)可以通过两种方式管理:受控组件(Controlled Components) 和 非受控组件(Uncontrolled Components)。
1. 受控组件(Controlled Components)
受控组件是指表单元素的值由 React 的状态(state
)控制的组件。在这种模式下,表单元素的值通过 value
属性绑定到 React 的状态,并通过事件处理器(如 onChange
)更新状态。
特点:
- 状态同步:表单元素的值始终与 React 的状态保持一致。
- 单向数据流:数据从 React 状态流向表单元素,用户输入会触发事件处理器来更新状态。
- 易于管理:适合需要严格控制表单数据的场景,如表单校验、动态更新等。
2. 非受控组件(Uncontrolled Components)
非受控组件是指表单元素的值不由 React 的状态控制,而是直接由 DOM 元素管理的组件。在这种模式下,表单元素的值通过 ref
获取,而不是通过 value
属性绑定到 React 的状态。
特点:
- 性能优势:在某些场景下,非受控组件的性能可能更好,因为不需要频繁更新 React 状态。
- 适合简单场景:适合不需要严格控制表单数据的场景,如简单的输入框。
使用场景:
-
受控组件:
- 表单校验:需要实时校验用户输入。
- 动态更新:需要根据用户输入动态更新 UI 或状态。
- 复杂表单:需要严格控制表单数据的场景。
- 默认值动态变化:表单的初始值可能来自外部状态或 API。
-
非受控组件:
- 简单表单:表单逻辑简单,不需要实时校验。
- 性能优化:在某些场景下,非受控组件的性能更好。
- 外部库集成:某些第三方库可能更适合非受控模式。
注意事项:
-
受控组件的陷阱:
- 如果忘记绑定
value
或onChange
,表单元素将无法正常工作。 - 受控组件需要频繁更新状态,可能会导致性能问题(尤其是在大型表单中)。
- 如果忘记绑定
-
非受控组件的限制:
- 非受控组件的值只能通过
ref
获取,无法通过 React 的状态直接访问。 - 非受控组件的初始值只能通过
defaultValue
或defaultChecked
设置。
- 非受控组件的值只能通过