学习记录-react篇

101 阅读22分钟

React18有哪些更新

  • 并发模式
  • 更新render API
  • 自动批处理
  • suspense支持ssr
  • startTransition
  • useTransition
  • useId
  • 提供给第三方库的hook

jsx是什么,它和js的区别

jsx是js语法的扩展,它允许编写类似于HTML的代码。它可以编译为常规的js函数带哦用,从而为创建组件标记提供了一中更好的方法。

简述React的生命周期

React的生命周期主要分为三个阶段:挂载(Mounting)更新(Updating)卸载(Unmounting)

React事件机制和原生DOM事件流有什么区别

  1. 绑定方式
    • 原生DOM事件:在原生DOM中,事件是直接绑定到每个DOM元素上的。每当你在DOM元素上使用addEventListener方法时,事件会被绑定到该元素上。
    • React事件机制:react使用事件委托的方式处理事件。所有的事件处理函数都绑定到document或根元素(如root)上,而不是每个DOM元素。React会在一个通用的事件处理器上捕获所有事件,并根据事件目标(target)来判断并执行相应的回调。并且react在事件传播时会利用‘合成事件’机制来确保不同浏览器下的事件表现一致。
  2. 事件委托VS直接绑定
    • 原生DOM事件:
      • 原生DOM事件需要为每个DOM元素单独绑定事件。这可能会导致性能问题,特别是在页面包含大量元素时。
      • 事件捕获:事件在触发时会一次从目标元素向上冒泡到父元素,直到根元素。也可以选择使用事件捕获(捕获阶段),即事件从根元素到目标元素的过程。
    • react事件机制:
      • react利用了事件委托机制,所有事件处理器都被委托到顶层的document或root节点,这样就避免了为每个DOM元素单独绑定事件的开销。
      • react的事件处理是在合成事件系统中完成的,它模拟了DOM事件流(捕获、目标、冒泡),但在内部进行优化,避免了直接操作DOM。
      • 事件委托使react具有更好的性能,因为它避免了每个组件单独绑定事件。
  3. 事件对象
    • 原生DOM事件:
      • 原生DOM事件对象直接访问浏览器提供的事件对象,包含关于事件的详细信息,如event.target,event.currentTarge等
    • react事件机制:
      • react使用合成事件。这是一个跨浏览器的封装对象,模拟了原生DOM事件对象的行为。合成事件提供了于原生DOM事件相同的API,但具有额外的性能优化。它会自动池化,并在事件回调后回收。
      • 池化意味着合成事件在事件处理后被充值,不再可以访问其属性,为了避免这个问题。react提供了event.persist()方法,可以手动保留事件对象。
  4. 事件流
    • 原生DOM事件:

      • 原生DOM事件遵循事件流的三个阶段:捕获阶段(Capture)目标阶段(Target)冒泡阶段(Bubble)

        1. 捕获阶段:事件从根元素向下传递到目标元素。
        2. 目标阶段:事件到达目标元素并触发。
        3. 冒泡阶段:事件从目标元素向上传递回根元素。
    • react事件机制:

      • React 的事件系统模拟了原生 DOM 的事件流,但它默认使用冒泡阶段来处理事件(捕获阶段也可以通过 onClickCapture 来触发)。React 会在事件传播到目标元素时触发事件处理程序。

      • 你无法在 React 中通过参数直接设置事件是捕获还是冒泡,但你可以使用 onClickCapture 来模拟捕获阶段的事件处理:

  5. 性能优化
    • 原生 DOM 事件:
      • 每个元素的事件绑定会产生一定的性能开销,尤其是在有很多元素时,事件监听会被附加到每个元素上。
    • React 事件机制:
      • 由于事件委托机制,React 将事件绑定到根元素上,这大大减少了内存的消耗和性能开销。React 的事件系统通过事件池化和事件委托来优化性能。
  6. 事件类型支持
    • 原生 DOM 事件
      • 原生 DOM 事件种类繁多,通常根据浏览器的实现而有所不同。
    • React 事件机制
      • React 使用统一的合成事件系统,这使得 React 在不同浏览器中具有一致的行为,避免了浏览器兼容性问题。

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生命周期,所有有状态逻辑都必须写在类组件里。这导致了以下问题:

  1. 逻辑复用困难 🧐

    • 组件逻辑(如订阅数据、获取 API、表单管理)很难复用。
    • 只能通过 HOC(高阶组件)Render Props,但这些方式增加了代码嵌套,使代码难以维护。
  2. 类组件复杂性高 🏗️

    • 需要 this 绑定,容易出错。
    • 生命周期函数分散,不同逻辑写在不同生命周期里,不易维护。
    • componentDidMountcomponentDidUpdatecomponentWillUnmount 可能重复执行相似逻辑。
  3. 函数组件功能受限 😞

    • 不能使用 state生命周期,功能有限。

React Hooks 诞生后,彻底解决了这些问题!🎉

函数组件 vs. 类组件的区别

对比点类组件 (Class Component)函数组件 (Function Component + Hooks)
语法结构需要继承 React.Component,使用 this直接用 function 定义,无需 this
状态管理this.state,用 setState 更新useState() 更新状态
生命周期componentDidMountcomponentDidUpdatecomponentWillUnmountuseEffect() 统一处理
代码复杂度逻辑分散,代码冗长逻辑集中,代码更简洁
逻辑复用依赖 HOC、Render PropsuseEffect() 让逻辑更清晰,自定义 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

  1. 使用 useEffect
jsx
复制编辑
useEffect(() => {
  console.log(count); // 这里可以获取最新 state
}, [count]);
  1. 使用 setState 的回调函数
jsx
复制编辑
this.setState((prevState) => {
  console.log(prevState.count); // 这里能拿到最新的 state
  return { count: prevState.count + 1 };
});
  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,可以用 useEffectsetState 回调

什么是fiber、fiber解决了什么问题

fiber是React16引入的新的协调(Reconciliation)算法,它彻底重构了React的渲染机制,使其更高效、可控,支持终端、优先级调度,极大的优化了大规模应用的渲染性能。 在React16之前,整个更新过程是递归的,不可中断的,导致:

  • 界面卡顿(更新事件过长时,主线程被阻塞)
  • 无法优先处理重要任务

核心

  1. 每个Virtual DOM编程一个Fiber节点,让更新过程分布执行&可控

  2. 渲染过程拆分为两个阶段

    • 任务拆分阶段(Reconciliation)->可中断,React先遍历Fiber树,计算哪些部分需要更新(可中断)
    • 提交阶段(Commit)->不可中断,计算完成后,一次性提交更新,渲染UI
  3. 支持优先级

    • 高优先级任务(如用户输入、动画)会被优先执行
    • 低优先级任务(如数据加载)可以稍后处理

什么是虚拟 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 个核心步骤:

  1. 创建 Virtual DOM(虚拟 DOM)

    • 每个 React 组件都会返回 JSX,React 将其转换成一个 JavaScript 对象,模拟真实 DOM 结构。
  2. Diff 算法对比新旧虚拟 DOM

    • 当组件的 stateprops 发生变化时,React 会生成新的虚拟 DOM
    • 通过 Diff 算法比较新旧虚拟 DOM,找出不同之处(最小变更)。
  3. 高效更新真实 DOM

    • React 只更新发生变化的部分,而不是重新渲染整个页面。
    • React 使用 Reconciliation(协调)算法,将变化批量合并,减少浏览器重绘次数,提高性能。

什么是React Diff?

文件路径作用
packages/react-reconciler/src/ReactFiberReconciler.jsFiber 的入口文件
packages/react-reconciler/src/ReactFiberWorkLoop.jsFiber 的调度核心
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 和数据状态。
    • 组件本身掌控输入框的值,使数据更可控,利于表单校验和动态交互。
  • 非受控组件(Uncontrolled Components)
    • 表单元素的值不受 React state 控制,而是由 DOM 自行管理。
    • 通过 ref 获取 DOM 元素,读取或设置其值。
    • 适用于无需频繁读取输入值或较简单的表单场景。

Immutable和Immutable在React中的应用

什么是 Immutable?

Immutable(不可变) 指的是数据在创建后 不能被修改,如果需要修改,则会生成新的数据,而不会改变原数据。

在编程中,不可变性 是一种重要的概念,尤其在函数式编程和 React 的状态管理中,它能提高性能、减少副作用,并增强代码的可预测性。

为什么要使用 Immutable?

  1. 防止意外修改

    • 避免多个引用修改同一对象,导致难以追踪的 Bug。
  2. 提升 React 性能

    • React 依赖 状态变化 触发重新渲染,不可变数据让 浅比较(shallow compare) 变得高效,减少不必要的渲染。
  3. 方便调试

    • 由于数据不会被修改,快照(Snapshot)调试更简单,可以轻松回溯历史状态。
  4. 提高并发安全性

    • 在多线程或异步环境下,避免因共享数据修改引发的竞争问题(Race Condition)。

Immutable 在 React 中的应用

  1. 避免 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]);

如果 dataid 没有变化,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
    • setTimeoutrequestAnimationFrame 等异步代码中的错误
    • 服务端渲染(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 函数
}, []);

📌 为什么这样可以?

  • fetchDataasync 函数,但它是 普通函数调用,不影响 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 的调用顺序来追踪状态和副作用,以下是一些原因:

  1. 调用顺序一致性:React 需要确保每次组件渲染时 Hooks 按照相同的顺序调用。如果在循环或条件语句中调用 Hooks,它们的调用顺序就可能会发生变化,这会导致 React 无法正确地与这些状态或副作用进行关联,从而引发错误。
  2. 条件或循环导致不同的调用路径:如果在条件语句(例如 if 语句)或循环中调用 Hooks,React 在每次渲染时可能会跳过某些 Hooks,或者顺序发生变化。React 需要知道 Hooks 是按固定顺序调用的,以便它能正确地管理每个 Hook 相关的状态和副作用。
  3. 性能和优化:React 的更新机制依赖于 Hook 的顺序来优化性能。如果调用顺序不一致,React 可能无法正确跳过不需要更新的部分,导致不必要的重新渲染和性能问题。

解决办法

  • 始终在顶层调用 Hook:Hooks 应该在组件的顶层直接调用,避免在任何条件语句、循环或嵌套函数内调用它们。
  • 使用条件渲染来控制渲染内容:如果你希望根据某些条件渲染不同的内容,可以将条件判断放在渲染的 JSX 中,而不是在调用 Hooks 时做判断。

react的使用有什么限制条件吗

React 的使用有一些限制条件或最佳实践,以确保性能、可维护性和代码的一致性。以下是一些关键限制和最佳实践:

1. 只能在函数组件和自定义 Hook 中调用 Hooks

  • 限制:React Hooks(如 useStateuseEffectuseContext 等)只能在函数组件或者自定义 Hook 中调用,不能在类组件、普通函数或外部作用域中调用。
  • 原因:Hooks 是为了让函数组件能够有状态和副作用,而类组件使用 this 来管理状态和生命周期,所以 Hooks 不能在类组件中使用。

2. Hooks 必须按顺序调用

  • 限制:Hooks 必须在每次渲染中按相同的顺序调用,不能在条件语句、循环或嵌套函数中调用。
  • 原因:React 内部依赖于 Hooks 的调用顺序来跟踪每个 Hook 的状态和副作用。如果顺序不一致,React 就无法正确处理这些状态。

3. 不能在条件语句、循环或嵌套函数中调用 Hooks

  • 限制:不要在 ifforwhile 等条件或循环语句中调用 Hooks,也不能在事件处理函数、回调函数或其他嵌套函数中调用 Hooks。
  • 原因:这会导致每次渲染时调用的 Hook 数量或顺序不同,破坏 React 的渲染流程,导致无法正确管理状态。

4. 不能在类组件中使用 Hooks

  • 限制:类组件不能使用 Hooks,只能使用 this.state 和生命周期方法。
  • 原因:Hooks 是为了简化函数组件的状态管理和副作用处理,而类组件的设计与 Hooks 是不兼容的。

5. 必须在顶层调用 Hooks

  • 限制:Hooks 应该在函数组件的顶层直接调用,不能嵌套在条件语句或其他函数内部。
  • 原因:React 需要保证每次渲染时 Hook 的调用顺序保持一致。

6. 每个组件只能有一个顶层 useStateuseReducer

  • 限制:你可以在同一个组件中使用多个 useStateuseReducer,但是必须确保它们在顶层进行调用,而不是在嵌套函数或条件内。
  • 原因: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的区别和使用场景

useMemouseCallback 都是 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 来缓存这个回调函数,避免不必要的重新渲染。

  • 性能优化场景:当回调函数被多次传递给子组件,或者回调函数作为依赖项传递给 useEffectuseMemo 时,使用 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 仅在 组件挂载时 执行一次,之后即使组件更新,也不会再执行。