跳过正文
Background Image

React Hooks 完全指南:从入门到精通

·1041 字·5 分钟· loading · loading · ·

🎯 什么是 React Hooks?
#

React Hooks 是 React 16.8 引入的新特性,它允许你在函数组件中使用状态和其他 React 特性,而无需编写 class 组件。Hooks 让函数组件变得更加强大和灵活。

🪝 基础 Hooks
#

✨ Hooks 的优势
#

  • 简化代码: 减少样板代码,提高可读性
  • 逻辑复用: 更容易在组件间共享逻辑
  • 学习成本: 无需理解 class 组件的复杂概念
  • 性能优化: 更好的 tree-shaking 和代码分割

🪝 基础 Hooks
#

1. useState - 状态管理
#

useState 是最基本的 Hook,用于在函数组件中添加状态。

import React, { useState } from 'react';

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

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        增加
      </button>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="输入姓名"
      />
    </div>
  );
}

函数式更新
#

// 推荐:使用函数式更新
setCount(prevCount => prevCount + 1);

// 避免:直接使用当前值
setCount(count + 1);

2. useEffect - 副作用处理
#

useEffect 用于处理组件中的副作用,如数据获取、订阅、手动修改 DOM 等。

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

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

  useEffect(() => {
    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        const userData = await response.json();
        setUser(userData);
      } catch (error) {
        console.error('获取用户信息失败:', error);
      } finally {
        setLoading(false);
      }
    }

    fetchUser();
  }, [userId]); // 依赖数组

  if (loading) return <div>加载中...</div>;
  if (!user) return <div>用户不存在</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

useEffect 的依赖数组
#

// 每次渲染后执行
useEffect(() => {
  console.log('每次渲染后执行');
});

// 只在组件挂载时执行一次
useEffect(() => {
  console.log('组件挂载时执行');
}, []);

// 当 count 变化时执行
useEffect(() => {
  console.log('count 变化时执行');
}, [count]);

// 清理函数
useEffect(() => {
  const timer = setInterval(() => {
    console.log('定时器执行');
  }, 1000);

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

3. useContext - 上下文共享
#

useContext 用于在组件树中共享数据,避免 props 层层传递。

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

// 创建上下文
const ThemeContext = createContext();

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

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

// 使用上下文的组件
function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);

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

🔧 高级 Hooks
#

1. useReducer - 复杂状态管理
#

useReducer 适用于管理复杂的状态逻辑,类似于 Redux 的 reducer 模式。

import React, { useReducer } from 'react';

const initialState = {
  count: 0,
  step: 1,
};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'decrement':
      return { ...state, count: state.count - state.step };
    case 'setStep':
      return { ...state, step: action.payload };
    case 'reset':
      return initialState;
    default:
      throw new Error(`未知操作: ${action.type}`);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>计数: {state.count}</p>
      <p>步长: {state.step}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>
        增加
      </button>
      <button onClick={() => dispatch({ type: 'decrement' })}>
        减少
      </button>
      <input
        type="number"
        value={state.step}
        onChange={(e) => dispatch({ 
          type: 'setStep', 
          payload: parseInt(e.target.value) || 1 
        })}
      />
      <button onClick={() => dispatch({ type: 'reset' })}>
        重置
      </button>
    </div>
  );
}

2. useCallback - 性能优化
#

useCallback 用于缓存函数,避免不必要的重新渲染。

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

const ExpensiveComponent = memo(({ onIncrement }) => {
  console.log('ExpensiveComponent 重新渲染');
  return (
    <button onClick={onIncrement}>
      增加计数
    </button>
  );
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(0);

  // 使用 useCallback 缓存函数
  const handleIncrement = useCallback(() => {
    setCount(c => c + 1);
  }, []); // 空依赖数组,函数永远不会改变

  return (
    <div>
      <p>计数: {count}</p>
      <p>其他状态: {otherState}</p>
      <ExpensiveComponent onIncrement={handleIncrement} />
      <button onClick={() => setOtherState(s => s + 1)}>
        更新其他状态
      </button>
    </div>
  );
}

3. useMemo - 计算值缓存
#

useMemo 用于缓存计算结果,避免重复计算。

import React, { useState, useMemo } from 'react';

function ExpensiveCalculation({ numbers }) {
  // 使用 useMemo 缓存计算结果
  const sum = useMemo(() => {
    console.log('执行昂贵的计算...');
    return numbers.reduce((acc, num) => acc + num, 0);
  }, [numbers]); // 只有当 numbers 变化时才重新计算

  return <div>总和: {sum}</div>;
}

function App() {
  const [numbers, setNumbers] = useState([1, 2, 3, 4, 5]);
  const [otherState, setOtherState] = useState(0);

  return (
    <div>
      <ExpensiveCalculation numbers={numbers} />
      <button onClick={() => setNumbers([...numbers, numbers.length + 1])}>
        添加数字
      </button>
      <button onClick={() => setOtherState(s => s + 1)}>
        更新其他状态: {otherState}
      </button>
    </div>
  );
}

🎨 自定义 Hooks
#

自定义 Hooks 让你可以提取组件逻辑到可重用的函数中。

1. 数据获取 Hook
#

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    async function fetchData() {
      try {
        setLoading(true);
        setError(null);
        const response = await fetch(url);
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const result = await response.json();
        setData(result);
      } 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(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

2. 表单处理 Hook
#

import { useState, useCallback } from 'react';

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

  const handleChange = useCallback((name, value) => {
    setValues(prev => ({ ...prev, [name]: value }));
    // 清除对应字段的错误
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  }, [errors]);

  const handleSubmit = useCallback((onSubmit) => {
    return (e) => {
      e.preventDefault();
      onSubmit(values);
    };
  }, [values]);

  const reset = useCallback(() => {
    setValues(initialValues);
    setErrors({});
  }, [initialValues]);

  return {
    values,
    errors,
    handleChange,
    handleSubmit,
    reset,
    setErrors,
  };
}

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

  const onSubmit = (formData) => {
    console.log('表单数据:', formData);
    // 处理登录逻辑
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        type="email"
        placeholder="邮箱"
        value={values.email}
        onChange={(e) => handleChange('email', e.target.value)}
      />
      <input
        type="password"
        placeholder="密码"
        value={values.password}
        onChange={(e) => handleChange('password', e.target.value)}
      />
      <button type="submit">登录</button>
      <button type="button" onClick={reset}>重置</button>
    </form>
  );
}

🚀 最佳实践
#

1. Hook 使用规则
#

  • 只在函数组件顶层调用 Hooks
  • 不要在循环、条件或嵌套函数中调用 Hooks
  • 只在 React 函数组件或自定义 Hooks 中调用 Hooks

2. 性能优化
#

  • 使用 useCallback 缓存函数
  • 使用 useMemo 缓存计算结果
  • 使用 React.memo 避免不必要的重新渲染
  • 合理设置 useEffect 的依赖数组

3. 代码组织
#

  • 将相关的状态和逻辑放在同一个 Hook 中
  • 提取可重用的逻辑到自定义 Hooks
  • 保持 Hooks 的单一职责原则

🔧 常见问题和解决方案
#

Q: 为什么 useEffect 无限循环?
#

A: 检查依赖数组,确保包含所有在 effect 中使用的变量。

Q: 如何避免 useCallback 的过度使用?
#

A: 只在需要传递给子组件且子组件使用 React.memo 时使用。

Q: useState 和 useReducer 如何选择?
#

A: 简单状态用 useState,复杂状态逻辑用 useReducer

📚 学习资源
#

🎯 总结
#

React Hooks 是现代 React 开发的核心,它们让函数组件变得更加强大和灵活。通过掌握基础 Hooks、高级 Hooks 和自定义 Hooks,你可以构建出更加优雅和高效的 React 应用。

记住,Hooks 的学习是一个渐进的过程,建议从基础开始,逐步掌握更高级的用法。实践是最好的老师,多写代码,多尝试不同的场景!


如果你有任何问题或建议,欢迎在评论区讨论!