深入理解 React useEffect

一、useEffect基礎概念

1、什么是副作用(Side Effects)?
在React中,副作用是指那些與組件渲染結果無關的操作,例如:

  • 數據獲取(API調用)
  • 手動修改DOM
  • 設置訂閱或定時器
  • 記錄日志

2、useEffect的基本語法

import { useEffect } from 'react';function MyComponent() {useEffect(() => {// 副作用邏輯在這里執行return () => {// 清理函數(可選)};}, [dependency1, dependency2]); // 依賴數組(可選)
}

二、useEffect的三種適用方式

1、每次渲染后都執行

useEffect(() => {// 每次組件渲染后都會執行console.log('組件已渲染或更新');
});

2、僅在掛載時執行一次

useEffect(() => {// 只在組件掛載時執行一次console.log('組件已掛載');return () => {// 清理函數,在組件卸載時執行console.log('組件即將卸載');};
}, []); // 空依賴數組

3、依賴特定值變化時執行

useEffect(() => {// 當 count 或 name 變化時執行console.log(`Count: ${count}, Name: ${name}`);return () => {// 清理上一次的 effectconsole.log('清理上一次的 effect');};
}, [count, name]); // 依賴數組

三、useEffect執行機制詳解

請添加圖片描述

四、常見使用場景

1、數據獲取

import { useState, useEffect } from 'react';function UserProfile({ userId }) {const [user, setUser] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {// 重置狀態setLoading(true);setError(null);const fetchUser = async () => {try {const response = await fetch(`/api/users/${userId}`);const userData = await response.json();setUser(userData);} catch (err) {setError(err.message);} finally {setLoading(false);}};fetchUser();// 不需要清理函數,因為 fetch 會自動取消}, [userId]); // 當 userId 變化時重新獲取if (loading) return <div>加載中...</div>;if (error) return <div>錯誤: {error}</div>;return (<div><h1>{user.name}</h1><p>{user.email}</p></div>);
}

2、事件監聽器

function WindowSizeTracker() {const [windowSize, setWindowSize] = useState({width: window.innerWidth,height: window.innerHeight});useEffect(() => {const handleResize = () => {setWindowSize({width: window.innerWidth,height: window.innerHeight});};// 添加事件監聽window.addEventListener('resize', handleResize);// 清理函數:移除事件監聽return () => {window.removeEventListener('resize', handleResize);};}, []); // 空數組表示只在掛載/卸載時執行return (<div>窗口尺寸: {windowSize.width} x {windowSize.height}</div>);
}

3、定時器

function Timer() {const [seconds, setSeconds] = useState(0);useEffect(() => {const intervalId = setInterval(() => {setSeconds(prevSeconds => prevSeconds + 1);}, 1000);// 清理函數:清除定時器return () => {clearInterval(intervalId);};}, []); // 空依賴數組,只在掛載時設置定時器return <div>已運行: {seconds}</div>;
}

4、手動操作DOM

function FocusInput() {const inputRef = useRef(null);useEffect(() => {// 組件掛載后自動聚焦輸入框if (inputRef.current) {inputRef.current.focus();}}, []); // 空數組表示只在掛載時執行return <input ref={inputRef} placeholder="自動聚焦" />;
}

五、依賴數組的詳細說明

1、依賴數組的規則

// ? 正確:包含所有依賴
useEffect(() => {document.title = `${title} - ${count} 次點擊`;
}, [title, count]); // 所有依賴都聲明// ? 錯誤:缺少依賴
useEffect(() => {document.title = `${title} - ${count} 次點擊`;
}, [title]); // 缺少 count 依賴// ? 正確:使用函數式更新避免依賴
useEffect(() => {const timer = setInterval(() => {setCount(prevCount => prevCount + 1); // 不需要 count 依賴}, 1000);return () => clearInterval(timer);
}, []); // 空依賴數組

2、處理對象和函數依賴

function UserProfile({ user }) {// 使用 useMemo 記憶化對象const userStatus = useMemo(() => ({isActive: user.active,statusText: user.active ? '活躍' : '非活躍'}), [user.active]); // 只有當 user.active 變化時重新計算// 使用 useCallback 記憶化函數const updateUser = useCallback((updates) => {// 更新用戶邏輯}, [user.id]); // 依賴 user.iduseEffect(() => {// 使用記憶化的值和函數console.log(userStatus);updateUser({ lastLogin: new Date() });}, [userStatus, updateUser]); // 依賴記憶化的值return <div>用戶狀態: {userStatus.statusText}</div>;
}

六、useEffect的進階用法

1、多個useEffect的使用

function ComplexComponent({ userId, autoRefresh }) {const [user, setUser] = useState(null);const [notifications, setNotifications] = useState([]);// 獲取用戶數據useEffect(() => {fetchUser(userId).then(setUser);}, [userId]);// 獲取通知(依賴用戶數據)useEffect(() => {if (user) {fetchNotifications(user.id).then(setNotifications);}}, [user]); // 依賴 user// 自動刷新通知useEffect(() => {if (!autoRefresh || !user) return;const intervalId = setInterval(() => {fetchNotifications(user.id).then(setNotifications);}, 30000);return () => clearInterval(intervalId);}, [autoRefresh, user]); // 依賴 autoRefresh 和 userreturn (<div>{/* 渲染邏輯 */}</div>);
}

2、適用自定義Hook封裝useEffect

// 自定義 Hook:使用防抖的搜索
function useDebounce(value, delay) {const [debouncedValue, setDebouncedValue] = useState(value);useEffect(() => {const handler = setTimeout(() => {setDebouncedValue(value);}, delay);return () => {clearTimeout(handler);};}, [value, delay]); // 依賴 value 和 delayreturn debouncedValue;
}// 在組件中使用
function SearchComponent() {const [query, setQuery] = useState('');const [results, setResults] = useState([]);const debouncedQuery = useDebounce(query, 500);useEffect(() => {if (debouncedQuery) {searchAPI(debouncedQuery).then(setResults);} else {setResults([]);}}, [debouncedQuery]); // 依賴防抖后的查詢return (<div><inputvalue={query}onChange={(e) => setQuery(e.target.value)}placeholder="搜索..."/><ul>{results.map(result => (<li key={result.id}>{result.name}</li>))}</ul></div>);
}

七、常見問題與解決方案

1、無限循環問題

// ? 錯誤:導致無限循環
const [count, setCount] = useState(0);useEffect(() => {setCount(count + 1); // 每次渲染都會更新 count,觸發重新渲染
}, [count]); // 依賴 count// ? 正確:使用函數式更新或無依賴
useEffect(() => {setCount(prevCount => prevCount + 1); // 不依賴外部 count 值
}, []); // 空依賴數組

2、異步操作處理

function AsyncComponent() {const [data, setData] = useState(null);useEffect(() => {let isMounted = true; // 跟蹤組件是否掛載const fetchData = async () => {try {const result = await fetch('/api/data');const jsonData = await result.json();if (isMounted) {setData(jsonData); // 只在組件仍掛載時更新狀態}} catch (error) {if (isMounted) {console.error('獲取數據失敗:', error);}}};fetchData();return () => {isMounted = false; // 組件卸載時設置為 false};}, []);return <div>{data ? data.message : '加載中...'}</div>;
}

3、依賴函數的問題

function ProblematicComponent() {const [count, setCount] = useState(0);const logCount = () => {console.log('當前計數:', count);};// ? 問題:logCount 在每次渲染都是新函數useEffect(() => {logCount();}, [logCount]); // 導致每次渲染都執行// ? 解決方案1:將函數移到 useEffect 內部useEffect(() => {const logCount = () => {console.log('當前計數:', count);};logCount();}, [count]); // 只依賴 count// ? 解決方案2:使用 useCallback 記憶化函數const logCountMemoized = useCallback(() => {console.log('當前計數:', count);}, [count]); // 依賴 countuseEffect(() => {logCountMemoized();}, [logCountMemoized]); // 依賴記憶化的函數return <button onClick={() => setCount(c => c + 1)}>增加</button>;
}

八、性能優化技巧

1、條件執行Effect

function ExpensiveComponent({ data, shouldProcess }) {useEffect(() => {if (shouldProcess) {// 只有 shouldProcess 為 true 時才執行昂貴操作performExpensiveOperation(data);}}, [data, shouldProcess]); // 仍然聲明所有依賴
});

2、使用useMemo優化依賴

function OptimizedComponent({ items, filter }) {// 使用 useMemo 避免不必要的重新計算const filteredItems = useMemo(() => {return items.filter(item => item.includes(filter));}, [items, filter]); // 只有當 items 或 filter 變化時重新計算// effect 只依賴記憶化的值useEffect(() => {console.log('過濾后的項目:', filteredItems);}, [filteredItems]); // 依賴記憶化的數組return (<ul>{filteredItems.map(item => (<li key={item}>{item}</li>))}</ul>);
}

3、避免不必要的Effect

// ? 不必要的 effect:可以在渲染期間直接計算
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');useEffect(() => {setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);// ? 更好的方案:在渲染期間直接計算
const fullName = `${firstName} ${lastName}`;

九、最佳實踐總結

1、明確依賴: 始終聲明所有effect中使用的依賴項
2、適當清理: 對于訂閱、定時器等,一定要提供清理函數
3、分離關注點: 使用多個useEffect分離不同的邏輯
4、避免無限循環: 謹慎設置狀態,避免創建渲染循環
5、性能優化: 使用useMemo和useCallback優化依賴項
6、條件執行: 在effect內部添加條件判斷,避免不必要的執行
7、異步處理: 正確處理異步操作的清理和競態條件

總結

useEffect 是React函數組件的核心Hook,它使得副作用管理變得更加聲明式和可預測。通過理解其執行機制、正確使用依賴數組、實現適當的清理邏輯,你可以編寫出高效、可靠的React組件。

記住,useEffect 的核心思想是將副作用與渲染邏輯分離,讓組件更專注于渲染UI,而將副作用操作放在統一的地方管理。這種分離使得代碼更容易理解、測試和維護。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/96692.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/96692.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/96692.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Yapi中通過MongoDB修改管理員密碼與新增管理員

如何在Docker部署的Yapi中通過MongoDB修改管理員密碼與新增管理員便捷管理Yapi權限&#xff1a;無需前端重置&#xff0c;直接通過數據庫操作修改密碼及添加管理員一、進入MongoDB容器并連接數據庫 首先&#xff0c;通過以下命令進入運行中的MongoDB容器&#xff1a; docker ex…

【EasyR1】GRPO訓練

官方github&#xff1a;https://github.com/hiyouga/EasyR1 參考&#xff1a;https://opendeep.wiki/hiyouga/EasyR1/quickstart 代碼和環境配置 github&#xff1a;https://github.com/hiyouga/EasyR1 新建一個虛擬環境&#xff1a; python -m venv easyr1 source easyr1/b…

2025年KBS SCI1區TOP,新穎獎勵與ε-貪婪衰減Q-learning算法+局部移動機器人路徑規劃,深度解析+性能實測

目錄1.摘要2.新穎獎勵與ε-貪婪衰減Q-learning算法3.結果展示4.參考文獻5.代碼獲取6.算法輔導應用定制讀者交流1.摘要 路徑規劃是移動機器人的核心任務&#xff0c;需要在高效導航的同時規避障礙。本文提出了一種改進Q-learning算法——定制化獎勵與ε-貪婪衰減Q-learning&…

運行npm run命令報錯“error:0308010C:digital envelope routines::unsupported”

下載的前后端分離架構的開源項目&#xff0c;運行“npm run serve”命令啟動前端服務時報錯“error:0308010C:digital envelope routines::unsupported”&#xff0c;這個錯誤通常是由于Node.js版本與項目依賴不兼容導致的&#xff0c;特別是在Node.js v17版本中&#xff0c;百…

AI計算提效關鍵。自適應彈性加速,基于存算架構做浮點運算

一、自適應彈性加速是提升芯片能效比的有力手段自適應彈性加速技術是現代芯片設計中提升能效比的關鍵路徑之一。它摒棄了傳統芯片在設計時采用的靜態、固化的資源分配與功能設定模式&#xff0c;通過引入動態調整機制&#xff0c;使得芯片能夠根據實時的應用需求和負載變化&…

Spring Boot測試陷阱:失敗測試為何“傳染”其他用例?

一個測試失敗&#xff0c;為何“傳染”其他測試&#xff1f;——Spring Boot 單元測試獨立性與泛型陷阱實戰解析 &#x1f6a9; 問題背景 在日常開發中&#xff0c;我們常會遇到這樣的場景&#xff1a; 正在開發新功能 A&#xff0c;寫了一個 testFeatureA() 測試方法&#xff…

Web開發中的CGI:通用網關接口詳解

一、CGI的設計意圖&#xff1a;解決Web的"靜態"困境 在CGI出現之前&#xff0c;Web服務器只能做一件事&#xff1a;返回預先寫好的靜態文件&#xff08;HTML、圖片等&#xff09;。每個用戶看到的內容都是一模一樣的。 設計意圖很簡單但卻革命性&#xff1a; 讓Web服…

在 SSMS 中查找和打開已保存的查詢文件

在 SSMS 中查找和打開已保存的查詢文件 在 SQL Server Management Studio (SSMS) 中&#xff0c;您可以輕松地查找并打開已保存的查詢文件&#xff08;通常以 .sql 擴展名保存&#xff09;。SSMS 提供了直觀的界面支持直接打開這些文件&#xff0c;無需額外工具。以下是詳細步驟…

Protues使用說明及Protues與Keil聯合仿真實現點亮小燈和流水燈

目錄 1Protues介紹及新建工程 1.1進入軟件 1.2文件創建 1.3默認選項 1.5設計面板 1.6添加元器件 1.7終端模式 1.8激勵源模式 1.9探針模式 1.10儀表 1.11二維直線 1.12字符 2 Protues電路原理圖仿真 2.1 220V交流電轉5V直流電穩壓電路仿真原理圖 2.1.1 仿真原理圖…

Linux PCI 子系統:工作原理與實現機制深度分析

Linux PCI 子系統&#xff1a;工作原理與實現機制深度分析 1. Linux PCI 子系統基礎概念 1.1 PCI/PCIe 基礎概念回顧 總線拓撲&#xff1a; PCI/PCIe 系統是一個樹形結構。CPU 連接到 Root Complex (RC)&#xff0c;RC 連接至 PCIe 交換機 (Switch) 和 PCIe 端點設備 (Endpoint…

RabbitMQ 全面指南:架構解析與案例實戰

目錄一、RabbitMQ 簡介1.1 什么是 RabbitMQ1.2 RabbitMQ 的核心組件1.3 RabbitMQ 的應用場景二、環境搭建2.1 安裝 RabbitMQ2.2 安裝 Erlang2.3 配置 RabbitMQ三、RabbitMQ 核心概念與工作原理3.1 消息模型3.2 交換機類型3.3 隊列特性3.4 消息確認機制四、Spring Boot 集成 Rab…

6.2 el-menu

一、 <el-menu>: 菜單組件&#xff0c;定義了側邊欄內部的具體導航項、層級結構和交互行為。<el-container><!-- 側邊欄容器 --><el-aside width"200px"><!-- 菜單內容 --><el-menu default-active"1" class"el-men…

Windows 筆記本實現僅關屏仍工作:一種更便捷的 “偽熄屏” 方案

在使用 Windows 筆記本作為臨時服務器或需要后臺持續運行程序時&#xff0c;我們常面臨一個需求&#xff1a;關閉屏幕以節省電量或減少光污染&#xff0c;同時保持系統正常工作。然而&#xff0c;網絡上流傳的諸多方法往往存在局限&#xff0c;要么無法兼顧 “熄屏” 與 “工作…

Linux應急響應一般思路(二)

進程排查進程(Process)是計算機中的程序關于某數據集合上的一次運行活動&#xff0c;是系統進行資源分配和調度的基本單位&#xff0c;是操作系統結構的基礎無論是在Windows系統還是Linux系統中&#xff0c;主機在感染惡意程序后&#xff0c;惡意程序都會啟動相應的進程&#x…

基于 SkyWalking + Elasticsearch + Grafana 的可落地調用鏈監控方案

這個方案成熟穩定、社區活躍、部署相對簡單,非常適合中小型團隊作為第一代調用鏈系統落地。 一、核心組件選型與角色 組件 版本建議 角色 優點 Apache SkyWalking v9.x+ 核心平臺 (采集、分析、存儲、UI) 國產優秀,Java Agent無侵入接入,功能全面,性能損耗低 Elasticsearc…

APP逆向——某站device-id參數

免責聲明本博客所涉及的 爬蟲技術、逆向分析方法 僅用于 學習、研究和技術交流。文中所有示例代碼、工具和方法&#xff0c;均不得用于以下行為&#xff1a;未經授權的數據采集侵犯他人知識產權干擾或破壞正常業務系統任何違反國家法律法規的行為因讀者將本教程內容用于 非法用…

C/C++數據結構之循環鏈表

概述循環鏈表本質上也是一個單向或雙向鏈表&#xff0c;但其最后一個節點的指針并不指向NULL&#xff0c;而是指向鏈表的第一個節點&#xff0c;從而形成一個閉合的環。這種結構使得在遍歷鏈表時&#xff0c;可以從任意一個節點開始&#xff0c;并最終回到起始點。音樂播放軟件…

Mongodb的教程

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 前言 一、mongodb是什么&#xff1f; 二、mongodb的下載與安裝教程 三、mongodb的常見操作 總結 前言 在當今數據驅動的世界中&#xff0c;數據庫技術是構建高效…

MySQL視圖有什么用?一文讀懂虛擬表的六大核心價值

引言 在數據庫開發中&#xff0c;你是否遇到過這樣的困境&#xff1a;業務人員需要查看復雜關聯數據卻難以理解多表JOIN&#xff0c;或需要限制某些用戶只能訪問特定字段&#xff1f;MySQL視圖正是為此設計的"數據透視鏡"。本文將通過官方定義、典型場景和最佳實踐&a…

ubuntu24.04 frps服務器端自動啟動設置【2025-08-20】

Ubuntu 24.04采用systemd作為默認的init系統&#xff0c;我們可以通過創建systemd服務單元文件來實現開機自啟動。以下是具體實施步驟&#xff1a;創建服務文件使用文本編輯器創建服務配置文件&#xff1a;sudo nano /etc/systemd/system/frps.service編寫服務配置內容在文件中…