副作用:和外部有交互
- 引用外部变量
- 调用外部函数
- 修改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