前端面试宝典

在 React 中,HookContext 是两个核心特性,用于解决函数组件的状态管理、副作用处理和跨组件数据传递问题,是现代 React 开发的基础。下面用通俗的语言 + 核心用法 + 实际场景,帮你彻底搞懂:

一、先搞懂 Hook:函数组件的“能力扩展器”

1. 什么是 Hook?

Hook 是 React 16.8 新增的函数,专门用于在函数组件中实现原本只有类组件才能做到的事情(比如状态管理、生命周期、 Refs 等)。

它的核心目的是:让函数组件摆脱“无状态”的限制,同时避免类组件的复杂度(比如 this 绑定、生命周期拆分混乱)

关键特点:

  • 只能在函数组件或自定义 Hook 中调用(不能在类组件、普通函数里用)
  • 必须在组件顶层调用(不能嵌套在 ifforuseEffect 里,保证调用顺序稳定)

2. 常用内置 Hook 及用法

React 提供了多个内置 Hook,最常用的有 4 个,覆盖 90% 场景:

(1)useState:管理组件内部状态

  • 作用:给函数组件添加“局部状态”(比如输入框值、开关状态、列表数据)
  • 语法:const [状态, 修改状态的函数] = useState(初始值)
  • 例子:计数器组件
import { useState } from 'react';

function Counter() {
  // 声明状态:count(当前值),setCount(修改函数),初始值 0
  const [count, setCount] = useState(0);

  return (
    <div>
      计数:{count}
      {/* 点击修改状态(注意:不能直接写 count++,要传新值) */}
      <button onClick={() => setCount(count + 1)}>加 1</button>
    </div>
  );
}

(2)useEffect:处理副作用

  • 作用:处理组件的“副作用”(比如网络请求、DOM 操作、定时器、订阅),替代类组件的 componentDidMountcomponentDidUpdatecomponentWillUnmount
  • 语法:useEffect(() => { 副作用逻辑 }, [依赖数组])
  • 核心规则:
    • 依赖数组为空 []:只在组件首次渲染时执行(类似 componentDidMount
    • 依赖数组有值 [a, b]:组件首次渲染 + ab 变化时执行(类似 componentDidUpdate
    • 无依赖数组:每次组件渲染都执行
    • 返回清理函数:在组件卸载或下次副作用执行前触发(类似 componentWillUnmount
  • 例子:组件挂载时请求数据,卸载时清除定时器
import { useState, useEffect } from 'react';

function User() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // 副作用:请求用户数据
    const fetchUser = async () => {
      const res = await fetch('/api/user');
      setUser(await res.json());
    };
    fetchUser();

    // 清理函数:组件卸载时清除定时器(避免内存泄漏)
    const timer = setInterval(() => console.log('计时'), 1000);
    return () => clearInterval(timer);
  }, []); // 空依赖:只执行一次

  return <div>{user ? user.name : '加载中...'}</div>;
}

(3)useContext:读取 Context 数据

  • 作用:在函数组件中快速读取 Context 中的数据(后面讲 Context 时会结合用)
  • 语法:const 数据 = useContext(Context 对象)

(4)useRef:获取 DOM 元素或保存可变值

  • 作用:两种核心用法:
    1. 获取 DOM 元素(类似类组件的 createRef
    2. 保存一个“不会触发组件重新渲染”的可变值(比如定时器 ID、前一次的状态)
  • 例子:获取输入框 DOM
import { useRef } from 'react';

function Input() {
  // 创建 ref 对象
  const inputRef = useRef(null);

  const focusInput = () => {
    // 通过 ref.current 获取 DOM 元素
    inputRef.current.focus();
  };

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>聚焦输入框</button>
    </>
  );
}

3. 自定义 Hook

如果多个组件有重复逻辑(比如“请求数据+加载状态+错误处理”),可以把逻辑抽成自定义 Hook(命名必须以 use 开头),实现复用。

例子:自定义 useFetch Hook 封装请求逻辑

import { useState, useEffect } from 'react';

// 自定义 Hook:封装请求逻辑
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error('请求失败');
        setData(await res.json());
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, [url]);

  // 返回给组件使用的数据和状态
  return { data, loading, error };
}

// 组件中使用自定义 Hook
function UserList() {
  const { data: users, loading, error } = useFetch('/api/users');

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误:{error}</div>;

  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

二、再搞懂 Context:跨组件数据“全局通道”

1. 什么是 Context?

Context 是 React 提供的跨组件数据传递机制,用于解决“props drilling(属性透传)”问题。

比如:爷爷组件有一个“主题色”数据,需要传给孙子、曾孙子组件,如果用 props 传递,中间的父组件必须层层接收再传递(即使父组件自己不用这个数据),非常繁琐。

Context 的核心目的:让数据在组件树中“全局可用”,无需层层透传 props

2. Context 的核心用法(3 步走)

Context 的使用分 3 步:创建 Context → 提供数据 → 消费数据

步骤 1:创建 Context(单独文件,方便复用)

// themes.js
import { createContext } from 'react';

// 创建 Context,可选:传入默认值(只有当没有 Provider 时才会生效)
const ThemeContext = createContext('light'); // 默认主题:浅色

export default ThemeContext;

步骤 2:提供数据(在组件树顶层用 Provider 包裹)

ThemeContext.Provider 组件包裹需要使用该数据的组件树,通过 value 属性传入要共享的数据。

// App.js
import ThemeContext from './themes';
import Header from './Header';
import Content from './Content';

function App() {
  const theme = 'dark'; // 要共享的主题数据(可以是状态、对象等)

  // Provider 包裹子组件,所有子组件都能访问 value 中的数据
  return (
    <ThemeContext.Provider value={theme}>
      <Header /> {/* 父组件(不用主题数据) */}
      <Content /> {/* 子组件(可能用主题数据) */}
    </ThemeContext.Provider>
  );
}

步骤 3:消费数据(子组件中读取 Context)

有两种方式读取 Context,推荐用 useContext(更简洁,配合 Hook 使用):

方式 1:useContext(函数组件推荐)
// Button.js(孙子组件)
import { useContext } from 'react';
import ThemeContext from './themes';

function Button() {
  // 直接读取 Context 数据
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
      主题按钮
    </button>
  );
}
方式 2:Context.Consumer(类组件/函数组件都能用)
// Button.js
import ThemeContext from './themes';

function Button() {
  return (
    <ThemeContext.Consumer>
      {/* 必须传入一个函数,参数是 Context 数据 */}
      {theme => (
        <button style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
          主题按钮
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

3. Context 的常见场景

  • 共享全局配置(比如主题色、语言、权限状态)
  • 共享用户信息(比如登录状态、用户名)
  • 共享工具函数(比如全局通知、日志函数)

注意:Context 不是“全局状态管理工具”!如果需要共享“会频繁修改且多个组件依赖”的数据(比如购物车、表单状态),单独用 Context 会导致不必要的重渲染,建议配合 useReducer 或 Redux、Zustand 等状态管理库使用。

三、Hook 和 Context 的关系

两者是互补关系,经常一起使用:

  • Context 负责“跨组件传递数据”
  • Hook(尤其是 useContext)负责“在函数组件中便捷地读取 Context 数据”

比如上面的主题例子,ThemeContext 传递数据,useContext(ThemeContext) 读取数据,缺一不可。

再比如“全局登录状态”:

  1. useState 管理登录状态(isLoginuserInfo
  2. 用 Context 把登录状态和修改状态的函数(loginlogout)传递给所有组件
  3. 子组件用 useContext 读取登录状态,或调用 login/logout 函数

总结

特性核心作用关键要点
Hook给函数组件扩展状态、副作用等能力只能在函数组件/自定义 Hook 顶层调用;命名规范(useXXX
Context跨组件传递数据,解决 props 透传配合 useContext 使用更简洁;不适合频繁修改的复杂状态

简单记:

  • 组件内部需要状态/副作用 → 用 Hook(useState/useEffect
  • 组件之间需要共享数据 → 用 Context + useContext