React 18+ 所有Hooks全解析(含实战示例,新手零踩坑)

React Hooks 是React 16.8+ 引入的核心特性,彻底替代了类组件,让函数组件拥有状态管理、副作用处理、性能优化等能力。本文整理了React 18+ 全部内置Hooks,按「基础必用、副作用、状态进阶、引用、上下文、性能优化、并发、工具类」分类,每个Hook都包含「核心作用、语法、实战示例、适用场景、注意事项」,覆盖日常开发99%的场景,新手可直接套用,老手可查漏补缺。

核心原则:所有Hooks仅能在「函数组件顶层」或「自定义Hook顶层」调用,不能在条件判断、循环、嵌套函数中调用(React通过调用顺序识别Hooks,避免逻辑混乱)。

一、基础必用Hooks(入门必备,100%高频)

这类Hooks是React开发的基础,几乎所有函数组件都会用到,必须熟练掌握。

1. useState ------ 函数组件状态管理(核心)

核心作用

给函数组件添加响应式状态,返回「当前状态值 + 状态更新函数」,是最基础、最常用的Hook。

语法

scss 复制代码
// 基础用法:初始值直接传入
const [state, setState] = useState(initialState);

// 函数式初始值:仅首次渲染执行(适合昂贵计算,避免每次渲染重复计算)
const [state, setState] = useState(() => {
  return computeInitialValue(); // 昂贵计算逻辑
});

// 函数式更新:依赖上一次的状态值(避免闭包陷阱)
setState(prevState => newState);

实战示例

javascript 复制代码
import { useState } from 'react';

// 示例1:简单计数器(基础状态)
function Counter() {
  // 声明count状态,初始值为0,setCount为更新函数
  const [count, setCount] = useState(0);
  
  // 函数式更新:依赖上一次的count值,避免闭包问题
  const increment = () => setCount(prev => prev + 1);
  const decrement = () => setCount(prev => prev - 1);
  
  return (
    
      当前计数:{count}
      <button onClick={increment}>+1<button onClick={-1
  );
}

// 示例2:对象状态(需注意更新方式)
function UserForm() {
  // 声明对象类型状态
  const [user, setUser] = useState({ name: '', age: 18 });
  
  // 对象更新:必须返回新对象(不能直接修改原对象,React无法检测变化)
  const updateName = (e) => {
    setUser(prev => ({ ...prev, name: e.target.value }));
  };
  
  return (
    <input value={姓名" />
      姓名:{user.name},年龄:{user.age}
  );
}

适用场景

简单响应式状态管理:计数器、开关(布尔值)、输入框值、弹窗显示/隐藏、简单数组/对象状态。

注意事项

  • 状态更新是「替换而非合并」:如果状态是对象/数组,必须返回新引用(使用扩展运算符...mapfilter等),不能直接修改原状态(如user.name = 'xxx'无效)。
  • 初始值仅首次渲染生效:即使初始值是函数,也只会在组件首次挂载时执行一次,后续渲染不会重复执行。
  • 避免闭包陷阱:如果更新状态依赖上一次的状态值(如计数器累加),必须使用「函数式更新」(prev => prev + 1),否则可能获取到旧状态。
  • 状态更新是异步的:React会批量处理状态更新,在同一个事件处理函数中多次调用setState,只会触发一次渲染(如setCount(1); setCount(2);最终只显示2)。

2. useEffect ------ 副作用处理(核心)

核心作用

处理函数组件的「副作用」,替代类组件的componentDidMount(挂载)、componentDidUpdate(更新)、componentWillUnmount(卸载)三个生命周期钩子,统一管理副作用逻辑。

常见副作用:数据请求、DOM操作、定时器/计时器、事件监听、订阅(如WebSocket)等。

语法

scss 复制代码
// setup:副作用函数,执行副作用逻辑
// dependencies:依赖数组,控制setup函数的执行时机(可选)
useEffect(setup, dependencies?);

// setup函数可返回一个「清理函数」,用于组件卸载/依赖变化时清理副作用
useEffect(() => {
  // 副作用逻辑(如请求、定时器)
  return () => {
    // 清理逻辑(如取消请求、清除定时器、移除事件监听)
  };
}, [dependencies]);

执行时机(关键)

  • 不传递依赖数组(useEffect(setup)):每次组件渲染(挂载+更新)都会执行setup函数,慎用(性能较差)。
  • 传递空依赖数组(useEffect(setup, [])):仅在组件挂载时执行一次setup函数,卸载时执行清理函数(对应componentDidMount + componentWillUnmount)。
  • 传递依赖数组(useEffect(setup, [a, b])):组件挂载时执行一次,后续只有当依赖数组中的ab发生变化时,才会执行setup函数(对应componentDidMount + componentDidUpdate),卸载时执行清理函数。

实战示例

scss 复制代码
import { useState, useEffect } from 'react';

// 示例1:数据请求(挂载时请求,卸载时取消请求)
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 创建AbortController,用于取消请求
    const controller = new AbortController();
    const signal = controller.signal;

    // 异步请求函数
    const fetchUsers = async () => {
      try {
        const res = await fetch('/api/users', { signal });
        const data = await res.json();
        setUsers(data);
      } catch (err) {
        // 忽略取消请求导致的错误
        if (err.name !== 'AbortError') {
          console.error('请求失败:', err);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();

    // 清理函数:组件卸载时取消请求
    return () => controller.abort();
  }, []); // 空依赖,仅挂载执行

  if (loading) return 加载中...;
  return (
    
      {users.map(user => (
        <div key={{user.name}
      ))}
  );
}

// 示例2:定时器(挂载时启动,卸载时清除)
function Timer() {
  const [time, setTime] = useState(0);

  useEffect(() => {
    // 启动定时器
    const timer = setInterval(() => {
      setTime(prev => prev + 1);
    }, 1000);

    // 清理函数:卸载时清除定时器(避免内存泄漏)
    return () => clearInterval(timer);
  }, []); // 空依赖,仅挂载执行

  return 当前时间:{time}秒;
}

适用场景

数据请求、DOM操作(如设置文档标题、操作DOM元素)、定时器/计时器、事件监听(如window.addEventListener)、订阅(如WebSocket连接)、清理无用资源(避免内存泄漏)。

注意事项

  • 清理函数的执行时机:组件卸载时、setup函数再次执行前(依赖变化时),必须清理可能导致内存泄漏的资源(定时器、事件监听、WebSocket、未完成的请求)。
  • 依赖数组必须完整:如果setup函数中使用了组件内的变量、状态、函数,必须将其加入依赖数组,否则可能获取到旧值(闭包陷阱);若依赖数组为空,setup函数中不能使用任何组件内的动态值。
  • 避免无限循环:如果setup函数中修改了依赖数组中的值,会导致依赖变化,再次触发setup函数,形成无限循环(如useEffect(() => setCount(prev => prev + 1), [count]))。
  • 副作用函数是异步的:不能直接给setup函数加async(会导致清理函数失效),若需执行异步逻辑,需在setup函数内部定义异步函数并调用。

二、副作用进阶Hooks(18+新增,替代useEffect特定场景)

React 18 新增了两个副作用Hook,专门解决useEffect的部分痛点,针对性更强,在特定场景下更推荐使用。

1. useLayoutEffect ------ 同步DOM副作用

核心作用

useEffect功能类似,区别在于「执行时机」:useLayoutEffect在DOM更新后、浏览器绘制前同步执行,useEffect在浏览器绘制后异步执行。适合需要同步操作DOM、避免页面闪烁的场景。

语法

scss 复制代码
useLayoutEffect(setup, dependencies?);
// 语法与useEffect完全一致,仅执行时机不同

实战示例

scss 复制代码
import { useLayoutEffect, useState, useRef } from 'react';

// 示例:同步调整DOM位置,避免页面闪烁
function LayoutDemo() {
  const [width, setWidth] = useState(0);
  const ref = useRef(null);

  // useLayoutEffect:DOM更新后立即执行,同步获取DOM尺寸并调整
  useLayoutEffect(() => {
    if (ref.current) {
      // 同步获取DOM宽度
      setWidth(ref.current.offsetWidth);
      // 同步调整DOM位置(避免闪烁)
      ref.current.style.marginLeft = `${(window.innerWidth - width) / 2}px`;
    }
  }, [width]);

  return <div ref={0px', background: 'red' }} />;
}

适用场景

需要同步操作DOM、获取DOM尺寸/位置、调整DOM样式(避免页面闪烁)的场景,如弹窗居中、滚动位置调整、DOM尺寸监听。

注意事项

  • 执行时机:DOM更新后、浏览器绘制前,同步执行,会阻塞浏览器绘制,若逻辑过久会影响页面性能,慎用。
  • 与useEffect的区别:大部分场景用useEffect即可,只有需要同步操作DOM、避免闪烁时,才用useLayoutEffect
  • 服务器端渲染(SSR):useLayoutEffect在服务器端不会执行,若需兼容SSR,优先用useEffect,或在客户端判断后执行。

2. useInsertionEffect ------ CSS-in-JS 专用副作用

核心作用

React 18 新增,专门用于 CSS-in-JS 库(如Styled Components、Emotion),在DOM更新前插入CSS样式,避免样式闪烁,是useLayoutEffect的补充。

语法

scss 复制代码
useInsertionEffect(setup, dependencies?);
// 语法与useEffect、useLayoutEffect一致,执行时机不同

执行时机

DOM更新前执行,早于useLayoutEffect,专门用于插入CSS样式,确保样式在DOM渲染前生效,避免样式闪烁。

适用场景

仅用于 CSS-in-JS 库的样式插入,普通开发场景几乎用不到,日常开发可忽略。

注意事项

  • 不能访问DOM:执行时机在DOM更新前,无法获取DOM元素(如ref.currentnull),仅能插入CSS样式。
  • 非CSS-in-JS不用:普通CSS、Sass/Less等场景,无需使用,用useEffectuseLayoutEffect即可。

三、状态进阶Hooks(复杂状态管理)

当状态逻辑复杂(如依赖多个状态、状态更新依赖其他状态)时,使用这类Hook,简化状态管理逻辑。

1. useReducer ------ 复杂状态管理(替代useState)

核心作用

用于管理复杂状态(如状态是对象/数组、状态更新依赖多个因素、多个状态相互关联),通过「 reducer 函数」统一处理状态更新逻辑,类似Redux的核心思想,比useState更适合复杂场景。

语法

csharp 复制代码
// reducer:状态更新函数,接收当前状态和动作,返回新状态
// initialState:初始状态
// init:可选,初始化函数,用于延迟初始化状态(适合昂贵计算)
const [state, dispatch] = useReducer(reducer, initialState, init?);

// reducer函数格式
function reducer(state, action) {
  switch (action.type) {
    case 'TYPE1':
      return newState1; // 根据action返回新状态
    case 'TYPE2':
      return newState2;
    default:
      return state; // 默认返回原状态,避免报错
  }
}

实战示例

javascript 复制代码
import { useReducer, useState } from 'react';

// 1. 定义reducer函数:统一处理状态更新
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      // 添加待办事项,返回新数组(不能修改原数组)
      return [...state, { id: Date.now(), text: action.payload, done: false }];
    case 'TOGGLE_TODO':
      // 切换待办状态
      return state.map(todo => 
        todo.id === action.payload ? { ...todo, done: !todo.done } : todo
      );
    case 'DELETE_TODO':
      // 删除待办事项
      return state.filter(todo => todo.id !== action.payload);
    default:
      return state;
  }
}

// 2. 使用useReducer管理状态
function TodoList() {
  // 初始状态:空数组
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [text, setText] = useState('');

  // 触发状态更新:通过dispatch发送action
  const addTodo = () => {
    if (!text.trim()) return;
    dispatch({ type: 'ADD_TODO', payload: text }); // payload:传递参数
    setText('');
  };

  return (
    <input value={e) => setText(e.target.value)} />
      <button onClick={}>添加待办
        {todos.map(todo => (
         <li 
            key={one ? 'line-through' : 'none' }}
            onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
          >
            {todo.text}<button onClick={ dispatch({ type: 'DELETE_TODO', payload: todo.id })}>删除
        ))}
      
  );
}

适用场景

复杂状态管理:待办列表、购物车、表单多字段状态、多个状态相互关联(如登录状态+用户信息+权限)。

注意事项

  • reducer必须是纯函数:不能修改原状态(必须返回新状态),不能包含副作用(如请求、DOM操作),仅处理状态更新逻辑。
  • action格式规范:推荐使用{ type: 'XXX', payload: 数据 }的格式,type用大写字符串,便于维护和调试。
  • 与useState的选择:简单状态用useState,复杂状态(多状态关联、多更新逻辑)用useReducer
  • 延迟初始化:如果初始状态需要昂贵计算,可使用第三个参数init函数,仅首次渲染执行一次。

四、引用Hook(获取DOM/保存持久值)

用于获取DOM元素、保存组件渲染周期内的持久值(不随渲染变化),核心是useRef,衍生出useImperativeHandle用于暴露组件内部方法。

1. useRef ------ 获取DOM/保存持久值(高频)

核心作用

有两个核心用途:① 获取DOM元素或组件实例;② 保存组件渲染周期内的持久值(不随渲染重新初始化,修改不会触发组件重新渲染)。

语法

csharp 复制代码
// initialValue:初始值,可选(默认undefined)
const ref = useRef(initialValue);

// 获取DOM:通过ref.current访问DOM元素
// 保存持久值:通过ref.current修改/访问,修改不会触发渲染

实战示例

scss 复制代码
import { useRef, useState, useEffect } from 'react';

// 示例1:获取DOM元素(如输入框聚焦)
function InputFocus() {
  // 创建ref,绑定到输入框
  const inputRef = useRef(null);

  useEffect(() => {
    // 组件挂载时,让输入框自动聚焦(访问ref.current获取DOM)
    inputRef.current?.focus();
  }, []);

  return <input ref={聚焦" />;
}

// 示例2:保存持久值(不触发渲染)
function TimerWithRef() {
  const [time, setTime] = useState(0);
  // 保存定时器ID,持久化存储,不随渲染变化
  const timerRef = useRef(null);

  useEffect(() => {
    // 启动定时器,将ID存入ref
    timerRef.current = setInterval(() => {
      setTime(prev => prev + 1);
    }, 1000);

    // 清理定时器:从ref中获取ID
    return () => clearInterval(timerRef.current);
  }, []);

  // 暂停定时器:修改ref.current,不会触发组件渲染
  const pause = () => {
    clearInterval(timerRef.current);
  };

  return (
    时间:{time}秒<button onClick={暂停 );
}

适用场景

获取DOM元素(输入框聚焦、获取DOM尺寸)、保存定时器ID、保存WebSocket连接、保存组件渲染周期内的临时值(不触发渲染)。

注意事项

  • ref.current修改不触发渲染:与useState不同,修改ref.current的值不会导致组件重新渲染,适合保存不需要触发渲染的临时值。
  • DOM ref的初始化时机:组件挂载完成后,ref.current才会指向DOM元素,挂载前为null,需用?.避免报错。
  • 函数组件不能直接获取实例:如果给函数组件绑定ref,需配合forwardRefuseImperativeHandle,才能暴露组件内部方法/属性。

2. useImperativeHandle ------ 暴露组件内部方法(进阶)

核心作用

配合forwardRef使用,让父组件通过ref获取子组件的内部方法/属性,自定义暴露给父组件的内容,避免暴露整个子组件实例,提升封装性。

语法

scss 复制代码
// ref:父组件传递的ref
// createHandle:函数,返回暴露给父组件的方法/属性对象
// dependencies:依赖数组,依赖变化时重新生成暴露的内容
useImperativeHandle(ref, createHandle, dependencies?);

// 必须配合forwardRef包裹子组件,才能接收父组件传递的ref
const Child = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    // 暴露给父组件的方法/属性
    method1: () => {},
    property1: ''
  }), []);
  return 子组件;
});

实战示例

javascript 复制代码
import { useRef, forwardRef, useImperativeHandle } from 'react';

// 子组件:配合forwardRef和useImperativeHandle,暴露内部方法
const ChildInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);

  // 暴露给父组件的方法:聚焦输入框、清空输入框
  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current?.focus(),
    clear: () => inputRef.current.value = ''
  }), []); // 空依赖,暴露的方法不随依赖变化

  return <input ref={" />;
});

// 父组件:通过ref调用子组件暴露的方法
function Parent() {
  const childRef = useRef(null);

  const handleFocus = () => {
    // 调用子组件暴露的focus方法
    childRef.current?.focus();
  };

  const handleClear = () => {
    // 调用子组件暴露的clear方法
    childRef.current?.clear();
  };

  return (
    <ChildInput ref={childRef} />
      <button onClick={聚焦输入框<button onClick={清空输入框
  );
}

适用场景

父组件需要调用子组件内部方法的场景:输入框聚焦/清空、弹窗显示/隐藏、子组件表单提交等。

注意事项

  • 必须配合forwardRef:子组件必须用forwardRef包裹,才能接收父组件传递的ref,否则无法使用useImperativeHandle
  • 避免过度暴露:仅暴露父组件需要的方法/属性,不要暴露整个子组件的内部状态,保持子组件的封装性。
  • 依赖数组:如果暴露的方法依赖子组件的状态/ props,必须将其加入依赖数组,否则可能获取到旧值。

五、上下文Hook(跨组件状态共享)

用于跨组件共享状态(无需逐层传递props),核心是useContext,配合createContext使用,替代类组件的Context API

1. useContext ------ 跨组件状态共享(高频)

核心作用

接收一个Context对象(由createContext创建),获取Context中共享的状态和方法,实现跨组件(父子、祖孙、兄弟)状态共享,无需逐层传递props(解决props drilling问题)。

语法

ini 复制代码
// 1. 创建Context对象(可设置默认值,可选)
const MyContext = createContext(defaultValue);

// 2. 用Provider包裹组件树,提供共享状态
<MyContext.Provider value={共享的状态/方法}>
  子组件树
</MyContext.Provider>

// 3. 在子组件中使用useContext获取共享内容
const context = useContext(MyContext);

实战示例

ini 复制代码
import { createContext, useContext, useState } from 'react';

// 1. 创建Context(主题上下文,默认值为light)
const ThemeContext = createContext('light');

// 2. 顶层组件:提供共享状态(主题切换)
function App() {
  const [theme, setTheme] = useState('light');

  // 切换主题的方法
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  // 用Provider包裹子组件,传递共享的theme和toggleTheme
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <div style={light' ? '#fff' : '#333',
        color: theme === 'light' ? '#333' : '#fff',
        minHeight: '100vh'
      }}>
        <Header />
        <Content />
      </ThemeContext.Provider>
  );
}

// 3. 子组件Header:使用useContext获取共享主题
function Header() {
  const { theme } = useContext(ThemeContext);
  return 当前主题:{theme};
}

// 4. 子组件Content:使用useContext获取共享方法,切换主题
function Content() {
  const { toggleTheme } = useContext(ThemeContext);
  return <button onClick={切换主题;
}

适用场景

跨组件状态共享:主题切换、用户登录状态、语言切换、全局配置(如接口基础地址)等。

注意事项

  • 必须有Provider包裹:如果子组件使用useContext,但没有被对应的Context.Provider包裹,会获取到Context的默认值(注意:默认值仅在没有Provider时生效,不是 fallback 值)。
  • 状态更新会触发所有使用该Context的组件重新渲染:如果共享状态频繁更新,可能导致性能问题,可配合useMemo优化。
  • Context嵌套:多个Context可以嵌套使用,子组件会获取最近的Provider提供的值。
  • TypeScript适配:创建Context时,建议指定类型,避免类型错误(如createContext<{ theme: string; toggleTheme: () => void } | undefined>(undefined))。

六、性能优化Hooks(避免不必要渲染)

React组件默认会在父组件渲染时重新渲染,这类Hook用于优化性能,避免不必要的渲染,提升组件运行效率。

1. useMemo ------ 缓存计算结果(高频)

核心作用

缓存「昂贵计算的结果」,仅当依赖数组中的值发生变化时,才重新计算结果;否则直接返回缓存的结果,避免每次渲染都重复执行昂贵计算,提升性能。

语法

scss 复制代码
// compute:需要缓存结果的计算函数(返回计算结果)
// dependencies:依赖数组,仅当依赖变化时,重新执行compute
const memoizedValue = useMemo(compute, dependencies);

实战示例

scss 复制代码
import { useMemo, useState } from 'react';

// 示例:缓存昂贵计算结果(如数组排序、过滤)
function ExpensiveCompute() {
  const [list, setList] = useState([3, 1, 4, 1, 5, 9, 2, 6]);
  const [count, setCount] = useState(0);

  // 昂贵计算:数组排序(假设数组很大,排序耗时)
  // useMemo缓存排序结果,仅当list变化时重新排序
  const sortedList = useMemo(() => {
    console.log('重新排序'); // 仅list变化时打印
    return [...list].sort((a, b) => a - b);
  }, [list]); // 依赖list,count变化不重新计算

  return (
   排序后:{sortedList.join(',')}<button onClick={ setCount(prev => prev + 1)}>计数:{count}<button onClick={ setList([...list, Math.random()])}>添加元素
  );
}

适用场景

有昂贵计算的场景:数组排序、过滤、复杂数据处理、大量DOM节点渲染前的计算等。

注意事项

  • 不要过度使用:简单计算(如a + b)无需使用useMemo,缓存本身也有性能开销,仅用于昂贵计算。
  • 依赖数组必须完整:compute函数中使用的所有变量、状态、props,都必须加入依赖数组,否则可能返回缓存的旧结果。
  • 返回值是缓存的结果:useMemo返回的是计算结果,不是函数,与useCallback(缓存函数)区分开。

2. useCallback ------ 缓存函数(高频)

核心作用

缓存函数引用,仅当依赖数组中的值发生变化时,才重新创建函数;否则直接返回缓存的函数引用,避免因函数引用变化导致子组件不必要的重新渲染(尤其配合React.memo使用)。

语法

scss 复制代码
// callback:需要缓存的函数
// dependencies:依赖数组,仅当依赖变化时,重新创建函数
const memoizedCallback = useCallback(callback, dependencies);

实战示例

ini 复制代码
import { useCallback, useState, memo } from 'react';

// 子组件:用memo包裹,避免不必要的渲染(仅props变化时渲染)
const Child = memo(({ onClick, name }) => {
  console.log('子组件渲染'); // 仅onClick或name变化时打印
  return <button onClick={{name};
});

// 父组件:用useCallback缓存函数,避免子组件频繁渲染
function Parent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('按钮');

  // 缓存onClick函数,仅当name变化时重新创建函数
  const handleClick = useCallback(() => {
    console.log('点击按钮:', name);
  }, [name]); // 依赖name,count变化不重新创建函数

  return (
    <Child onClick={handleClick} name={name} />
      <button onClick={ setCount(prev => prev + 1)}>计数:{count}<button onClick={ setName('新按钮')}>修改按钮名称
  );
}

适用场景

父组件向子组件传递函数,且子组件用React.memo包裹的场景:避免因父组件渲染导致函数引用变化,进而触发子组件不必要的渲染。

注意事项

  • 配合React.memo使用:useCallback本身不会阻止父组件渲染,需配合React.memo(包裹子组件),才能避免子组件不必要的渲染。
  • 依赖数组必须完整:缓存的函数中使用的所有变量、状态、props,都必须加入依赖数组,否则可能获取到旧值(闭包陷阱)。
  • 与useMemo的区别:useCallback缓存函数引用,useMemo缓存计算结果;useCallback(fn, deps) 等价于 useMemo(() => fn, deps)

3. useMemoizedFn ------ 缓存函数(无依赖,18+新增)

核心作用

React 18 新增(需导入react-dom),用于缓存函数引用,无需依赖数组 ,函数内部能获取到最新的组件状态/ props,避免闭包陷阱,比useCallback更简洁。

语法

javascript 复制代码
import { useMemoizedFn } from 'react-dom';

// callback:需要缓存的函数,无需依赖数组
const memoizedFn = useMemoizedFn(callback);

实战示例

ini 复制代码
import { useState, memo } from 'react';
import { useMemoizedFn } from 'react-dom';

const Child = memo(({ onClick }) => {
  console.log('子组件渲染');
  return <button onClick={点击;
});

function Parent() {
  const [count, setCount] = useState(0);

  // 无需依赖数组,函数内部能获取到最新的count
  const handleClick = useMemoizedFn(() => {
    console.log('当前计数:', count); // 始终获取最新count
  });

  return (
    <Child onClick={handleClick} />
     <button onClick={ setCount(prev => prev + 1)}>计数:{count}
  );
}

适用场景

需要缓存函数,且函数内部依赖组件状态/ props,不想手动维护依赖数组的场景(简化useCallback的使用)。

注意事项

  • 无需依赖数组:函数内部能自动获取最新的组件状态/ props,无需手动添加依赖,避免遗漏依赖导致的闭包陷阱。
  • 需导入react-dom:useMemoizedFn不是从react导入,而是从react-dom导入,注意导入路径。
  • React版本要求:仅支持React 18+,低版本React无法使用。

七、并发模式Hooks(React 18+ 新增,并发渲染)

React 18 引入并发渲染机制,这类Hook用于配合并发模式,控制组件的渲染优先级、中断/恢复渲染,提升用户体验。

1. useTransition ------ 低优先级更新(高频)

核心作用

将某些状态更新标记为「低优先级」,让高优先级更新(如输入框输入、按钮点击)优先执行,避免低优先级更新(如大量数据渲染)阻塞UI,提升用户体验。

语法

scss 复制代码
// startTransition:函数,包裹低优先级更新逻辑
// isPending:布尔值,标记低优先级更新是否正在进行(可用于显示加载状态)
const [isPending, startTransition] = useTransition();

// 使用:将低优先级更新逻辑放入startTransition
startTransition(() => {
  setLowPriorityState(newValue);
});

实战示例

ini 复制代码
import { useTransition, useState } from 'react';

// 示例:输入框搜索(高优先级)+ 搜索结果渲染(低优先级)
function Search() {
  const [searchText, setSearchText] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  // 输入框变化:高优先级更新,优先执行,不阻塞输入
  const handleInputChange = (e) => {
    const value = e.target.value;
    setSearchText(value);

    // 搜索结果渲染:低优先级更新,在高优先级更新完成后执行
    startTransition(() => {
      // 模拟昂贵的搜索逻辑(如过滤大量数据)
      const filteredResults = Array(10000).fill(0).filter((_, index) => 
        index.toString().includes(value)
      );
      setResults(filteredResults);
    });
  };

  return (
    <input 
        type="text" 
        value={ 
        onChange={handleInputChange} 
        placeholder="搜索"
      />
      {/* 低优先级更新正在进行时,显示加载状态 */}
      {isPending && 加载中...}
      
        {results.map((item, index) => (
          <li key={{index}
        ))}
     
  );
}

适用场景

高优先级操作与低优先级操作并存的场景:输入框搜索、下拉框筛选、大量数据渲染、路由切换前的准备工作等。

注意事项

  • 低优先级更新可被中断:如果有更高优先级的更新(如输入框输入),低优先级更新会被中断,等高优先级更新完成后再恢复,避免阻塞UI。
  • 不能用于紧急更新:如表单提交、按钮点击后的状态更新(高优先级),不能用useTransition,否则会导致更新延迟。
  • isPending状态:用于显示低优先级更新的加载状态,提升用户体验,避免用户误以为页面卡死。

2. useDeferredValue ------ 延迟更新值(进阶)

核心作用

useTransition类似,用于延迟更新某个值,将其标记为低优先级,优先保证高优先级更新的响应速度;区别在于useDeferredValue是"延迟值",useTransition是"延迟更新逻辑"。

语法

scss 复制代码
// value:需要延迟的原始值
// options:可选配置,如timeoutMs(延迟时间)
const deferredValue = useDeferredValue(value, options?);

实战示例

ini 复制代码
import { useDeferredValue, useState } from 'react';

// 示例:输入框(高优先级)+ 延迟显示输入值(低优先级)
function DeferredDemo() {
  const [inputValue, setInputValue] = useState('');
  // 延迟更新inputValue,优先保证输入框响应
  const deferredInput = useDeferredValue(inputValue);

  return (
    <input 
        type="text" 
        value={        onChange={(e) => setInputValue(e.target.value)}
        placeholder="输入内容"
      />
      {/* 延迟显示输入值,避免输入时卡顿 */}
      延迟显示:{deferredInput}
  );
}

适用场景

需要延迟显示某个值,优先保证高优先级操作的场景:输入框实时显示、大量数据渲染时的延迟更新等,与useTransition互补。

注意事项

  • 与useTransition的区别:useDeferredValue是延迟"值",useTransition是延迟"更新逻辑";useDeferredValue更适合简单的"值延迟"场景,useTransition更适合复杂的"更新逻辑延迟"场景。
  • 延迟值是只读的:deferredValue是延迟后的只读值,不能直接修改,修改需通过原始值(如inputValue)。

八、工具类Hooks(辅助开发,低频但实用)

这类Hooks用于辅助开发,解决特定场景的问题,使用频率较低,但需要时能大幅简化代码。

1. useDebugValue ------ 自定义Hook调试(调试用)

核心作用

用于自定义Hook中,在React开发者工具中显示自定义Hook的调试信息,方便调试,不影响组件功能。

语法

scss 复制代码
// label:调试信息(字符串或函数,函数返回调试信息)
useDebugValue(label);

// 示例:自定义Hook中使用
function useCustomHook() {
  // 自定义Hook逻辑
  useDebugValue('自定义Hook调试信息');
  return ...;
}

适用场景

开发自定义Hook时,用于调试,方便在React开发者工具中查看自定义Hook的状态和信息。

注意事项

  • 仅用于调试:生产环境中,useDebugValue会被自动移除,不会影响性能。
  • 适合复杂自定义Hook:简单自定义Hook无需使用,复杂自定义Hook(多状态、多逻辑)使用,提升调试效率。

2. useId ------ 生成唯一ID(高频)

核心作用

生成跨渲染、跨组件的唯一ID,用于表单标签关联(labelfor属性与inputid属性)、 accessibility(无障碍)开发,避免ID重复。

语法

ini 复制代码
// 生成唯一ID
const id = useId();

// 生成带前缀的唯一ID(多个ID关联时使用)
const id = useId();
const inputId = `${id}-input`;
const labelId = `${id}-label`;

实战示例

javascript 复制代码
import { useId } from 'react';

// 示例1:表单标签关联,生成唯一ID
function FormInput() {
  // 生成唯一ID
  const id = useId();

  return (

      {/* label
相关推荐
snow_yan4 小时前
AI 对话流式输出: 实现“逐字丝滑、不闪烁、不卡顿”的打字机效果
前端·react.js·openai
鬣主任8 小时前
重生之我上班学React----360档案篇。
javascript·react.js
Ruihong8 小时前
一文看懂:Vue3 watch 用 VuReact 转成 React 长啥样
vue.js·react.js
软弹8 小时前
快速了解前端中的跨域问题
前端·javascript·vue.js·react.js·node.js·跨域
RePeaT8 小时前
React 常用知识点整理
前端·react.js·面试
Ruihong8 小时前
一文看懂:Vue3 watchEffect 用 VuReact 转成 React 长啥样
vue.js·react.js
有意义9 小时前
【面试复盘】前端底层原理与 React 核心机制深度梳理
前端·react.js·面试
San309 小时前
前端渲染:从 CSR、SSR 到同构与手写 Vite+React SSR 实践
react.js·前端框架·浏览器
Lee川9 小时前
React Router 实战指南:构建现代化前端路由系统
前端·react.js·架构