React18有哪些更新
- 并发模式
- 更新render API
- 自动批处理
- suspense支持ssr
- startTransition
- useTransition
- useId
- 提供给第三方库的hook
jsx是什么,它和js的区别
jsx是js语法的扩展,它允许编写类似于HTML的代码。它可以编译为常规的js函数带哦用,从而为创建组件标记提供了一中更好的方法。
简述React的生命周期
React的生命周期主要分为三个阶段:挂载(Mounting) 、更新(Updating) 、卸载(Unmounting) 。
React事件机制和原生DOM事件流有什么区别
- 绑定方式
- 原生DOM事件:在原生DOM中,事件是直接绑定到每个DOM元素上的。每当你在DOM元素上使用addEventListener方法时,事件会被绑定到该元素上。
- React事件机制:react使用事件委托的方式处理事件。所有的事件处理函数都绑定到document或根元素(如root)上,而不是每个DOM元素。React会在一个通用的事件处理器上捕获所有事件,并根据事件目标(target)来判断并执行相应的回调。并且react在事件传播时会利用‘合成事件’机制来确保不同浏览器下的事件表现一致。
- 事件委托VS直接绑定
- 原生DOM事件:
- 原生DOM事件需要为每个DOM元素单独绑定事件。这可能会导致性能问题,特别是在页面包含大量元素时。
- 事件捕获:事件在触发时会一次从目标元素向上冒泡到父元素,直到根元素。也可以选择使用事件捕获(捕获阶段),即事件从根元素到目标元素的过程。
- react事件机制:
- react利用了事件委托机制,所有事件处理器都被委托到顶层的document或root节点,这样就避免了为每个DOM元素单独绑定事件的开销。
- react的事件处理是在合成事件系统中完成的,它模拟了DOM事件流(捕获、目标、冒泡),但在内部进行优化,避免了直接操作DOM。
- 事件委托使react具有更好的性能,因为它避免了每个组件单独绑定事件。
- 原生DOM事件:
- 事件对象
- 原生DOM事件:
- 原生DOM事件对象直接访问浏览器提供的事件对象,包含关于事件的详细信息,如event.target,event.currentTarge等
- react事件机制:
- react使用合成事件。这是一个跨浏览器的封装对象,模拟了原生DOM事件对象的行为。合成事件提供了于原生DOM事件相同的API,但具有额外的性能优化。它会自动池化,并在事件回调后回收。
- 池化意味着合成事件在事件处理后被充值,不再可以访问其属性,为了避免这个问题。react提供了event.persist()方法,可以手动保留事件对象。
- 原生DOM事件:
- 事件流
-
原生DOM事件:
-
原生DOM事件遵循事件流的三个阶段:捕获阶段(Capture) 、目标阶段(Target) 和 冒泡阶段(Bubble) 。
- 捕获阶段:事件从根元素向下传递到目标元素。
- 目标阶段:事件到达目标元素并触发。
- 冒泡阶段:事件从目标元素向上传递回根元素。
-
-
react事件机制:
-
React 的事件系统模拟了原生 DOM 的事件流,但它默认使用冒泡阶段来处理事件(捕获阶段也可以通过
onClickCapture
来触发)。React 会在事件传播到目标元素时触发事件处理程序。 -
你无法在 React 中通过参数直接设置事件是捕获还是冒泡,但你可以使用
onClickCapture
来模拟捕获阶段的事件处理:
-
-
- 性能优化
- 原生 DOM 事件:
- 每个元素的事件绑定会产生一定的性能开销,尤其是在有很多元素时,事件监听会被附加到每个元素上。
- React 事件机制:
- 由于事件委托机制,React 将事件绑定到根元素上,这大大减少了内存的消耗和性能开销。React 的事件系统通过事件池化和事件委托来优化性能。
- 原生 DOM 事件:
- 事件类型支持
- 原生 DOM 事件
- 原生 DOM 事件种类繁多,通常根据浏览器的实现而有所不同。
- React 事件机制
- React 使用统一的合成事件系统,这使得 React 在不同浏览器中具有一致的行为,避免了浏览器兼容性问题。
- 原生 DOM 事件
Redux工作原理
Redux的工作原理主要围绕单一数据源(Single Source of Truth)、状态是只读的(State is Read-only)、纯函数修改状态(Changes are made with pure functions) 这三大原则来展开。 深度学习
React-Router工作原理?react-router-dom有哪些组件
React-Router工作原理: React-Router是React官方提供的前端路由解决方案,它的核心原理是:通过监听URL地址变化,动态渲染对应的组件,从而实现页面的跳转和切换。
React-Router-Dom常用组件 :
- BrowserRouter(必须包裹根组件):基于history模式。
- HashRouter(基于 Hash 模式):使用#实现路由跳转
- Route (定义路由):Route 负责匹配 URL并渲染对应组件。
- Routes (包裹所有路由):React 18 强制要求
Route
必须包裹在Routes
内部。 - Link (跳转页面):Link 用于代替
<a>
标签。 - useNavigate (编程式跳转):useNavigate() 替代
useHistory()
。 - useParams (获取动态参数):获取 URL 参数。
- Navigate (路由重定向): 类似
window.location.replace
。
React hooks解决了什么问题,函数组件与类组件的区别
✅ React Hooks 解决了什么问题?
在 React 16.8 之前,函数组件是无状态的,无法使用 state
或 生命周期
,所有有状态逻辑都必须写在类组件里。这导致了以下问题:
-
逻辑复用困难 🧐
- 组件逻辑(如订阅数据、获取 API、表单管理)很难复用。
- 只能通过 HOC(高阶组件) 或 Render Props,但这些方式增加了代码嵌套,使代码难以维护。
-
类组件复杂性高 🏗️
- 需要
this
绑定,容易出错。 - 生命周期函数分散,不同逻辑写在不同生命周期里,不易维护。
componentDidMount
、componentDidUpdate
、componentWillUnmount
可能重复执行相似逻辑。
- 需要
-
函数组件功能受限 😞
- 不能使用
state
和生命周期
,功能有限。
- 不能使用
React Hooks 诞生后,彻底解决了这些问题!🎉
✅ 函数组件 vs. 类组件的区别
对比点 | 类组件 (Class Component) | 函数组件 (Function Component + Hooks) |
---|---|---|
语法结构 | 需要继承 React.Component ,使用 this | 直接用 function 定义,无需 this |
状态管理 | this.state ,用 setState 更新 | useState() 更新状态 |
生命周期 | componentDidMount ,componentDidUpdate ,componentWillUnmount | useEffect() 统一处理 |
代码复杂度 | 逻辑分散,代码冗长 | 逻辑集中,代码更简洁 |
逻辑复用 | 依赖 HOC、Render Props | useEffect() 让逻辑更清晰,自定义 Hook 实现复用 |
性能 | 需要实例化,内存占用较大 | 轻量级,性能更好 |
事件处理 | this.handleClick = this.handleClick.bind(this) | onClick={() => setCount(count + 1)} |
可读性 | 代码嵌套多,不易读 | 代码扁平化,更直观 |
setState是同步的还是异步的,setState做了什么
React 16:
在 React 16 之前,setState
的异步行为并不是完全一致的。它在 事件处理程序 和 生命周期方法 中是异步的,但在某些情况下(如原生事件回调、setTimeout
等)会同步更新。
React 16+:
React 16 引入了 异步更新机制,并确保 setState
始终是异步的,无论它发生在什么情况下。React 会将多个状态更新合并成一个批量更新,然后重新渲染组件。这种优化确保了 React 的性能,尤其是在处理多个更新时。
-
在 React 16+ 中,
setState
在任何地方(事件回调、生命周期方法、setTimeout
等)都是异步的。 -
通过这种机制,React 会延迟对
setState
的处理,直到事件循环的最后,确保在一次渲染过程中更新状态。
为什么 setState
是异步的?
React 出于性能优化,会 合并多个 setState
调用,并 批量更新,这样可以减少不必要的渲染。
什么时候 setState
是同步的?
在 原生事件、setTimeout、Promise 等环境中,React 不会进行批量更新,setState
是同步的。
import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
setTimeout(() => {
setCount(count + 1);
console.log(count); // 这里是 **新值**(同步更新)
}, 1000);
};
return <button onClick={handleClick}>增加 {count}</button>;
}
export default App;
setTimeout
触发的setCount(count + 1)
不在 React 事件处理函数内部- React 不会 进行批量更新,所以
console.log(count)
立即得到更新后的值
setState
究竟做了什么?
当你调用 setState(newState)
时,React 不会立即更新 state
,而是会执行以下流程:
1. setState
触发更新
js
复制编辑
this.setState({ count: this.state.count + 1 });
- React 发现
setState
被调用,创建一个新的state
变更对象 - 不会立即更新
this.state
2. 触发调度(Scheduling)
- React 不会 直接更新
state
,而是将所有setState
调用放入一个队列 - 通过 批量更新(Batch Update) 机制,合并
setState
3. 重新渲染(Reconciliation)
- React 在合适的时机(如下一次事件循环) ,合并所有
setState
并执行重新渲染 - 在更新前,React 会计算新的
state
- 渲染 UI
✅ 如何在 setState
后获取最新的 state
?
- 使用
useEffect
jsx
复制编辑
useEffect(() => {
console.log(count); // 这里可以获取最新 state
}, [count]);
- 使用
setState
的回调函数
jsx
复制编辑
this.setState((prevState) => {
console.log(prevState.count); // 这里能拿到最新的 state
return { count: prevState.count + 1 };
});
- 使用
flushSync
(React 18 新增)
jsx
复制编辑
import { flushSync } from 'react-dom';
const handleClick = () => {
flushSync(() => {
setCount(count + 1);
});
console.log(count); // 立即拿到更新值
};
💡 记住
- React 会合并
setState
,在下一次渲染前更新state
- 原生事件、setTimeout、Promise 中,
setState
是同步的 - 如果想立即获取最新
state
,可以用useEffect
或setState
回调
什么是fiber、fiber解决了什么问题
fiber是React16引入的新的协调(Reconciliation)算法,它彻底重构了React的渲染机制,使其更高效、可控,支持终端、优先级调度,极大的优化了大规模应用的渲染性能。 在React16之前,整个更新过程是递归的,不可中断的,导致:
- 界面卡顿(更新事件过长时,主线程被阻塞)
- 无法优先处理重要任务
核心
-
每个Virtual DOM编程一个Fiber节点,让更新过程分布执行&可控
-
渲染过程拆分为两个阶段
- 任务拆分阶段(Reconciliation)->可中断,React先遍历Fiber树,计算哪些部分需要更新(可中断)
- 提交阶段(Commit)->不可中断,计算完成后,一次性提交更新,渲染UI
-
支持优先级
- 高优先级任务(如用户输入、动画)会被优先执行
- 低优先级任务(如数据加载)可以稍后处理
什么是虚拟 DOM(Virtual DOM)?
虚拟 DOM(Virtual DOM, VDOM) 是 React 等前端框架采用的一种优化技术,它是对真实 DOM 的一种抽象表示,用 JavaScript 对象(JSON 结构)来模拟 DOM 结构和属性。React 通过对比新旧虚拟 DOM(Diff 算法)来高效更新 UI,避免直接操作真实 DOM,从而提高性能。
1. 为什么需要虚拟 DOM?
在原生 JavaScript 或 jQuery 中,直接修改 DOM 会导致:
- 性能低下:DOM 操作通常是昂贵的,每次修改都会导致页面重绘(Repaint)和重排(Reflow)。
- 代码复杂:手动管理状态变化,导致代码维护难度增加。
- 不可预测性:多个操作可能互相影响,导致 bug。
虚拟 DOM 的优化点
- 减少直接 DOM 操作,通过批量更新降低性能损耗。
- Diff 算法对比新旧虚拟 DOM,只更新必要的部分,提高渲染效率。
- 保持 UI 状态一致,提高可维护性。
2. 虚拟 DOM 的工作原理
React 使用虚拟 DOM来优化 UI 更新过程,主要包含 3 个核心步骤:
-
创建 Virtual DOM(虚拟 DOM)
- 每个 React 组件都会返回 JSX,React 将其转换成一个 JavaScript 对象,模拟真实 DOM 结构。
-
Diff 算法对比新旧虚拟 DOM
- 当组件的
state
或props
发生变化时,React 会生成新的虚拟 DOM。 - 通过 Diff 算法比较新旧虚拟 DOM,找出不同之处(最小变更)。
- 当组件的
-
高效更新真实 DOM
- React 只更新发生变化的部分,而不是重新渲染整个页面。
- React 使用 Reconciliation(协调)算法,将变化批量合并,减少浏览器重绘次数,提高性能。
什么是React Diff?
文件路径 | 作用 |
---|---|
packages/react-reconciler/src/ReactFiberReconciler.js | Fiber 的入口文件 |
packages/react-reconciler/src/ReactFiberWorkLoop.js | Fiber 的调度核心 |
packages/react-reconciler/src/ReactFiberBeginWork.js | 处理组件更新 |
packages/react-reconciler/src/ReactFiberCompleteWork.js | 处理 Fiber 提交 |
packages/react-reconciler/src/ReactFiberCommitWork.js | 处理 DOM 变更 |
packages/react-reconciler/src/ReactFiberLane.js | 任务优先级管理 |
受控组件和非受控组件的区别
- 受控组件:
- 表单元素的值由 React 组件的
state
控制。 - 通过
onChange
事件更新state
,从而同步 UI 和数据状态。 - 组件本身掌控输入框的值,使数据更可控,利于表单校验和动态交互。
- 表单元素的值由 React 组件的
- 非受控组件(Uncontrolled Components)
- 表单元素的值不受 React
state
控制,而是由 DOM 自行管理。 - 通过
ref
获取 DOM 元素,读取或设置其值。 - 适用于无需频繁读取输入值或较简单的表单场景。
- 表单元素的值不受 React
Immutable和Immutable在React中的应用
什么是 Immutable?
Immutable(不可变) 指的是数据在创建后 不能被修改,如果需要修改,则会生成新的数据,而不会改变原数据。
在编程中,不可变性 是一种重要的概念,尤其在函数式编程和 React 的状态管理中,它能提高性能、减少副作用,并增强代码的可预测性。
为什么要使用 Immutable?
-
防止意外修改
- 避免多个引用修改同一对象,导致难以追踪的 Bug。
-
提升 React 性能
- React 依赖 状态变化 触发重新渲染,不可变数据让 浅比较(shallow compare) 变得高效,减少不必要的渲染。
-
方便调试
- 由于数据不会被修改,快照(Snapshot)调试更简单,可以轻松回溯历史状态。
-
提高并发安全性
- 在多线程或异步环境下,避免因共享数据修改引发的竞争问题(Race Condition)。
Immutable 在 React 中的应用
- 避免 React 组件不必要的重新渲染
const [count, setCount] = useState(0);
const handleClick = () => setCount(prev => prev + 1);
如果 setState
传入的值没有变,React 不会重新渲染。
2. 使用 useMemo()
和 useCallback()
依赖不可变数据
const memoizedValue = useMemo(() => expensiveCalculation(data), [data]);
const memoizedCallback = useCallback(() => handleClick(id), [id]);
如果 data
或 id
没有变化,React 不会重新计算。
3. Redux 的 reducer
必须返回新状态
const reducer = (state, action) => {
switch (action.type) {
case "INCREMENT":
return { ...state, count: state.count + 1 }; // 返回新对象
default:
return state;
}
};
Redux 的 useSelector
依赖数据不可变性,否则可能不会正确触发更新。
react错误边界
1. 什么是错误边界?
错误边界(Error Boundaries) 是 React 16 引入的一种机制,用于捕获组件树中发生的 JavaScript 错误,并防止整个应用崩溃。
-
错误边界只会捕获子组件的错误,不会捕获它自己内部的错误。
-
不会捕获:
- 事件处理函数中的错误(需手动
try...catch
) setTimeout
、requestAnimationFrame
等异步代码中的错误- 服务端渲染(SSR)中的错误
- 事件处理函数中的错误(需手动
2. 如何创建错误边界?
错误边界必须是 类组件,因为它依赖于生命周期方法:
static getDerivedStateFromError(error)
👉 当子组件抛出错误时,更新状态以渲染降级 UI。componentDidCatch(error, errorInfo)
👉 记录错误信息(如日志上报)。 示例:
import React, { Component } from "react";
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state,让 UI 降级
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Caught error:", error, errorInfo);
// 这里可以把错误上报给服务器
}
render() {
if (this.state.hasError) {
return <h2>出了点问题,请稍后再试。</h2>;
}
return this.props.children;
}
}
使用
function BuggyComponent() {
throw new Error("Oops! 出错了!");
}
function App() {
return (
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
);
}
export default App;
3. React 18+ 使用 Hooks 实现类似错误边界
React 官方不支持 Hooks 版本的错误边界,因为 Hooks 组件没有生命周期方法。但可以用 useEffect
+ ErrorBoundary
结合:
import React, { useEffect } from "react";
import { ErrorBoundary, useErrorBoundary } from "react-error-boundary";
function BuggyComponent() {
useEffect(() => {
throw new Error("Oops! Hook 组件出错了!");
}, []);
return <h2>不会渲染</h2>;
}
function FallbackComponent({ error, resetErrorBoundary }) {
return (
<div>
<h2>发生错误: {error.message}</h2>
<button onClick={resetErrorBoundary}>重试</button>
</div>
);
}
export default function App() {
return (
<ErrorBoundary FallbackComponent={FallbackComponent}>
<BuggyComponent />
</ErrorBoundary>
);
}
如何让useEffect支持async、await
useEffect
不支持 直接使用 async/await
,因为 useEffect
期望返回的是一个清理函数,而 async
函数返回的是 Promise,这会导致 React 无法正确处理副作用。
❌ 错误写法
jsx
复制编辑
useEffect(async () => {
const data = await fetchData(); // ❌ useEffect 不能是 async 函数
console.log(data);
}, []);
📌 为什么错误?
useEffect
期望返回清理函数(如return () => {}
),但async
会让useEffect
返回一个Promise
,React 无法正确处理。
✅ 正确写法
👉 方法 1:
jsx
复制编辑
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("https://api.example.com/data");
const result = await response.json();
console.log(result);
} catch (error) {
console.error("数据请求失败", error);
}
};
fetchData(); // 调用 async 函数
}, []);
📌 为什么这样可以?
fetchData
是async
函数,但它是 普通函数调用,不影响useEffect
结构。
👉 方法 2:使用 useEffect
+ useState
+ useRef
如果你的副作用涉及组件状态,需要避免内存泄漏:
jsx
复制编辑
import { useEffect, useState, useRef } from "react";
function MyComponent() {
const [data, setData] = useState(null);
const isMounted = useRef(true); // 追踪组件是否已卸载
useEffect(() => {
isMounted.current = true;
const fetchData = async () => {
try {
const response = await fetch("https://api.example.com/data");
const result = await response.json();
if (isMounted.current) { // 组件卸载后不再更新状态
setData(result);
}
} catch (error) {
console.error("数据请求失败", error);
}
};
fetchData();
return () => {
isMounted.current = false; // 清理
};
}, []);
return <div>{data ? JSON.stringify(data) : "加载中..."}</div>;
}
📌 为什么要 useRef
?
- 避免 组件卸载后继续更新状态,防止内存泄漏(⚠
setState
可能导致 React 抛出警告)。
为什么不能在循环、条件或嵌套函数中调用Hooks
React 的 Hooks 不能在循环、条件或嵌套函数中调用,主要是为了确保 Hooks 的调用顺序在每次渲染中保持一致。React 依赖于 Hooks 的调用顺序来追踪状态和副作用,以下是一些原因:
- 调用顺序一致性:React 需要确保每次组件渲染时 Hooks 按照相同的顺序调用。如果在循环或条件语句中调用 Hooks,它们的调用顺序就可能会发生变化,这会导致 React 无法正确地与这些状态或副作用进行关联,从而引发错误。
- 条件或循环导致不同的调用路径:如果在条件语句(例如
if
语句)或循环中调用 Hooks,React 在每次渲染时可能会跳过某些 Hooks,或者顺序发生变化。React 需要知道 Hooks 是按固定顺序调用的,以便它能正确地管理每个 Hook 相关的状态和副作用。 - 性能和优化:React 的更新机制依赖于 Hook 的顺序来优化性能。如果调用顺序不一致,React 可能无法正确跳过不需要更新的部分,导致不必要的重新渲染和性能问题。
解决办法
- 始终在顶层调用 Hook:Hooks 应该在组件的顶层直接调用,避免在任何条件语句、循环或嵌套函数内调用它们。
- 使用条件渲染来控制渲染内容:如果你希望根据某些条件渲染不同的内容,可以将条件判断放在渲染的 JSX 中,而不是在调用 Hooks 时做判断。
react的使用有什么限制条件吗
React 的使用有一些限制条件或最佳实践,以确保性能、可维护性和代码的一致性。以下是一些关键限制和最佳实践:
1. 只能在函数组件和自定义 Hook 中调用 Hooks
- 限制:React Hooks(如
useState
、useEffect
、useContext
等)只能在函数组件或者自定义 Hook 中调用,不能在类组件、普通函数或外部作用域中调用。 - 原因:Hooks 是为了让函数组件能够有状态和副作用,而类组件使用
this
来管理状态和生命周期,所以 Hooks 不能在类组件中使用。
2. Hooks 必须按顺序调用
- 限制:Hooks 必须在每次渲染中按相同的顺序调用,不能在条件语句、循环或嵌套函数中调用。
- 原因:React 内部依赖于 Hooks 的调用顺序来跟踪每个 Hook 的状态和副作用。如果顺序不一致,React 就无法正确处理这些状态。
3. 不能在条件语句、循环或嵌套函数中调用 Hooks
- 限制:不要在
if
、for
、while
等条件或循环语句中调用 Hooks,也不能在事件处理函数、回调函数或其他嵌套函数中调用 Hooks。 - 原因:这会导致每次渲染时调用的 Hook 数量或顺序不同,破坏 React 的渲染流程,导致无法正确管理状态。
4. 不能在类组件中使用 Hooks
- 限制:类组件不能使用 Hooks,只能使用
this.state
和生命周期方法。 - 原因:Hooks 是为了简化函数组件的状态管理和副作用处理,而类组件的设计与 Hooks 是不兼容的。
5. 必须在顶层调用 Hooks
- 限制:Hooks 应该在函数组件的顶层直接调用,不能嵌套在条件语句或其他函数内部。
- 原因:React 需要保证每次渲染时 Hook 的调用顺序保持一致。
6. 每个组件只能有一个顶层 useState
或 useReducer
等
- 限制:你可以在同一个组件中使用多个
useState
或useReducer
,但是必须确保它们在顶层进行调用,而不是在嵌套函数或条件内。 - 原因:React 需要确定哪些状态在某个组件中,确保状态与组件实例相关联。
7. useEffect 和清理副作用
- 限制:
useEffect
只能在函数组件内调用,并且副作用的清理工作(例如取消订阅或清理定时器)应当在返回的清理函数中进行。 - 原因:React 会在每次渲染后执行
useEffect
,并且为了避免内存泄漏,必须正确清理副作用。
8. 不可变性(Immutability)
- 限制:React 强烈推荐状态应该是不可变的(即每次更新时应该返回一个新对象或新值,而不是直接修改原状态)。
- 原因:这有助于 React 高效地比较组件的状态和渲染结果,从而提高性能。
9. React 的 Concurrent Mode(并发模式)
- 限制:React 的并发模式在某些情况下可能会导致不兼容,特别是对于一些副作用和状态管理的复杂逻辑。
- 原因:并发模式会让 React 可以更加灵活地控制组件渲染的时机,可能需要调整一些使用模式来确保兼容性。
10. 过度依赖 Context
- 限制:虽然 React 的
useContext
Hook 很方便,但不要滥用它来管理全局状态。过度依赖 Context 会导致性能问题。 - 原因:每次 Context 发生变化时,所有消费者组件都会重新渲染。如果过多地依赖 Context,可能导致不必要的重渲染,影响性能。
usememo和usecallback的区别和使用场景
useMemo
和 useCallback
都是 React 中用于优化性能的 Hook,它们都可以避免不必要的计算和函数重新创建,但它们的使用场景和工作原理有所不同。以下是它们的区别和使用场景:
1. useMemo
-
作用:用于缓存计算结果,避免在每次渲染时都进行重复计算。它接受一个计算函数和依赖项数组,只有在依赖项发生变化时才重新计算值。
-
语法:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
-
什么时候使用:当你有一个复杂的计算过程,需要根据某些输入(如 props 或 state)计算结果,而你希望只有在输入发生变化时才重新计算这个值。
-
性能优化场景:当某个计算量大的操作依赖于某些 props 或 state,并且只有在这些依赖项变化时才需要重新计算时,使用
useMemo
可以避免不必要的重新计算。
如果没有 useMemo
,每次渲染组件时 a * b
都会重新计算,这可能会导致性能问题,尤其是在 expensiveCalculation
的计算过程较为复杂时。
2. useCallback
-
作用:用于缓存函数,避免在每次渲染时都创建新的函数实例。它接受一个回调函数和依赖项数组,只有在依赖项发生变化时才会返回一个新的函数。
-
语法:
javascript 复制编辑 const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
-
什么时候使用:当你将回调函数作为 prop 传递给子组件时,使用
useCallback
来缓存这个回调函数,避免不必要的重新渲染。 -
性能优化场景:当回调函数被多次传递给子组件,或者回调函数作为依赖项传递给
useEffect
或useMemo
时,使用useCallback
可以避免不必要的函数重建,从而提升性能。
总结
- 使用
useMemo
来缓存计算结果,避免昂贵计算。 - 使用
useCallback
来缓存函数实例,避免在子组件中不必要的重新渲染。
react转换jsx为真实dom的过程
-
JSX 转为 React 元素:JSX 被编译为
React.createElement
调用,生成 React 元素。 -
React 元素转为虚拟 DOM:React 元素表示为虚拟 DOM 树,这是一个轻量级的 JavaScript 对象。
-
虚拟 DOM 对比:通过
diff
算法对比当前虚拟 DOM 树和之前的虚拟 DOM 树,找到差异。 -
更新真实 DOM:React 根据差异更新真实 DOM,仅更新变化的部分,最大化性能
useEffect未传入依赖时会执行几次
1. 没有传入依赖数组 (useEffect(callback)
)
当 useEffect
没有传入第二个参数时,默认会在每次组件渲染后执行。这意味着:
- 首次渲染时执行:在组件挂载时执行一次。
- 每次更新时执行:每当组件更新(包括状态更新、父组件重新渲染等)时,都会执行
useEffect
中的回调函数。
2. 依赖数组为空 (useEffect(callback, [])
)
当 useEffect
传入一个空数组作为第二个参数时,useEffect
仅在 组件挂载时 执行一次,之后即使组件更新,也不会再执行。