1. 什么是函数的副作用(Side Effects)
副作用是指在组件渲染过程中,除了返回 JSX 之外的其他利用,例如:
- 数据获取(API 调用)
- 订阅数据源
- 手动修改 DOM
- 设置定时器
- 存储数据
- 日志记录
纯函数是特定的输入只会有特定的输出,也就是说组件会输出特定的DOM给浏览器渲染,撤除这份逻辑以外的利用就称之为副作用,比如获取数据,监听,订阅等等
2. useEffect 的实行时机
2.1 省略依赖项
- useEffect(() => {
- console.log('每次渲染都会执行');
- }); // 没有依赖项数组
复制代码
2.2 指定依赖项
- useEffect(() => {
- console.log(`count 发生变化:${count}`);
- }, [count]); // 依赖于 count
复制代码
- 首次渲染时实行
- 依赖项发生变化时实行
- 多个依赖项时,任意一个变化都会触发实行
2.3 空数组依赖项
- useEffect(() => {
- console.log('只在组件挂载时执行一次');
- }, []); // 空数组
复制代码
- 仅在组件首次渲染(挂载)时实行一次
- 雷同于 class 组件的 componentDidMount
3. 常见问题和最佳实践
3.1 避免依赖项循环
- // ❌ 错误示例:造成无限循环
- function BadExample() {
- const [count, setCount] = useState(0);
-
- useEffect(() => {
- setCount(count + 1); // 直接修改依赖项
- }, [count]);
-
- return <div>{count}</div>;
- }
- // ✅ 正确示例:使用函数式更新
- function GoodExample() {
- const [count, setCount] = useState(0);
-
- useEffect(() => {
- setCount(prevCount => prevCount + 1);
- }, []); // 不需要依赖项
-
- return <div>{count}</div>;
- }
复制代码 3.2 分离关注点
- function UserProfile({ userId }) {
- const [user, setUser] = useState(null);
- const [posts, setPosts] = useState([]);
- // ✅ 分开声明不同功能的 useEffect
- useEffect(() => {
- // 获取用户信息
- fetchUser(userId).then(setUser);
- }, [userId]);
- useEffect(() => {
- // 获取用户帖子
- fetchUserPosts(userId).then(setPosts);
- }, [userId]);
- return (
- <div>
- <UserInfo user={user} />
- <UserPosts posts={posts} />
- </div>
- );
- }
复制代码 4. 清除副作用
4.1 清理函数的实行时机
清理函数会在以下情况实行:
4.2 变乱监听示例
- function WindowWidth() {
- const [width, setWidth] = useState(window.innerWidth);
-
- useEffect(() => {
- const handleResize = () => setWidth(window.innerWidth);
-
- // 添加事件监听
- window.addEventListener('resize', handleResize);
-
- // 清理函数
- return () => {
- window.removeEventListener('resize', handleResize);
- };
- }, []); // 空依赖数组,只在挂载和卸载时执行
- return <div>Window width: {width}</div>;
- }
复制代码 4.3 定时器示例
- function Timer() {
- const [count, setCount] = useState(0);
- useEffect(() => {
- const timer = setInterval(() => {
- setCount(c => c + 1);
- }, 1000);
- // 清理函数:组件卸载时清除定时器
- return () => clearInterval(timer);
- }, []); // 空依赖数组
- return <div>Count: {count}</div>;
- }
复制代码 4.4 数据订阅示例
- function DataSubscriber({ dataSource }) {
- const [data, setData] = useState(null);
- useEffect(() => {
- let isSubscribed = true;
- const handleData = (newData) => {
- if (isSubscribed) {
- setData(newData);
- }
- };
- // 订阅数据源
- const subscription = dataSource.subscribe(handleData);
- // 清理函数:取消订阅
- return () => {
- isSubscribed = false;
- subscription.unsubscribe();
- };
- }, [dataSource]); // 依赖于 dataSource
- return <div>{data ? <DataView data={data} /> : 'Loading...'}</div>;
- }
复制代码 4.5 WebSocket 连接示例
- function WebSocketComponent({ url }) {
- const [messages, setMessages] = useState([]);
- useEffect(() => {
- const ws = new WebSocket(url);
- ws.onmessage = (event) => {
- setMessages(prev => [...prev, event.data]);
- };
- // 清理函数:关闭 WebSocket 连接
- return () => {
- ws.close();
- };
- }, [url]);
- return (
- <div>
- {messages.map((msg, index) => (
- <div key={index}>{msg}</div>
- ))}
- </div>
- );
- }
复制代码 5. 现实应用场景
5.1 表单主动生存
- function AutoSaveForm() {
- const [content, setContent] = useState('');
- const [saving, setSaving] = useState(false);
- useEffect(() => {
- // 防抖处理
- const timeoutId = setTimeout(() => {
- if (content) {
- setSaving(true);
- saveContent(content)
- .then(() => setSaving(false));
- }
- }, 1000);
- return () => clearTimeout(timeoutId);
- }, [content]);
- return (
- <div>
- <textarea
- value={content}
- onChange={e => setContent(e.target.value)}
- />
- {saving && <span>Saving...</span>}
- </div>
- );
- }
复制代码 5.2 及时搜索
- function SearchComponent() {
- const [query, setQuery] = useState('');
- const [results, setResults] = useState([]);
- useEffect(() => {
- // 避免空查询
- if (!query.trim()) {
- setResults([]);
- return;
- }
- const abortController = new AbortController();
- async function fetchResults() {
- try {
- const response = await fetch(
- `/api/search?q=${query}`,
- { signal: abortController.signal }
- );
- const data = await response.json();
- setResults(data);
- } catch (error) {
- if (error.name === 'AbortError') {
- // 忽略中止的请求错误
- return;
- }
- console.error('搜索出错:', error);
- }
- }
- const timeoutId = setTimeout(fetchResults, 300);
- // 清理函数:取消请求和清除定时器
- return () => {
- clearTimeout(timeoutId);
- abortController.abort();
- };
- }, [query]);
- return (
- <div>
- <input
- value={query}
- onChange={e => setQuery(e.target.value)}
- placeholder="搜索..."
- />
- <ul>
- {results.map(result => (
- <li key={result.id}>{result.title}</li>
- ))}
- </ul>
- </div>
- );
- }
复制代码 6. 最佳实践总结
- 保持 effect 函数简便,专注于单一功能
- 公道利用依赖项,避免不须要的实行
- 始终清理副作用,防止内存泄漏
- 利用条件语句控制 effect 的实行
- 考虑利用自定义 Hook 封装常用的副作用逻辑
- 在开辟环境下利用 ESLint 的 exhaustive-deps 规则检查依赖项
- 利用 useCallback 和 useMemo 优化依赖项
通过公道利用 useEffect,我们可以优雅地处理组件的副作用,实现更复杂的交互逻辑,同时保持代码的可维护性和性能。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |