react 常用hook

6 阅读9分钟

1. useState - 状态管理

作用

在函数组件中声明和更新局部状态。

语法

const [state, setState] = useState(initialValue);
  • state:当前状态值。
  • setState:更新状态的函数(类似 this.setState)。
  • initialValue:初始状态(可以是任意类型)。

使用场景

  • 管理组件内部的状态(如计数器、表单输入、开关状态等)。
  • 替代类组件的 this.state 和 this.setState

示例

import { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

2. useEffect - 副作用处理

作用

在组件渲染后执行副作用操作(如数据获取、订阅、手动 DOM 操作等)。

语法

useEffect(() => {
  // 副作用逻辑
  return () => {
    // 清理逻辑(可选)
  };
}, [dependencies]); // 依赖项数组
  • 无依赖([] :仅在组件挂载和卸载时执行(类似 componentDidMount + componentWillUnmount)。
  • 有依赖([dep1, dep2] :依赖变化时执行。
  • 无依赖数组:每次渲染后都执行(慎用)

注意:

  1. 挂载时执行:传入 useEffect 的函数会在组件首次渲染后执行(即 componentDidMount 的时机)。
  2. 卸载时执行:如果该 useEffect 返回一个清理函数(cleanup),则清理函数会在组件卸载时执行(即 componentWillUnmount 的时机)。

示例代码:

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    console.log('组件挂载时执行(类似 componentDidMount)');

    return () => {
      console.log('组件卸载时执行(类似 componentWillUnmount)');
    };
  }, []); // 空依赖数组表示仅在挂载和卸载时执行

  return <div>My Component</div>;
}

关键点:

  • 如果 useEffect 没有返回清理函数,则卸载时不会执行任何操作。
  • 依赖数组 [] 确保副作用仅在挂载和卸载时运行,不会因 props 或 state 变化而重新执行。

总结:

  • 挂载时:执行 useEffect 的主函数。
  • 卸载时:执行 useEffect 返回的清理函数(如果有的话)。
  • 如果没有清理函数,卸载时不会执行任何操作。

使用场景

  • 数据获取(fetchaxios)。
  • 事件监听(addEventListener)。
  • 定时器(setInterval / setTimeout)。
  • 手动修改 DOM。

示例

import { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(res => res.json())
      .then(data => setData(data));
  }, []); // 仅在组件挂载时执行

  return <div>{data ? data.name : 'Loading...'}</div>;
}

3. useContext - 跨组件数据共享

作用

访问 React Context 的值,避免层层传递 props,避免手动逐层传递 props(即 "prop drilling")。它通常与 createContext 配合使用,适用于全局或主题、用户身份等需要多组件访问的数据。

语法

const value = useContext(MyContext);
  • MyContext:由 React.createContext() 创建的 Context 对象。

使用场景

  • 全局主题、用户身份、语言切换等共享数据。

1. 基本使用步骤

(1) 创建 Context

import { createContext } from 'react';

// 创建一个 Context 并设置默认值(默认值在未提供 Provider 时生效)
const ThemeContext = createContext('light'); // 'light' 是默认值

(2) 用 Provider 包裹组件并传递数据

function App() {
  const [theme, setTheme] = useState('dark');

  return (
    // 通过 Provider 的 value 属性共享数据(此处共享 theme 和 setTheme)
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

(3) 在子组件中使用 useContext 获取数据

import { useContext } from 'react';

function Button() {
  // 直接获取 ThemeContext 的数据
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button 
      style={{ background: theme === 'dark' ? '#333' : '#eee' }}
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
    >
      Toggle Theme
    </button>
  );
}

 关键特性

特性说明
跨组件共享数据无需通过 props 层层传递,任意层级的子组件均可直接访问 Context 数据。
性能优化当 Context 的 value 变化时,所有用到该 Context 的组件都会重新渲染
默认值如果组件不在 Provider 的包裹下,useContext 会返回 createContext 的默认值。

适用场景

  • 全局主题(如暗黑模式切换)。
  • 用户身份信息(如当前登录用户)。
  • 多语言国际化(如切换语言包)。
  • 全局配置(如 API 端点、功能开关)。

性能注意事项

  • 避免频繁更新的 Context:如果 Context 的 value 经常变化(如每秒更新的状态),可能导致大量子组件重新渲染。解决方案:

    • 拆分 Context(将稳定数据和易变数据分开)。
    • 使用 React.memo 优化子组件。

示例:拆分 Context

// 将 theme 和 setTheme 分开,避免不必要的渲染
const ThemeContext = createContext('light');
const ThemeUpdateContext = createContext(() => {});

function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={theme}>
      <ThemeUpdateContext.Provider value={setTheme}>
        <Toolbar />
      </ThemeUpdateContext.Provider>
    </ThemeContext.Provider>
  );
}

// 只有用到 setTheme 的组件会订阅更新
function Button() {
  const theme = useContext(ThemeContext);
  const setTheme = useContext(ThemeUpdateContext);
  // ...
}

ThemeContext.Provider 是 React Context API 的核心部分,它负责向下传递共享的数据,所有被它包裹的子组件都可以通过 useContext(ThemeContext) 获取到这些数据。下面详细解释它的作用和工作原理:


1. ThemeContext.Provider 是什么?

  • 来源:通过 createContext() 创建 Context 对象时,会自动生成一个 Provider 组件(这里是 ThemeContext.Provider)。
  • 作用:它是一个组件,通过 value 属性向其所有子组件(无论层级多深)提供共享数据。
  • 类比:类似一个“数据管道”,包裹在它内部的组件可以直接“接上”这个管道获取数据,无需手动逐层传递 props。
  • 2. 基本语法

import { createContext } from 'react';

// 1. 创建 Context
const ThemeContext = createContext('light'); // 'light' 是默认值

function App() {
  const [theme, setTheme] = useState('dark');

  // 2. 用 Provider 包裹需要共享数据的组件
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <ChildComponent />
    </ThemeContext.Provider>
  );
}

3. 核心特点

特性说明
数据共享所有子组件(如 <ChildComponent />)均可通过 useContext(ThemeContext) 获取 value 的数据。
value 是必需的必须通过 value 属性传递数据,没有 value 会导致子组件获取默认值或报错。
动态更新如果 value 变化,所有消费该 Context 的子组件会自动重新渲染

4. 为什么需要 Provider?

❌ 不用 Provider 的问题

// 如果直接使用 Context,但没有 Provider 包裹:
function Button() {
  const theme = useContext(ThemeContext); 
  // 这里获取的是 createContext 的默认值('light'),无法动态更新
}

✅ 使用 Provider 的优势

function App() {
  const [theme, setTheme] = useState('dark');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {/* 子组件可以获取到最新的 theme 和 setTheme */}
      <Button />
    </ThemeContext.Provider>
  );
}

5. 多层 Provider 嵌套

如果存在多个同类型 Provider,子组件会获取最近的 Provider 的值

<ThemeContext.Provider value="dark">
  <Header /> {/* 这里获取到 'dark' */}
  <ThemeContext.Provider value="light">
    <Button /> {/* 这里获取到 'light' */}
  </ThemeContext.Provider>
</ThemeContext.Provider>

6. 性能优化建议

  • 避免频繁变化的 value:如果 Provider 的 value 是一个新对象(如 value={{ theme, setTheme }}),每次渲染都会生成新对象,导致所有子组件重新渲染。解决方案:

    // 优化:使用 useMemo 缓存 value
    const value = useMemo(() => ({ theme, setTheme }), [theme]);
    return <ThemeContext.Provider value={value}>...</ThemeContext.Provider>;
    

4. useMemo - 缓存计算结果

作用

缓存昂贵的计算,避免每次渲染都重新计算。

语法

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 仅在 a 或 b 变化时才重新计算。

使用场景

  • 优化复杂计算(如排序、筛选大数据)。
  • 避免子组件不必要的重渲染。

示例

import { useMemo } from 'react';

function ExpensiveComponent({ list }) {
  const sortedList = useMemo(() => {
    return list.sort((a, b) => a - b);
  }, [list]);

  return <div>{sortedList.join(', ')}</div>;
}

5. useCallback - 缓存函数

作用

缓存函数,避免子组件因函数引用变化而重渲染。

语法

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

使用场景

  • 优化子组件(如 React.memo 包裹的组件)。
  • 防止因父组件重渲染导致子组件不必要的更新。

示例

import { useCallback } from 'react';

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

  const handleClick = useCallback(() => {
    console.log('Clicked!', count);
  }, [count]);

  return <Child onClick={handleClick} />;
}

const Child = React.memo(({ onClick }) => {
  return <button onClick={onClick}>Click Me</button>;
});

6. useRef - 引用 DOM 或保存可变值

作用

  • 访问 DOM 节点。
  • 保存可变值(不触发重渲染)。

语法

const ref = useRef(initialValue);
ref.current; // 访问当前值

使用场景

  • 获取输入框焦点。
  • 存储定时器 ID、上一次渲染的值等。

示例

import { useRef } from 'react';

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

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

useRef 和 useState 是 React Hooks 中两个常用的 Hook,它们的主要区别在于用途、触发渲染、数据可变性等方面。

存储上次的值

import { useState, useEffect, useRef } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();

  useEffect(() => {
    prevCountRef.current = count; // 修改 ref 不会触发渲染
  }, [count]);

  return (
    <div>
      <p>Now: {count}, Before: {prevCountRef.current}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}
特性useStateuseRef
用途管理组件状态,状态变化触发重新渲染存储可变值,变化不触发重新渲染
返回值[state, setState](状态 + 更新函数){ current: value }(可变 ref 对象)
是否触发渲染✅ 状态更新会触发组件重新渲染❌ 修改 ref.current 不会触发渲染
数据可变性不可变(应使用 setState 更新)可变(直接修改 ref.current
适用场景需要 UI 响应的数据(如表单输入、计数器)存储 DOM 引用、保存上一次的值、不触发渲染的变量

关键区别总结

场景用 useState用 useRef
需要 UI 更新的数据(如计数器)✅ 适合❌ 不适合(不会触发渲染)
存储 DOM 引用❌ 不适合✅ 适合
记录上一次的 props/state❌ 不适合(会触发额外渲染)✅ 适合
存储定时器、动画 ID❌ 不适合(可能导致渲染问题)✅ 适合

简单记忆:

  • 需要触发渲染的数据 → useState
  • 需要存储可变值但不触发渲染 → useRef

7. useReducer - 复杂状态管理

作用

类似 Redux 的状态管理,适合复杂逻辑。

语法

const [state, dispatch] = useReducer(reducer, initialState);
  • reducer(state, action) => newState 函数。
  • dispatch:发送 action 触发更新。

使用场景

  • 表单状态管理。
  • 替代 useState 处理复杂逻辑。

示例

import { useReducer } from 'react';

function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

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

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+1</button>
    </div>
  );
}

7.1. useReducer 的基本结构

useReducer 是 React 提供的状态管理 Hook,适合处理复杂的状态逻辑(比 useState 更结构化)。
它接收两个参数:

  • reducer 函数:定义如何更新状态((state, action) => newState)。
  • 初始状态(这里是 { count: 0 })。

返回:

  • 当前状态state)。
  • dispatch 函数:用于触发状态更新(发送 action)。

7.2. Reducer 函数(counterReducer

这是一个纯函数,根据 action.type 决定如何计算新状态:

function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }; // 返回新状态
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state; // 未知 action 时返回原状态
  }
}
  • 规则:必须返回新的状态对象(不可直接修改 state)。
  • 扩展性:可以轻松添加更多操作(如 resetmultiply)。

7.3. 组件中使用 useReducer

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

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+1</button>
      {/* 可以添加递减按钮: */}
      <button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
    </div>
  );
}
  • dispatch({ type: 'increment' }) :发送一个 action,触发 reducer 计算新状态。
  • UI 自动更新:状态变化后,组件会重新渲染,显示最新的 state.count

7.4. 与 useState 的对比

场景useStateuseReducer
适用情况简单状态(如布尔值、数字)复杂状态逻辑或多关联状态
更新逻辑直接通过 setState 更新通过 dispatch(action) 集中管理
代码组织分散在多个 useState逻辑集中在 reducer 中,更清晰

7.5. 何时选择 useReducer

  • 状态逻辑较复杂(如多个子状态相互依赖)。
  • 需要更可预测的状态管理(类似 Redux 的模式)。
  • 需要复用或测试状态更新逻辑。

8. useLayoutEffect - 同步副作用

作用

与 useEffect 类似,但在浏览器 绘制前 同步执行。

语法

useLayoutEffect(() => {
  // DOM 测量或同步操作
}, [dependencies]);

使用场景

  • 计算 DOM 尺寸或位置(如动画)。
  • 避免闪烁(如强制同步渲染)。

示例

import { useLayoutEffect, useRef } from 'react';

function MeasureElement() {
  const divRef = useRef(null);

  useLayoutEffect(() => {
    const { width } = divRef.current.getBoundingClientRect();
    console.log('Width:', width);
  }, []);

  return <div ref={divRef}>Measure Me</div>;
}

总结

Hook核心用途类比类组件
useState管理组件内部状态this.state + setState
useEffect处理副作用(数据获取、订阅等)componentDidMount + componentDidUpdate + componentWillUnmount
useContext跨组件共享数据Context.Consumer
useMemo缓存计算结果shouldComponentUpdate
useCallback缓存函数引用shouldComponentUpdate
useRef访问 DOM 或存储可变值createRef()
useReducer复杂状态管理Redux-like reducer
useLayoutEffect同步 DOM 操作componentDidMount(同步)