有些組件需要與外部系統同步。例如,你可能希望根據 React state 控制非 React 組件、設置服務器連接或在組件出現在屏幕上時發送分析日志。Effects 會在渲染后運行一些代碼,以便可以將組件與 React 之外的某些系統同步。
簡單理解,就是需要操作外部非React元素,但React未渲染完時是不允許操作原生DOM的,所以需要一個類似渲染完成后的回調函數。其實也可以在root渲染完成后硬編碼實現,但這樣的話代碼顯的不工整了。
Effect使用
一個播放器的示例,注意React很多功能全是在開發環境中執行兩次,生產環境中執行一次,這主要是為了性能調優用的。
- 生產環境中:掛載-清理
- 開發環境中:掛載-清理-掛載
import { useState, useRef, useEffect } from 'react';function VideoPlayer({ src, isPlaying }) {const ref = useRef(null);useEffect(() => {if (isPlaying) { //增加判斷,防止每次更改界面都刷新console.log('Calling video.play()');ref.current.play();} else {console.log('Calling video.pause()');ref.current.pause();}return () => {connection.disconnect(); //清理函數,在組件卸載時執行};}, [isPlaying]); //這個參數后面有詳細解釋//ref引用return <video ref={ref} src={src} loop playsInline />;
}export default function App() {const [isPlaying, setIsPlaying] = useState(false);const [text, setText] = useState('');return (<><input value={text} onChange={e => setText(e.target.value)} /><button onClick={() => setIsPlaying(!isPlaying)}>{isPlaying ? 'Pause' : 'Play'}</button><VideoPlayerisPlaying={isPlaying}src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"/></>);
}
useEffect(() => {// 這里的代碼會在每次渲染后執行
});useEffect(() => {// 這里的代碼只會在組件掛載后執行
}, []);useEffect(() => {//這里的代碼只會在每次渲染后,并且 a 或 b 的值與上次渲染不一致時執行,這個特性非常重要,可以不需要參數來判斷是否要重新加載,其實也沒太大關系。
}, [a, b]);
利用effect訂閱事件
簡單的函數不建議這樣來做,可以單用ref即可,但這樣會比較規范一些。
useEffect(() => {function handleScroll(e) {console.log(window.scrollX, window.scrollY);}window.addEventListener('scroll', handleScroll);//組件卸載時執行,防止內存溢出return () => window.removeEventListener('scroll', handleScroll);
}, []);
利用effect初始化數據
比如遠程訪問獲取數據等
useEffect(() => {let ignore = false;async function startFetching() {const json = await fetchTodos(userId);if (!ignore) {setTodos(json);}}startFetching();return () => {ignore = true;};
}, [userId]);
昂貴的計算
如果上述遠程計算的時間會比較長的話,就不適合用effect來做了,可以用userMemo
來執行。這會告訴 React,除非 todos 或 filter 發生變化,否則不要重新執行傳入的函數。React 會在初次渲染的時候記住 getFilteredTodos() 的返回值。在下一次渲染中,它會檢查 todos 或 filter 是否發生了變化。如果它們跟上次渲染時一樣,useMemo 會直接返回它最后保存的結果。如果不一樣,React 將再次調用傳入的函數(并保存它的結果)。
import { useMemo, useState } from 'react';function TodoList({ todos, filter }) {const [newTodo, setNewTodo] = useState('');const visibleTodos = useMemo(() => {// 除非 todos 或 filter 發生變化,否則不會重新執行return getFilteredTodos(todos, filter);}, [todos, filter]);// ...
}
編寫 Effect 需要遵循以下三個規則
- 聲明 Effect。默認情況下,Effect 會在每次 commit 后都會執行。
- 指定 Effect 依賴。大多數 Effect 應該按需執行,而不是在每次渲染后都執行。例如,淡入動畫應該只在組件出現時觸發。連接和斷開服務器的操作只應在組件出現和消失時,或者切換聊天室時執行。文章將介紹如何通過指定依賴來控制如何按需執行。
- 必要時添加清理(cleanup)函數。有時 Effect 需要指定如何停止、撤銷,或者清除它的效果。例如,“連接”操作需要“斷連”,“訂閱”需要“退訂”,“獲取”既需要“取消”也需要“忽略”。你將學習如何使用 清理函數 來做到這一切。
關于Effect的內容非常多,主要是這東西屬于脫圍機制的一種,而且還需要和React生命周期相吻合,還要考慮好性能問題。所以具體情況需要具體分析,無法統一下結論。需要多思考嘗試。