React Hooks 编程指南:useState 与 useEffect 的完美实践 🚀

273 阅读2分钟

函数式组件已成为 React 开发的主流模式,Hooks 的出现彻底改变了状态管理和生命周期处理的方式。本文将深入剖析核心 Hooks 的使用技巧和最佳实践!🔍✨


一、useState:函数式组件的状态核心 💡

import React, { useState } from 'react';

function Counter() {
  // 声明状态变量和更新函数
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({ name: 'John', age: 25 });

  // 更新状态(函数式更新确保最新值)
  const increment = () => setCount(prev => prev + 1);
  
  // 更新对象(保持不可变性)
  const updateUser = () => setUser(prev => ({ ...prev, age: 26 }));

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
      
      <p>User: {user.name}, Age: {user.age}</p>
      <button onClick={updateUser}>Update Age</button>
    </div>
  );
}

关键点:

  • 状态更新函数是异步的,连续调用会被批量处理 🔄
  • 对象/数组更新需保持不可变性,使用扩展运算符 🌀
  • 函数式更新 (setCount(prev => prev + 1)) 避免闭包陷阱 ⚠️

二、useEffect:副作用管理的瑞士军刀 🔧

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

function DataFetcher({ userId }) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 创建取消标志
    const abortController = new AbortController();
    
    const fetchData = async () => {
      try {
        const response = await fetch(
          `https://api.example.com/users/${userId}`,
          { signal: abortController.signal }
        );
        setData(await response.json());
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      }
    };

    fetchData();

    // 清理函数:取消请求和定时器
    return () => abortController.abort();
  }, [userId]); // 依赖项:userId 变化时重新执行

  // 组件卸载时自动执行清理函数
  // ...
}

三、生命周期映射:类组件 → Hooks 🧩

类组件生命周期Hooks 等效实现
componentDidMountuseEffect(fn, [])
componentDidUpdateuseEffect(fn, [deps])
componentWillUnmountuseEffect(() => { return cleanup }, [])

挂载阶段示例:

useEffect(() => {
  console.log('组件挂载完成!');
  const timer = setInterval(() => {
    console.log('定时器运行中...');
  }, 1000);

  return () => clearInterval(timer); // 卸载时清理
}, []);

四、数据请求最佳实践 📡

useEffect(() => {
  let isMounted = true; // 组件挂载标志
  
  const fetchData = async () => {
    try {
      const result = await axios.get('/api/data');
      if (isMounted) setData(result.data);
    } catch (err) {
      if (isMounted) setError(err);
    }
  };

  fetchData();

  return () => {
    isMounted = false; // 组件卸载时标记
    // 可在此取消 Axios 请求
  };
}, []);

五、为什么 useEffect 不能直接用 async? 🚫

错误示例:

// ❌ 危险!返回 Promise 而非清理函数
useEffect(async () => {
  const data = await fetchData();
  setData(data);
}, []);

正确解决方案:

useEffect(() => {
  // 在效果内部声明 async 函数
  const loadData = async () => {
    const data = await fetchData();
    setData(data);
  };

  loadData();
  
  // 返回同步的清理函数
  return () => { /* 清理逻辑 */ };
}, []);

六、高级技巧:自定义 Hook 封装 🛠️

// 创建可复用的数据获取 Hook
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        setData(await response.json());
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading };
}

// 在组件中使用
function UserProfile({ userId }) {
  const { data: user, loading } = useFetch(`/users/${userId}`);
  
  if (loading) return <Spinner />;
  return <ProfileCard user={user} />;
}

关键总结 📝

  1. 状态分离:多个 useState 调用比合并大对象更清晰 💡
  2. 依赖项诚实:确保 useEffect 依赖项包含所有变化值 📈
  3. 清理资源:定时器、订阅、请求必须清理 🧹
  4. 异步处理:在 useEffect 内部声明 async 函数 🔄
  5. 自定义Hook:提取复杂逻辑保持组件简洁 🧑‍💻

黄金法则:每个 useEffect 只做一件事!将不同逻辑拆分到多个 effect 中,让代码更可预测且易于维护!🔮

通过合理运用 useState 和 useEffect,你的函数式组件将获得媲美类组件的能力,同时享受更简洁的代码结构和更好的可测试性!🎯

【延伸思考】当遇到复杂状态逻辑时,可探索 useReducer 与 useContext 的配合使用,它们能优雅处理跨组件状态共享!🌐