从生命周期到useEffect:我的React函数组件进化之旅

还记得刚学React时,我被类组件的生命周期搞得头大------componentDidMountcomponentDidUpdatecomponentWillUnmount... 这么多方法要记!直到Hooks出现,特别是useEffect,让我终于能用一个API搞定所有场景。今天就来分享我的useEffect实战心得,帮你彻底告别生命周期方法的烦恼!


🎯 一、为什么说useEffect是"生命周期替代品"?

以前在类组件中,我们需要在不同的生命周期方法中编写代码:

jsx 复制代码
class OldComponent extends React.Component {
  componentDidMount() {
    console.log('组件挂载了');
    this.timer = setInterval(() => {
      this.doSomething();
    }, 1000);
  }
  
  componentDidUpdate(prevProps) {
    if (this.props.value !== prevProps.value) {
      console.log('props变化了');
    }
  }
  
  componentWillUnmount() {
    console.log('组件要卸载了');
    clearInterval(this.timer);
  }
  
  render() {
    return <div>老式写法</div>;
  }
}

现在用useEffect,一个API搞定所有:

jsx 复制代码
const NewComponent = ({ value }) => {
  useEffect(() => {
    console.log('组件挂载了');
    const timer = setInterval(() => {
      doSomething();
    }, 1000);
    
    return () => {
      console.log('组件要卸载了');
      clearInterval(timer);
    };
  }, []);
  
  useEffect(() => {
    console.log('value变化了:', value);
  }, [value]);
  
  return <div>新时代写法</div>;
};

是不是清爽多了?


🛠️ 二、useEffect的三种使用姿势

1. 模拟componentDidMount:只运行一次

jsx 复制代码
const UserProfile = () => {
  const [userData, setUserData] = useState(null);
  
  useEffect(() => {
    // 组件挂载时获取用户数据
    const fetchUserData = async () => {
      try {
        const response = await fetch('/api/user');
        const data = await response.json();
        setUserData(data);
      } catch (error) {
        console.error('获取数据失败:', error);
      }
    };
    
    fetchUserData();
  }, []); // 空依赖数组 = 只运行一次
  
  return <div>{userData?.name || '加载中...'}</div>;
};

💡 关键 :空数组[]作为第二个参数


2. 模拟componentDidUpdate:依赖变化时运行

jsx 复制代码
const SearchResults = ({ query }) => {
  const [results, setResults] = useState([]);
  
  useEffect(() => {
    // 搜索词变化时重新搜索
    if (query) {
      const search = async () => {
        const newResults = await fetchResults(query);
        setResults(newResults);
      };
      
      search();
    }
  }, [query]); // query变化时重新执行
  
  return (
    <div>
      {results.map(item => (
        <div key={item.id}>{item.title}</div>
      ))}
    </div>
  );
};

🚨 陷阱提醒:忘记加依赖数组会导致每次渲染都执行!


3. 模拟componentWillUnmount:清理副作用

jsx 复制代码
const RealTimeData = () => {
  useEffect(() => {
    // 建立WebSocket连接
    const ws = new WebSocket('ws://api.example.com');
    
    ws.onmessage = (event) => {
      console.log('收到消息:', event.data);
    };
    
    // 返回清理函数
    return () => {
      ws.close();
      console.log('WebSocket连接已关闭');
    };
  }, []);
  
  return <div>实时数据组件</div>;
};

🎯 重点:return的函数会在组件卸载时自动执行


⚠️ 三、我踩过的坑和解决方案

问题1:无限循环地狱

jsx 复制代码
// ❌ 错误写法:缺少依赖
useEffect(() => {
  setCount(count + 1); // 会导致无限重新渲染!
});

// ✅ 正确写法1:使用函数式更新
useEffect(() => {
  setCount(prevCount => prevCount + 1);
}, []);

// ✅ 正确写法2:添加完整依赖
useEffect(() => {
  // 一些操作
}, [count]); // 明确声明依赖

问题2:异步操作竞争条件

jsx 复制代码
useEffect(() => {
  let isActive = true;
  
  const fetchData = async () => {
    const result = await fetchSomeData(id);
    
    // 防止组件已卸载时设置状态
    if (isActive) {
      setData(result);
    }
  };
  
  fetchData();
  
  return () => {
    isActive = false;
  };
}, [id]);

🎯 四、实用技巧:自定义Hook封装

我把常用的useEffect模式封装成了自定义Hook:

jsx 复制代码
// 封装数据获取逻辑
const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  return { data, loading, error };
};

// 使用示例
const UserList = () => {
  const { data, loading, error } = useFetch('/api/users');
  
  if (loading) return <div>加载中...</div>;
  if (error) return <div>出错啦: {error}</div>;
  
  return (
    <div>
      {data.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
};

📝 五、总结:useEffect最佳实践

  1. 明确依赖:认真填写依赖数组,避免无限循环
  2. 及时清理:返回清理函数,防止内存泄漏
  3. 分离关注点:多个不相关的副作用用多个useEffect
  4. 自定义Hook:复用逻辑,保持组件简洁

从生命周期方法到useEffect,不仅是API的变化,更是编程思维的进化。刚开始可能需要适应,但一旦掌握,你就会发现函数组件的简洁和强大。

💪 动手试试吧:下次写组件时,尝试用useEffect重构旧代码,你会发现代码变得更清晰易维护!

⭐ 写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

相关推荐
brzhang2 小时前
当AI接管80%的执行,你“不可替代”的价值,藏在这20%里
前端·后端·架构
江城开朗的豌豆2 小时前
React组件传值:轻松掌握React组件通信秘籍
前端·javascript·react.js
Sailing2 小时前
别再放任用户乱填 IP 了!一套前端 IP 与 CIDR 校验的高效方案
前端·javascript·面试
程序员爱钓鱼5 小时前
Go语言实战案例 — 项目实战篇:简易博客系统(支持评论)
前端·后端·go
excel12 小时前
ES6 中函数的双重调用方式:fn() 与 fn\...``
前端
可乐爱宅着12 小时前
全栈框架next.js入手指南
前端·next.js
你的人类朋友14 小时前
什么是API签名?
前端·后端·安全
会豪16 小时前
Electron-Vite (一)快速构建桌面应用
前端
中微子16 小时前
React 执行阶段与渲染机制详解(基于 React 18+ 官方文档)
前端