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
相关推荐
空中海7 小时前
01 React Native 基础、核心组件与布局体系
javascript·react native·react.js
空中海7 小时前
05 React架构设计、项目实践与专家清单
前端·react.js·前端框架
空中海10 小时前
04 工程化、质量体系与 React 生态
前端·ubuntu·react.js
空中海10 小时前
03 性能、动画与 React Native 新架构
react native·react.js·架构
空中海11 小时前
02 React Native状态、导航、数据流与设备能力
javascript·react native·react.js
空中海12 小时前
04 React Native工程化、质量、发布与生态选型
javascript·react native·react.js
郑生zs15 小时前
Hooks-useEffect
react.js
光影少年15 小时前
react函数组件、类组件、纯组件、受控/非受控组件
前端·react.js·掘金·金石计划
空中海16 小时前
05 React Native架构设计、主线项目与专家实践
javascript·react native·react.js
killerbasd1 天前
还是迷茫 5.3
前端·react.js·前端框架