副作用:和外部有交互
- 引用外部變量
- 調用外部函數
- 修改dom、全局變量
- ajax
- 計時器(依賴window.setTimeout)
- 存儲相關
純函數:相同的輸入一定會得到相同的輸出
Effect Hook可以讓你在函數組件中執行副作用操作
類組件中處理副作用
- 在
componentDidMount
/componentDidUpdate
聲明周期中(真實dom構建以前)
useEffect執行時機
- 初次渲染之后 didMount(真實dom構建以后)
- 渲染更新時 didUpdate
- 是異步的,在回調函數中拿到更新的state
存在清理函數
- 首次執行: render → useEffect
- 再次執行: render → 清理函數 → useEffect
- 清理函數:組件更新、組件銷毀時執行
組件更新
useEffect(() => {console.log('useEffect')return () => {console.log('clear Effect')}
})
import { useState, useEffect } from 'react'
export default function App(props) {const [count, setCount] = useState(() => {console.log(1); // 惰性初始化,只會打印一次return 1});useEffect(() => {// 持續遞增console.log('useEffect')let timer = setInterval(() => { // 2. 每一次副作用都會重新初始化一個timersetCount(count + 1)}, 1000)return () => {clearInterval(timer) // 1.閉包 第二次運行時,先清理上一次的timerconsole.log('clear Effect')}})return (<><h1>{count}</h1></>)
}
組件銷毀
import { useState, useEffect } from 'react'
function Test() {const [count, setCount] = useState(1);useEffect(() => {console.log('useEffect')return () => {console.log('clear Effect') // 組件更新、銷毀時執行}})return (<><h1>{count}</h1><button onClick={() => setCount(count + 1)}>add</button></>)
}
export default function App() {const [show, setShow] = useState(true)return (<>{show && <Test />}<button onClick={() => setShow(!show)}>changeShow</button></>)
}
只在didMount時執行
依賴項
- 指定當前effect函數所需要的依賴項
- 若依賴項是
[]
,在初次渲染和卸載的時候執行 - 若依賴項不變,effect不執行
- 存在依賴項 && 依賴項更新時,effect執行
import { useState, useEffect } from 'react'
function Test() {const [count, setCount] = useState(1);useEffect(() => {console.log('useEffect')let timer = setInterval(() => { // didMount時執行一次// setCount(count + 1) // 若在依賴項中未填入count,則此時count拿到的一直是0!// 但填入count依賴不能解決“只在didMount時執行”的問題// 改成回調的方式,能獲取最新的countsetCount(count => count + 1)}, 1000)return () => {clearInterval(timer) // 組件銷毀時執行,didMount時不執行console.log('clear Effect')}}, []) // 增加了依賴項return (<><h1>{count}</h1><button onClick={() => setCount(count + 1)}>add</button></>)
}
export default function App() {const [show, setShow] = useState(true)return (<>{show && <Test />}<button onClick={() => setShow(!show)}>changeShow</button></>)
}
競態問題
- 接口返回的時長不同,后返回的覆蓋了之前的數據,導致沒有渲染正確的結果
現象:結果3覆蓋了4
import { useState, useEffect } from 'react'
const API = {async queryEmployeesByid(id) {return new Promise((resolve) => {setTimeout(() => {resolve({id,currentDepartment: `currentDepartment:${id}`})}, 300 * (10 - id))// id越大,返回越快,模擬后發的請求比先發的請求快})}
}
const Department = props => {let { id } = props;let [employees, setEmployees] = useState({})useEffect(() => {let didCancel = false; // 解決競態問題(async function fetchData() {let employee = await API.queryEmployeesByid(id)// 解決競態問題,最后一次點的時候先true再false,拿到對應id的請求結果if (!didCancel) {setEmployees(employee)}})()return () => { // 解決競態問題didCancel = true}}, [id])return (<>{employees.currentDepartment}</>)
}
const App = params => {let [id, setId] = useState(1)return (<><p>id:{id}</p><Department id={id} /><br /><button onClick={() => setId(id + 1)}>id++</button></>)
}
export default App