Skip to content

React Hooks 核心

React Hooks 是 React 16.8+ 引入的新特性,它允许我们在函数组件中使用状态和其他 React 特性。本章节将介绍 React Hooks 的核心概念和使用方法。

8.1 什么是Hooks?

Hooks 的定义

Hooks 是一些特殊的函数,它们允许我们在函数组件中使用 React 的状态和生命周期特性。

解决的问题

  • 类组件的复杂性:类组件中 this 的指向问题,生命周期方法的拆分
  • 逻辑复用:在类组件中,逻辑复用通常需要使用高阶组件或 render props,代码复杂
  • 函数组件的限制:在 React 16.8 之前,函数组件无法使用状态和生命周期方法

Hooks 的优势

  • 代码更简洁:使用函数组件,避免了类组件的复杂性
  • 逻辑复用更简单:可以使用自定义 Hooks 来复用逻辑
  • 更好的类型推断:函数组件在 TypeScript 中更容易进行类型推断
  • 更好的性能:减少了类组件的开销

8.2 常用Hooks详解

useState:状态管理

基本使用

jsx
import { useState } from 'react';

function Counter() {
  // 声明一个名为 count 的状态变量,初始值为 0
  // setCount 是更新 count 的函数
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

复杂状态

jsx
import { useState } from 'react';

function UserProfile() {
  const [user, setUser] = useState({
    name: '张三',
    age: 20,
    email: 'zhangsan@example.com'
  });

  function updateName(name) {
    setUser(prevUser => ({
      ...prevUser,
      name
    }));
  }

  return (
    <div>
      <h1>{user.name}</h1>
      <input
        type="text"
        value={user.name}
        onChange={e => updateName(e.target.value)}
      />
    </div>
  );
}

useEffect:副作用处理

基本使用

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

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

  // 组件挂载时执行一次,相当于 componentDidMount
  useEffect(() => {
    console.log('组件挂载了');
  }, []);

  // 组件更新时执行,相当于 componentDidUpdate
  useEffect(() => {
    console.log('组件更新了,count:', count);
  }, [count]);

  // 组件卸载时执行,相当于 componentWillUnmount
  useEffect(() => {
    return () => {
      console.log('组件卸载了');
    };
  }, []);

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

依赖项

  • 空数组 []:只在组件挂载和卸载时执行
  • 包含依赖项:当依赖项变化时执行
  • 无依赖项:每次组件渲染时都执行

清除副作用

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

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

  useEffect(() => {
    // 设置定时器
    const timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);

    // 清除定时器
    return () => {
      clearInterval(timer);
    };
  }, []);

  return <p>计数:{count}</p>;
}

常见场景

  • 数据请求
  • 订阅事件
  • 操作 DOM
  • 设置定时器

useRef:获取DOM元素、保存持久化值

获取 DOM 元素

jsx
import { useRef } from 'react';

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

  function focusInput() {
    inputRef.current.focus();
  }

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

保存持久化值

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

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

  useEffect(() => {
    prevCountRef.current = count;
  });

  return (
    <div>
      <p>当前计数:{count}</p>
      <p>之前计数:{prevCountRef.current}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

useContext:跨组件通信

创建 Context

jsx
// src/contexts/ThemeContext.jsx
import { createContext, useState, useContext } from 'react';

// 创建 Context
const ThemeContext = createContext();

// 提供者组件
export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

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

// 自定义 Hook
export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

使用 Context

jsx
import { ThemeProvider, useTheme } from './contexts/ThemeContext';

function ThemedButton() {
  const { theme, setTheme } = useTheme();

  return (
    <div>
      <p>当前主题:{theme}</p>
      <button
        onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
        style={{
          backgroundColor: theme === 'light' ? '#fff' : '#333',
          color: theme === 'light' ? '#333' : '#fff'
        }}
      >
        切换主题
      </button>
    </div>
  );
}

function App() {
  return (
    <ThemeProvider>
      <ThemedButton />
    </ThemeProvider>
  );
}

8.3 Hooks 使用规则

1. 只在函数最顶层使用 Hooks

  • 不要在循环、条件或嵌套函数中使用 Hooks
  • 确保 Hooks 在每次渲染时都以相同的顺序调用

2. 只在 React 函数中使用 Hooks

  • 在函数组件中使用
  • 在自定义 Hooks 中使用
  • 不要在普通 JavaScript 函数中使用

3. 依赖项数组要正确设置

  • 不要遗漏依赖项
  • 不要包含不必要的依赖项
  • 对于复杂对象,要考虑使用 useMemo 或 useCallback

4. 自定义 Hooks 命名规范

  • 自定义 Hooks 必须以 use 开头
  • 这样 React 才能识别它们是 Hooks

8.4 自定义Hooks封装

基本结构

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

function useCustomHook(initialValue) {
  const [state, setState] = useState(initialValue);

  // 逻辑

  return [state, setState];
}

export default useCustomHook;

示例1:useRequest

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

function useRequest(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;

    async function fetchData() {
      setLoading(true);
      setError(null);

      try {
        const response = await fetch(url);
        const json = await response.json();

        if (!cancelled) {
          setData(json);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }

    fetchData();

    return () => {
      cancelled = true;
    };
  }, [url]);

  return { data, loading, error };
}

// 使用
function DataDisplay() {
  const { data, loading, error } = useRequest('https://api.example.com/data');

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

  return <div>{JSON.stringify(data)}</div>;
}

示例2:useLocalStorage

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

function useLocalStorage(key, initialValue) {
  // 从 localStorage 中获取初始值
  const [value, setValue] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  // 当值变化时,更新 localStorage
  useEffect(() => {
    try {
      localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  }, [key, value]);

  return [value, setValue];
}

// 使用
function TodoList() {
  const [todos, setTodos] = useLocalStorage('todos', []);

  function addTodo(todo) {
    setTodos(prevTodos => [...prevTodos, todo]);
  }

  return (
    <div>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
      <button onClick={() => addTodo('新任务')}>添加任务</button>
    </div>
  );
}

实战练习

练习1:useCounter

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

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

  const increment = useCallback(() => setCount(prev => prev + 1), []);
  const decrement = useCallback(() => setCount(prev => prev - 1), []);
  const reset = useCallback(() => setCount(initialValue), [initialValue]);

  return { count, increment, decrement, reset };
}

// 使用
function Counter() {
  const { count, increment, decrement, reset } = useCounter(10);

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={increment}>增加</button>
      <button onClick={decrement}>减少</button>
      <button onClick={reset}>重置</button>
    </div>
  );
}

练习2:useMousePosition

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

function useMousePosition() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    function handleMouseMove(e) {
      setPosition({ x: e.clientX, y: e.clientY });
    }

    window.addEventListener('mousemove', handleMouseMove);

    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, []);

  return position;
}

// 使用
function MouseTracker() {
  const { x, y } = useMousePosition();

  return (
    <div>
      <p>鼠标位置:x = {x}, y = {y}</p>
    </div>
  );
}

练习3:useForm

jsx
import { useState } from 'react';

function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});

  function handleChange(e) {
    const { name, value, type, checked } = e.target;
    setValues(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  }

  function handleSubmit(onSubmit) {
    return function(e) {
      e.preventDefault();
      onSubmit(values);
    };
  }

  function setFieldValue(name, value) {
    setValues(prev => ({
      ...prev,
      [name]: value
    }));
  }

  return {
    values,
    errors,
    handleChange,
    handleSubmit,
    setFieldValue
  };
}

// 使用
function LoginForm() {
  const { values, handleChange, handleSubmit } = useForm({
    email: '',
    password: ''
  });

  function onSubmit(data) {
    console.log('登录数据:', data);
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>邮箱:</label>
        <input
          type="email"
          name="email"
          value={values.email}
          onChange={handleChange}
        />
      </div>
      <div>
        <label>密码:</label>
        <input
          type="password"
          name="password"
          value={values.password}
          onChange={handleChange}
        />
      </div>
      <button type="submit">登录</button>
    </form>
  );
}

通过本章节的学习,你已经掌握了 React Hooks 的核心概念和使用方法。Hooks 是 React 16.8+ 的重要特性,它使得函数组件更加强大和灵活,是现代 React 开发的基础。

© 2026 编程马·菜鸟教程 版权所有