速通React之Hooks的实现

 更新时间:2026年06月17日 09:26:18   作者:林希_Rachel_傻希希  
本文系统梳理 React 中 8 个最常用的核心 Hooks:useState、useReducer、useContext、useEffect、useRef、useImperativeHandle、useMemo、useCallback,下面就一起来了解一下

本文系统梳理 React 中 8 个最常用的核心 Hooks:useState、useReducer、useContext、useEffect、useRef、useImperativeHandle、useMemo、useCallback。从底层运行机制到高阶实战用法,配合完整可运行的代码块,助你彻底掌握函数式组件的开发精髓。

前言

React 16.8 正式引入 Hooks,使得函数组件拥有了类组件的全部能力(状态、生命周期、上下文等),同时摒弃了类组件中 this 指向混乱、逻辑复用困难(HOC / Render Props)等问题。Hooks 的本质是将组件状态逻辑与视图逻辑进行解耦,其底层依赖 React 内部的 Fiber 架构,通过链表(memoizedState)来按顺序存储各个 Hook 的状态值。

核心铁律(必须遵守)

  1. 只在 React 函数组件或自定义 Hook 中调用 Hooks。
  2. 只在顶层调用 Hooks(不在循环、条件或嵌套函数中),以保证 Hooks 的调用顺序在每次渲染中一致。

1. useState —— 响应式状态的基石

原理

useState 是 React 内部状态管理的最小单元。在 Fiber 节点上,状态以单向链表的形式存储。每次渲染时,React 根据 Hook 的调用顺序依次从链表上读取对应的状态值。setState 会触发组件重新渲染,并生成新的 Fiber 树。

作用

为函数组件添加内部状态,状态改变时 UI 自动更新。

语法

const [state, setState] = useState(initialState);
  • 支持函数式更新:setState(prev => prev + 1) 可避免因闭包导致的状态过期问题。

示例

import { useState } from 'react';

function UserForm() {
  const [username, setUsername] = useState('');
  const [age, setAge] = useState(18);

  const handleSubmit = () => {
    alert(`用户名:${username},年龄:${age}`);
  };

  return (
    <div>
      <input 
        value={username} 
        onChange={(e) => setUsername(e.target.value)} 
        placeholder="请输入用户名" 
      />
      <input 
        type="number" 
        value={age} 
        onChange={(e) => setAge(Number(e.target.value))} 
      />
      <button onClick={handleSubmit}>提交</button>
    </div>
  );
}

2. useReducer —— 复杂状态逻辑的治理专家

原理

与 useState 共享底层状态存储机制,但将状态更新逻辑集中到 reducer 纯函数中。通过 dispatch 派发 actionreducer 根据 action.type 计算出新状态。这种模式源自 Redux,让状态变化可预测、可追踪

作用

  • 当状态更新逻辑包含多个子值或复杂转换(如购物车、表单校验)时,比 useState 更具可读性。
  • 适合状态更新依赖先前状态,且操作类型多样(增、删、改、重置)的场景。

语法

const [state, dispatch] = useReducer(reducer, initialArg, init);

示例:购物车数量管理

import { useReducer } from 'react';

// 1. 定义 reducer
function cartReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return { ...state, count: state.count + 1 };
    case 'SUBTRACT':
      return { ...state, count: Math.max(0, state.count - 1) };
    case 'RESET':
      return { ...state, count: 0 };
    default:
      return state;
  }
}

function ShoppingCart() {
  const [state, dispatch] = useReducer(cartReducer, { count: 0 });

  return (
    <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
      <button onClick={() => dispatch({ type: 'SUBTRACT' })}>-</button>
      <span>数量:{state.count}</span>
      <button onClick={() => dispatch({ type: 'ADD' })}>+</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>重置</button>
    </div>
  );
}

3. useContext —— 跨层级数据传递的直通车

原理

useContext 基于 React 的 Context API。当上层组件通过 <Provider value={...}> 提供数据时,React 会在 Fiber 节点上维护一个 context 链。下层调用 useContext 会向上查找最近的 Provider,并订阅其值。当 Provider 的 value 变化时,所有订阅该 Context 的组件会触发更新。

作用

彻底解决 Props Drilling(属性钻取)问题,让数据在组件树中无障碍传递,常用于主题、语言环境、用户鉴权信息等全局数据。

语法

const MyContext = React.createContext(defaultValue);
const value = useContext(MyContext);

示例:全局主题切换

import { createContext, useContext, useState } from 'react';

// 1. 创建 Context
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });

// 2. 提供者组件
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const toggleTheme = () => {
    setTheme((t) => (t === 'light' ? 'dark' : 'light'));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. 深层子组件消费
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);
  const isDark = theme === 'dark';

  return (
    <button
      onClick={toggleTheme}
      style={{
        background: isDark ? '#333' : '#fff',
        color: isDark ? '#fff' : '#000',
        padding: '8px 16px',
        border: '1px solid #ccc',
        cursor: 'pointer',
      }}
    >
      当前主题:{theme},点击切换
    </button>
  );
}

function Toolbar() {
  return <ThemedButton />; // 中间组件无需透传任何 props
}

// 4. 使用
export default function App() {
  return (
    <ThemeProvider>
      <Toolbar />
    </ThemeProvider>
  );
}

4. useEffect —— 副作用与生命周期的统一管理

原理

useEffect 的调度基于 Fiber 的 Effect List。在浏览器完成布局与绘制之后,React 会异步执行 Effect 函数(默认)。通过依赖数组,React 使用 Object.is 比较前后依赖项,决定是否跳过执行。清理函数会在组件卸载或下一次 Effect 执行之前调用,用于清除定时器、取消订阅等。

作用

  • 替代类组件生命周期:componentDidMount(空依赖)、componentDidUpdate(指定依赖)、componentWillUnmount(清理函数)。
  • 执行数据获取、手动 DOM 操作、订阅事件等副作用。

语法

useEffect(() => {
  // 副作用逻辑
  return () => {
    // 清理逻辑(可选)
  };
}, [dependencies]);

示例:获取用户数据并设置定时器

import { useState, useEffect } from 'react';

function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let isMounted = true; // 防止组件卸载后依然 setState

    // 模拟异步请求
    const fetchData = async () => {
      try {
        const response = await new Promise((resolve) =>
          setTimeout(() => resolve({ name: '王五', age: 30 }), 2000)
        );
        if (isMounted) {
          setUser(response);
          setLoading(false);
        }
      } catch (error) {
        if (isMounted) setLoading(false);
      }
    };

    fetchData();

    // 清理函数:组件卸载时取消挂载标记
    return () => {
      isMounted = false;
    };
  }, []); // 仅在挂载时执行一次

  if (loading) return <p>加载中...</p>;
  return (
    <div>
      <h2>用户信息</h2>
      <p>姓名:{user.name}</p>
      <p>年龄:{user.age}</p>
    </div>
  );
}

5. useRef —— 存储可变数据与 DOM 引用

原理

useRef 返回一个 { current: initialValue } 对象。该对象在组件的整个生命周期内内存地址恒定(类似类组件的实例属性)。修改 current 不会触发组件重新渲染,这使得它非常适合存储“不影响视图”的可变数据。

作用

  • 获取 DOM 元素的引用(如聚焦、测量尺寸)。
  • 存储上一次渲染的值、定时器 ID、WebSocket 实例等跨渲染周期数据。

语法

const refContainer = useRef(initialValue);
// 使用:refContainer.current

示例一:自动聚焦输入框

import { useRef, useEffect } from 'react';

function AutoFocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} placeholder="页面加载后自动聚焦" />;
}

示例二:保存计时器 ID 并清除

import { useRef, useState } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const timerIdRef = useRef(null);

  const start = () => {
    if (timerIdRef.current) return; // 防止重复启动
    timerIdRef.current = setInterval(() => {
      setSeconds((prev) => prev + 1);
    }, 1000);
  };

  const stop = () => {
    clearInterval(timerIdRef.current);
    timerIdRef.current = null;
  };

  return (
    <div>
      <p>计时:{seconds} 秒</p>
      <button onClick={start}>开始</button>
      <button onClick={stop}>停止</button>
    </div>
  );
}

6. useImperativeHandle —— 封装子组件的命令式接口

原理

useImperativeHandle 必须与 forwardRef 配合。父组件传递的 ref 对象会被子组件接收,useImperativeHandle 允许子组件自定义挂载到 ref.current 上的内容(通常是一个方法对象)。这通过劫持 ref 的映射过程实现,防止父组件直接操作子组件的 DOM 或内部状态,增强封装性。

作用

  • 暴露特定方法给父组件(如 focusresetvalidate)。
  • 控制父组件对子组件的访问权限,避免破坏子组件的内部逻辑。

语法

useImperativeHandle(ref, () => ({
  // 返回暴露的方法
}), [dependencies]);

示例:父组件调用子组件表单重置与聚焦

import { forwardRef, useImperativeHandle, useRef, useState } from 'react';

// 子组件
const CustomInput = forwardRef((props, ref) => {
  const [value, setValue] = useState('');
  const inputRef = useRef(null);

  // 暴露方法给父组件
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    reset: () => {
      setValue('');
      inputRef.current.focus();
    },
    getValue: () => value,
  }));

  return (
    <input
      ref={inputRef}
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="子组件输入框"
    />
  );
});

// 父组件
function ParentForm() {
  const childRef = useRef(null);

  const handleReset = () => {
    childRef.current.reset();
  };

  const handleGetValue = () => {
    alert(childRef.current.getValue());
  };

  return (
    <div>
      <CustomInput ref={childRef} />
      <button onClick={handleReset}>重置并聚焦</button>
      <button onClick={handleGetValue}>获取子组件值</button>
    </div>
  );
}

7. useMemo —— 记忆化计算结果,提升渲染性能

原理

useMemo 利用闭包存储上一次的返回值。在渲染阶段,React 会对比依赖项数组,若依赖未变,直接返回缓存的旧值,跳过函数执行。其缓存挂载在 Fiber 节点的 memoizedState 上,属于渲染期间的计算优化。

作用

  • 缓存高开销的计算(如大数据过滤、复杂数学运算)。
  • 保持引用类型的稳定性(如对象、数组),避免因每次渲染重新创建而导致子组件(React.memo)无效重绘。

语法

const memoizedValue = useMemo(() => compute(a, b), [a, b]);

示例:大数据过滤与输入框丝滑交互

import { useState, useMemo } from 'react';

function FilterList() {
  const [keyword, setKeyword] = useState('');
  const [count, setCount] = useState(0);

  // 模拟一个庞大的静态数据列表
  const allItems = useMemo(() => {
    const items = [];
    for (let i = 0; i < 20000; i++) {
      items.push(`商品-${i}`);
    }
    return items;
  }, []); // 只在首次渲染生成一次

  // 根据关键词过滤(仅当 keyword 或 allItems 变化时重新计算)
  const filteredItems = useMemo(() => {
    console.log('执行过滤计算...');
    if (!keyword) return allItems;
    return allItems.filter((item) => item.includes(keyword));
  }, [keyword, allItems]);

  return (
    <div>
      <input
        value={keyword}
        onChange={(e) => setKeyword(e.target.value)}
        placeholder="输入关键词过滤(体验丝滑输入)"
      />
      <button onClick={() => setCount((c) => c + 1)}>重渲染计数器:{count}</button>
      <ul style={{ height: '300px', overflow: 'auto', border: '1px solid #ccc' }}>
        {filteredItems.slice(0, 100).map((item) => (
          <li key={item}>{item}</li>
        ))}
        {filteredItems.length > 100 && <li>...仅显示前100条</li>}
      </ul>
    </div>
  );
}

点击计数器改变 count 时,组件重渲染,但 filteredItems 因依赖未变直接命中缓存,列表不会重新过滤,输入框交互依然丝滑。

8. useCallback —— 记忆化函数引用,优化子组件渲染

原理

useCallback 本质是 useMemo(() => fn, deps) 的语法糖,返回的是一个记忆化的函数。当依赖项不变时,函数的引用地址保持不变。

作用

  • 当父组件重渲染时,传递给 React.memo 子组件的回调函数引用不变,子组件得以跳过渲染。
  • 作为 useEffect 的依赖项时,避免因函数重新创建而导致 Effect 频繁执行。

语法

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

示例:配合 React.memo 避免子组件无效重绘

import { useState, useCallback, memo } from 'react';

// 子组件用 memo 包裹,进行浅层 props 比较
const Child = memo(({ onAdd, label }) => {
  console.log(`Child ${label} 渲染了`);
  return <button onClick={onAdd}>增加 {label}</button>;
});

function Parent() {
  const [countA, setCountA] = useState(0);
  const [countB, setCountB] = useState(0);
  const [text, setText] = useState('');

  // 使用 useCallback 缓存,依赖为空,函数永远不变
  const handleAddA = useCallback(() => {
    setCountA((prev) => prev + 1);
  }, []);

  // 此处故意不使用 useCallback,每次渲染都会创建新函数
  const handleAddB = () => {
    setCountB((prev) => prev + 1);
  };

  return (
    <div>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="在此输入,观察子组件渲染情况"
      />
      <p>Count A: {countA}</p>
      <p>Count B: {countB}</p>
      <Child onAdd={handleAddA} label="A (优化)" />
      <Child onAdd={handleAddB} label="B (未优化)" />
    </div>
  );
}

当在输入框中打字时,父组件重渲染。Child A 因 handleAddA 引用不变,不会重新渲染;Child B 因每次接收新的 handleAddB 函数,导致 props 变化,触发不必要的重渲染。

扩展补充:React.memo(非 Hook,但密切配合)

React.memo 是一个高阶组件(HOC),用于缓存组件。它对 props 进行浅层比较,若 props 未变则跳过本次渲染。常与 useCallback 和 useMemo 组成性能优化的“铁三角”。

const MyComponent = memo(function MyComponent(props) {
  // 仅当 props 改变时才重新渲染
});

完整总结对照表

Hook 名称核心职责典型应用场景
useState管理简单局部响应式状态表单输入、开关、计数器
useReducer管理含有复杂逻辑的局部状态购物车、具有多种操作类型的对象状态
useContext跨组件树共享数据,避免 Props 透传主题、语言、用户认证信息
useEffect执行副作用与生命周期管理数据请求、DOM 操作、订阅与清理
useRef存储可变数据(不触发渲染)及 DOM 引用获取 DOM 节点、保存定时器 ID、记录上一次值
useImperativeHandle自定义暴露给父组件的实例方法封装表单组件、暴露 focus / reset 方法
useMemo记忆化计算结果,缓存对象/数组引用大数据计算、稳定子组件 props
useCallback记忆化函数引用,配合 memo 优化传递给子组件的回调函数

进阶提示(并非全部,但值得了解)

除了上述 8 个核心 Hook,React 还提供了:

  • useLayoutEffect:与 useEffect 类似,但在 DOM 更新后、浏览器绘制前同步执行,适合处理 DOM 尺寸测量等需要避免闪烁的操作。
  • useDebugValue:在 React DevTools 中为自定义 Hook 显示标签,方便调试。
  • useId:生成稳定且唯一的 ID,用于 SSR 场景下的无障碍属性(如 aria-describedby)。
  • useTransition / useDeferredValue:用于并发模式下的 UI 更新降级与渲染优化。

到此这篇关于速通React之Hooks的实现的文章就介绍到这了,更多相关React Hooks内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 在React中与后端API进行交互的操作步骤

    在React中与后端API进行交互的操作步骤

    在现代Web开发中,前后端分离的架构已经成为一种趋势,React,作为一种流行的前端库,常常与后端API进行交互,以实现动态数据更新和用户体验优化,本文将深入探讨如何在React应用中与后端API进行交互,需要的朋友可以参考下
    2025-02-02
  • React报错之Parameter event implicitly has an any type解决

    React报错之Parameter event implicitly has a

    这篇文章主要为大家介绍了React报错之Parameter event implicitly has an any type,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • 详解如何在Remix 中使用 tailwindcss

    详解如何在Remix 中使用 tailwindcss

    这篇文章主要为大家介绍了如何在Remix中使用tailwindcss方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • 详解基于React.js和Node.js的SSR实现方案

    详解基于React.js和Node.js的SSR实现方案

    这篇文章主要介绍了详解基于React.js和Node.js的SSR实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • react 国际化的实现代码示例

    react 国际化的实现代码示例

    这篇文章主要介绍了react 国际化的实现代码示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • React实现锚点跳转组件附带吸顶效果的示例代码

    React实现锚点跳转组件附带吸顶效果的示例代码

    这篇文章主要为大家详细介绍了React如何实现移动端锚点跳转组件附带吸顶效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-01-01
  • 在 React Native 中给第三方库打补丁的过程解析

    在 React Native 中给第三方库打补丁的过程解析

    这篇文章主要介绍了在 React Native 中给第三方库打补丁的过程解析,有时使用了某个React Native 第三方库,可是它有些问题,我们不得不修改它的源码,本文介绍如何修改源码又不会意外丢失修改结果的方法,需要的朋友可以参考下
    2022-08-08
  • 详解React Hooks是如何工作的

    详解React Hooks是如何工作的

    React Hooks是在React 16.8版本新增的特性,在我看了React官网和一些博客对React Hook的讲解后还是觉得没有get到本质。本篇博客通过手动实现useState()来了解Hook的原理和本质。阅读此篇博客的前提是你要知道一些 React Hooks的基本用法和使用规则,不然会看得云里雾里。
    2021-05-05
  • React Hooks使用startTransition与useTransition教程示例

    React Hooks使用startTransition与useTransition教程示例

    这篇文章主要为大家介绍了React Hooks使用startTransition与useTransition教程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • Redis数据结构面试高频问题解析

    Redis数据结构面试高频问题解析

    这篇文章主要为大家介绍了Redis数据结构高频面试问题解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06

最新评论