1. React HooksReact 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性;

  2. Hooks 允许我们在函数组件中管理状态、副作用、上下文等,消除了使用类组件的复杂性;​

常用的 React Hooks

useState

  1. useStateReact 提供的最基本的 Hook,用于在函数组件中添加状态管理;它返回一个状态变量和一个更新状态的函数;

  2. 使用场景​

    1. 适合管理简单的状态;
    2. 适合管理组件内部的局部状态;
  3. 示例:

    import { createRoot } from "react-dom/client";
    import { useState } from 'react';
    
    function Counter() {
      // 声明一个 state 变量 count 和更新函数 setCount
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>Click me</button>
          {/* 更新函数可以接受函数参数来基于先前的 state 计算新 state */}
          <button onClick={() => setCount(prevCount => prevCount + 1)}>Click me</button>
        </div>
      );
    }
    
    createRoot(document.getElementById("root")).render(<Counter />);
    

useReducer

  1. useReduceruseState 的替代方案,适合用于管理更复杂的状态逻辑;它通过 reducer 函数来管理状态,类似于 Redux;​

  2. 如果组件内部状态足够多,那么状态会逐渐趋于复杂,这时,需要更好的编程范式来解决状态存储与更新;react 单向数据流告诉了我们,状态的管理需要注意以下几点:​

    1. 使用一个对象存储变量 state​
    2. 订阅模式实现对于该对象的变更响应处理 reducer​
    3. 定义更改对象变更的动作 action​
    4. 订阅该对象的变更,完成状态到视图的映射 ui = fx(state)
  3. 用一句话来概括:状态由 useReducer 借助 reducer 生发,状态的变更由 dispach 发起,最终状态变更驱动视图更新;

  4. 使用场景​:

    1. 适合管理复杂的状态逻辑;
    2. 状态更新依赖于先前状态;
  5. 示例代码

    import { createRoot } from "react-dom/client";
    import { useReducer } from 'react';
    
    // 定义 reducer 函数
    function reducer(state, action) {
      switch (action.type) {
        case 'increment':
          return { count: state.count + 1 };
        case 'decrement':
          return { count: state.count - 1 };
        default:
          throw new Error();
      }
    }
    
    function Counter() {
      // 使用 useReducer
      const [state, dispatch] = useReducer(reducer, { count: 0 });
    
      return (
        <>
          Count: {state.count}
          <button onClick={() => dispatch({ type: 'increment' })}>+</button>
          <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
        </>
      );
    }
    
    createRoot(document.getElementById("root")).render(<Counter />);
    

useContext

  1. useContext 用于共享全局状态,避免了通过多层组件传递 props;它通过 Context 对象提供全局状态管理;​2. 使用场景​:

    1. 适合管理跨组件树的全局状态;
    2. 避免多层组件传递 props
  2. 示例:

    import { createRoot } from "react-dom/client";
    import { createContext, useContext } from 'react';
    
    // 1. 创建 Context
    const ThemeContext = createContext('light');
    
    function App() {
      // 2. 提供 Context 值
      return (
        <ThemeContext.Provider value="light">
          <Toolbar />
        </ThemeContext.Provider>
      );
    };
    
    function Toolbar() {
      return <ThemedButton />;
    };
    
    function ThemedButton() {
      // 3. 使用 Context
      const theme = useContext(ThemeContext);
      return <button style={{ background: theme === 'dark' ? '#333' : '#eee' }}>Themed Button</button>;
    };
    
    createRoot(document.getElementById("root")).render(<App />);
    

useEffect

  1. useEffect 用于在函数组件中执行副作用操作,例如数据获取、订阅或手动更改 DOMuseEffect 会在组件渲染后执行;

  2. 用法:

    import { createRoot } from "react-dom/client";
    import { useState, useEffect } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      // 相当于 componentDidMount 和 componentDidUpdate
      useEffect(() => {
        // 使用浏览器 API 更新文档标题
        document.title = `You clicked ${count} times`;
      });
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
    
    createRoot(document.getElementById("root")).render(<Example />);
    
  3. 依赖数组控制执行时机

    // 只在组件挂载时执行(相当于 componentDidMount)
    useEffect(() => {
      console.log('Component mounted');
    }, []);
    
    // 只在 count 变化时执行
    useEffect(() => {
      console.log('Count changed:', count);
    }, [count]);
    
  4. 清理副作用

    useEffect(() => {
      const timer = setInterval(() => {
        console.log('Timer tick');
      }, 1000);
    
      return () => {
        // 相当于 componentWillUnmount
        clearInterval(timer);
      };
    }, []);
    

useRef

  1. useRef 返回一个可变的 ref 对象,该对象的 .current 属性被初始化为传入的参数 (initialValue)useRef 常用于访问 DOM 元素直接或存储某个可变值,该值在组件的整个生命周期内保持不变;

  2. 用法:

    JSX
    JSX
    import { createRoot } from "react-dom/client";
    import { useRef, useEffect } from 'react';
    
    function TextInputWithFocusButton() {
      const inputEl = useRef(null);
    
      const onButtonClick = () => {
        // `current` 指向已挂载的 input 元素
        inputEl.current.focus();
      };
    
      return (
        <>
          <input ref={inputEl} type="text" />
          <button onClick={onButtonClick}>Focus the input</button>
        </>
      );
    }
    
    createRoot(document.getElementById("root")).render(<TextInputWithFocusButton />);
    
    import { createRoot } from "react-dom/client";
    import { useRef, useEffect } from 'react';
    
    // 保存可变值(不会触发重新渲染)
    function TimerComponent() {
      const intervalRef = useRef();
    
      useEffect(() => {
        intervalRef.current = setInterval(() => {
          console.log('Timer tick');
        }, 1000);
    
        return () => {
          clearInterval(intervalRef.current);
        };
      }, []);
    
      return <div>Check console for timer logs</div>;
    }
    
    createRoot(document.getElementById("root")).render(<TimerComponent />);
    

useMemo

  1. useMemo 用于缓存计算结果,避免在每次渲染时都进行昂贵的计算;

  2. 昂贵的计算:

    function ExpensiveComponent({ list }) {
      // 只有 list 变化时才重新计算排序结果,list 不变重新渲染的时候也不会重新计算
      const sortedList = useMemo(() => {
        console.log('Sorting...');
        return [...list].sort((a, b) => a.value - b.value);
      }, [list]);
    
      return <div>{sortedList.map(item => <div key={item.id}>{item.value}</div>)}</div>;
    }
    
  3. 避免不必要的重新渲染:

    function Parent({ a }) {
      // 只有 a 变化时才重新创建对象,a不变重新渲染的时候也不会重新计算
      const childProps = useMemo(() => ({ value: a }), [a]);
      
      return <Child data={childProps} />;
    }
    
    // Child 组件使用 React.memo 避免不必要的渲染
    const Child = React.memo(function Child({ data }) {
      console.log('Child rendered');
      return <div>{data.value}</div>;
    });
    
  4. 引用类型的依赖项:

    function Component({ items }) {
      // 避免每次渲染都创建新数组
      const itemIds = useMemo(() => items.map(item => item.id), [items]);
      
      useEffect(() => {
        console.log('Item IDs changed', itemIds);
      }, [itemIds]); // 只有当 items 实际变化时才会触发
    }
    

useCallback

  1. useCallback 用于缓存函数引用,避免在每次渲染时都创建新的函数;

  2. 优化子组件渲染(配合 React.memo):

    function Parent() {
      const [count, setCount] = useState(0);
      
      // 缓存回调函数,避免每次渲染都创建新函数
      const increment = useCallback(() => {
        setCount(c => c + 1);
      }, []); // 依赖数组为空,函数永远不会改变
      
      return (
        <div>
          <Child onClick={increment} />
          <div>Count: {count}</div>
        </div>
      );
    }
    
    // 使用 React.memo 避免不必要的子组件渲染
    const Child = React.memo(function Child({ onClick }) {
      console.log('Child rendered');
      return <button onClick={onClick}>Increment</button>;
    });
    
  3. 作为其他 Hook 的依赖项:

    function Example() {
      const [data, setData] = useState(null);
      
      // 缓存 fetch 函数
      const fetchData = useCallback(async () => {
        const response = await fetch('/api/data');
        setData(await response.json());
      }, []); // 依赖数组为空,函数永远不会改变
      
      // 只有当 fetchData 变化时才重新创建 effect
      useEffect(() => {
        fetchData();
      }, [fetchData]);
      
      return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
    }
    
  4. 事件处理函数需要依赖当前状态:

    function Timer() {
      const [count, setCount] = useState(0);
      
      // 使用 useCallback 确保回调函数能访问到最新的 count
      const startTimer = useCallback(() => {
        const timer = setInterval(() => {
          setCount(c => c + 1); // 使用函数式更新
        }, 1000);
        
        return () => clearInterval(timer);
      }, []);
      
      useEffect(() => {
        const cleanup = startTimer();
        return cleanup;
      }, [startTimer]);
      
      return <div>Count: {count}</div>;
    }
    
  5. 不适用场景:

    1. 函数不需要作为 props 传递给子组件
    2. 函数不用于依赖数组
    3. 函数创建开销很小

useLayoutEffect

  1. useLayoutEffectuseEffect 类似,但它在 DOM 更新后、浏览器绘制前同步执行,适用于需要直接操作 DOM 或测量 DOM 元素的场景;

  2. 谨慎使用:过度使用可能导致性能问题,因为它会阻塞渲染;

  3. 用法:

    import { createRoot } from "react-dom/client";
    import { useLayoutEffect, useRef } from 'react';
    
    function LayoutEffectExample() {
      const ref = useRef(null);
    
      useLayoutEffect(() => {
        console.log('useLayoutEffect');
        ref.current.style.backgroundColor = 'yellow';
      });
    
      return <div ref={ref}>Layout Effect</div>;
    }
    
    createRoot(document.getElementById("root")).render(<LayoutEffectExample />);
    
  4. useEffect 的区别:

    特性 useLayoutEffect useEffect
    执行时机 DOM 更新后,绘制前 绘制后
    阻塞性 同步执行,阻塞绘制 异步执行,不阻塞绘制
    适用场景 DOM 测量/操作 数据获取/订阅
    SSR 警告

useImperativeHandle

  1. useImperativeHandle 用于自定义通过 ref 暴露给父组件的实例值,通常与 forwardRef 配合使用;

  2. 用法:

    import { createRoot } from "react-dom/client";
    import { useRef, forwardRef, useImperativeHandle } from 'react';
    
    const FancyInput = forwardRef((props, ref) => {
      const inputRef = useRef();
    
      // 控制子组件向父组件暴露哪些方法和属性
      useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus();
        }
      }));
    
      return <input ref={inputRef} />;
    });
    
    function App() {
      const inputRef = useRef();
      return (
        <div>
          <FancyInput ref={inputRef} />
          <button onClick={() => inputRef.current.focus()}>Focus the input</button>
        </div>
      );
    }
    
    createRoot(document.getElementById("root")).render(<App />);
    

useId

  1. useId 是一个用于生成唯一 IDHook,通常用于无障碍特性 (例如绑定 label 和 input)

  2. 用法:

    import { createRoot } from "react-dom/client";
    import React, { useId } from 'react';
    
    function Form() {
      const id = useId();
    
      return (
        <div>
          <label htmlFor={id}>Name</label>
          <input id={id} type="text" />
        </div>
      );
    }
    
    createRoot(document.getElementById("root")).render(<Form />);
    

useTransition

  1. useTransition 是一个用于处理 UI 过渡的 Hook,允许你将某些更新标记为过渡,从而避免阻塞界面;

  2. 用法:

    import { createRoot } from "react-dom/client";
    import { useState, useTransition } from 'react';
    
    function App() {
      const [isPending, startTransition] = useTransition();
      const [count, setCount] = useState(0);
    
      const handleClick = () => {
        startTransition(() => {
          setCount(c => c + 1);
        });
      };
    
      return (
        <div>
          <button onClick={handleClick}>Increment</button>
          {isPending ? 'Loading...' : <p>Count: {count}</p>}
        </div>
      );
    }
    
    createRoot(document.getElementById("root")).render(<App />);
    

useDeferredValue

  1. useDeferredValueReact 19 中用于性能优化的重要 Hook,它允许你延迟更新某些非关键值,从而保持界面的响应性,特别适用于处理高开销的渲染操作;

  2. 用法:

    import { createRoot } from "react-dom/client";
    import { useState, useDeferredValue } from 'react';
    
    function App() {
      const [text, setText] = useState('');
      const deferredText = useDeferredValue(text);
    
      const handleChange = (e) => {
        setText(e.target.value);
      };
    
      return (
        <div>
          <input type="text" value={text} onChange={handleChange} />
          <p>{deferredText}</p>
        </div>
      );
    }
    
    createRoot(document.getElementById("root")).render(<App />);
    
  3. 典型使用场景:

    1. 搜索/筛选大型列表
    2. 图表数据渲染

useSyncExternalStore

  1. useSyncExternalStoreReact 19 中用于安全地订阅外部数据源的 Hook,它专门设计用来解决外部状态管理与 React 集成时的常见问题;

  2. 基本语法

    const state = useSyncExternalStore(
      subscribe,  // 接收一个回调函数,并返回取消订阅的函数
      getSnapshot, // 返回当前存储的快照值
      getServerSnapshot? // 服务端渲染时使用的快照
    );
    
  3. 典型使用场景

    1. 集成 Redux 类状态库
      function useReduxStore(store) {
        return useSyncExternalStore(
          store.subscribe,
          () => store.getState()
        );
      }
      
    2. 自定义全局状态管理
      import { createRoot } from "react-dom/client";
      import { useSyncExternalStore } from 'react';
      
      let globalState = { count: 0 };
      const listeners = new Set();
      
      function increment() {
        globalState = { ...globalState, count: globalState.count + 1 };
        listeners.forEach(l => l());
      }
      
      function App() {
        const state = useSyncExternalStore(
          (callback) => {
            listeners.add(callback);
            return () => listeners.delete(callback);
          },
          () => globalState
        );
        return <button onClick={increment}>{state.count}</button>;
      }
      
      createRoot(document.getElementById("root")).render(<App />);
      
    3. 浏览器 API 订阅
      function useOnlineStatus() {
        return useSyncExternalStore(
          callback => {
            window.addEventListener('online', callback);
            window.addEventListener('offline', callback);
            return () => {
              window.removeEventListener('online', callback);
              window.removeEventListener('offline', callback);
            };
          },
          () => navigator.onLine
        );
      }
      
  4. 与其它状态管理方式的对比

    特性 useSyncExternalStore useState/useReducer useContext
    适用场景 外部状态源 组件内部状态 跨组件共享
    性能优化 ✅ 内置优化 ⚠️ 需手动优化 ⚠️ 需注意
    并发模式支持 ✅ 完全支持 ✅ 支持 ✅ 支持
    SSR支持 ✅ 完整支持 ✅ 支持 ✅ 支持

封装自定义 Hook

  1. 封装一个用于管理表单输入状态的自定义 Hook

    import { useState } from "react";
    
    export function useFormInput(initialValue) {
      const [value, setValue] = useState(initialValue);
      const handleChange = (e) => {
        setValue(e.target.value);
      };
      return {
        value,
        onChange: handleChange,
      };
    }
    
  2. 使用自定义 Hook

    import { createRoot } from "react-dom/client";
    import { useFormInput } from './useFormInput';
    
    function Form() {
      const name = useFormInput('');
      const email = useFormInput('');
    
      const handleSubmit = (e) => {
        e.preventDefault();
        alert(`Name: ${name.value}, Email: ${email.value}`);
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <div>
            <label>
              Name:
              <input type="text" {...name} />
            </label>
          </div>
          <div>
            <label>
              Email:
              <input type="email" {...email} />
            </label>
          </div>
          <button type="submit">Submit</button>
        </form>
      );
    }
    
    createRoot(document.getElementById("root")).render(<Form />);
    

深入理解 Hooks 原理

Hooks 的设计哲学

React Hooks 的核心思想是让函数组件拥有类组件的能力,同时解决类组件的几个关键问题:

  1. 状态逻辑复用困难 (render props/HOC 带来的嵌套地狱)
  2. 复杂组件难以理解 (生命周期方法中混杂不相关逻辑)
  3. 类组件的心智负担 (this 绑定、方法属性等)

Hooks 的实现基础

  1. React 内部使用闭包+链表的方式存储 Hooks 状态:

    // 简化的内部实现示意
    let currentHook = null; // 当前处理的Hook
    let workInProgressHook = null; // 正在工作的Hook
    let isMount = true; // 是否是首次渲染
    
    function mountWorkInProgressHook() {
      const hook = {
        memoizedState: null, // 存储状态
        next: null, // 下一个Hook
      };
      
      if (!workInProgressHook) {
        // 第一个Hook
        workInProgressHook = hook;
      } else {
        // 添加到链表末尾
        workInProgressHook.next = hook;
      }
      return hook;
    }
    
  2. Hooks 调用规则的本质:“只在最顶层使用 Hooks” 的规则源于 Hooks 的链表存储方式;

    // 错误示例 - 条件语句会导致 Hook 顺序变化
    if (condition) {
      useState(1); // 第一次渲染可能调用
    }
    useState(2); // 第二次渲染可能变成第一个 Hook
    

Hooks 底层架构

React Hooks 的实现依赖于几个关键部分:

  1. Dispatcher:负责分配当前使用的 Hooks 实现;
    1. 渲染阶段使用 HooksDispatcherOnMount
    2. 更新阶段使用 HooksDispatcherOnUpdate
  2. Fiber 架构:每个组件对应一个 Fiber 节点,存储 Hooks 链表;
  3. 调度系统:协调 Hooks 的更新与渲染时机;

Hooks VS 类组件生命周期

类组件生命周期 Hooks 等效实现
componentDidMount useEffect(fn, [])
componentDidUpdate useEffect(fn, [deps])
componentWillUnmount useEffect(() => fn, [])
shouldComponentUpdate React.memo / useMemo

面试题

react 中 useState 返回的更新函数,接受两种形式的参数有什么区别?

  1. 直接传递新值(如字符串变更)

    1. 特点:
      • 适用于不依赖前一个状态的情况,比如直接替换当前状态;
      • 如果多次调用,React 可能会批量处理更新,导致最终结果不符合预期(特别是在异步环境中);
    2. 示例问题:
      const handleClick = () => {
        setName(name + "A"); // 假设 name 当前是 "Alice"
        setName(name + "B"); // 这里仍然基于旧的 name ("Alice"),而不是 "AliceA"
        // 最终 name 可能是 "AliceB" 而不是 "AliceAB"
      };
      
  2. 传递函数(函数变更)

    1. 特点:
      • 适用于依赖前一个状态的情况,确保获取最新的状态值;
      • 即使多次调用,也能正确基于前一个更新计算新状态(React 会确保按顺序执行);
      • 在异步操作 (如 setTimeout、Promise) 中更安全,避免闭包问题;
    2. 正确示例:
      const handleClick = () => {
        setName(prev => prev + "A"); // 基于前一个状态
        setName(prev => prev + "B"); // 基于前一个更新后的状态
        // 最终 name 会是 "AliceAB"
      };
      
  3. 关键区别总结

    场景 直接传递新值(如字符串) 传递函数(函数变更)
    依赖前一个状态 ❌ 可能出错(闭包问题) ✅ 安全可靠
    多次调用时的行为 可能覆盖更新 按顺序执行更新
    异步环境 可能出错 安全
    适用场景 简单状态更新 复杂或依赖前状态的计算
  4. 何时使用哪种方式?

    1. 直接传递值:当新状态不依赖旧状态时 (如直接替换字符串、布尔值切换)
    2. 传递函数:当新状态依赖旧状态时 (如计数器、字符串拼接、数组/对象更新)
  5. 额外说明:React 的批处理 (Batching)

    1. React 会批量处理多个 setState 调用以提高性能;
    2. React 18 的并发模式下,批处理更加普遍,因此使用函数式更新能避免潜在的问题;
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 常用的 React Hooks
    1. 1.1. useState
    2. 1.2. useReducer
    3. 1.3. useContext
    4. 1.4. useEffect
    5. 1.5. useRef
    6. 1.6. useMemo
    7. 1.7. useCallback
    8. 1.8. useLayoutEffect
    9. 1.9. useImperativeHandle
    10. 1.10. useId
    11. 1.11. useTransition
    12. 1.12. useDeferredValue
    13. 1.13. useSyncExternalStore
  2. 2. 封装自定义 Hook
  3. 3. 深入理解 Hooks 原理
    1. 3.1. Hooks 的设计哲学
    2. 3.2. Hooks 的实现基础
    3. 3.3. Hooks 底层架构
  4. 4. Hooks VS 类组件生命周期
  5. 5. 面试题
    1. 5.1. react 中 useState 返回的更新函数,接受两种形式的参数有什么区别?