什么是副作用操作?
useEffect 用于编写由渲染本身引起的对接组件外部的操作(官方称呼为:副作用操作)
以下情况会触发页面渲染
- 初次加载页面(组的挂载)
- 响应式变量发生变化,触发页面根据新值重新渲染(组件更新)
- 关闭页面(组件卸载)
以下情况需要添加副作用操作
- 页面初步渲染完成后,向服务器获取数据完成页面的最终渲染
- 响应式变量发生变化时,先根据新值执行必要的业务逻辑,再进行最终的页面更新渲染
- 关闭页面时,需清除副作用,如清除定时器,移除全局事件等。
useEffect 语法
useEffect 是 hook 函数
一个参数(必要): 自定义的处理函数(官方称呼为:副作用函数),可在副作用函数中 return 一个函数来清除副作用,从第二次执行副作用函数开始,每次都会先执行return中的清除副作用的函数,再执行副作用函数,当组件卸载时,所有清除副作用的函数会按顺序依次执行。
第二个参数(可选): 依赖项 (不能为对象/数组等引用类型的数据,因为引用类型的数据每次页面渲染时都会生成新地址,若useEffect的处理函数会引发页面重新渲染,则会导致死循环)
无依赖项
执行副作用函数的时机
- 组件初次渲染后(组件挂载完成),在开发环境,会执行两次,生产环境仅执行一次
- 组件更新渲染前(任一响应式变量变化都会触发!)
import { useEffect } from "react"; function Demo() { useEffect(() => { console.log("执行了副作用函数"); }); return ( <> <div>你好</div> </> ); } export default Demo;
依赖项为空数组 []
类似 vue 的生命周期 mounted
执行副作用函数的时机
- 组件初次渲染后(组件挂载完成),在开发环境,会执行两次,生产环境仅执行一次
import { useEffect } from "react"; function Demo() { useEffect(() => { console.log("执行了副作用函数"); }, []); return ( <> <div>你好</div> </> ); } export default Demo;
使用场景
初次渲染页面时访问接口加载页面数据
import { useEffect, useState } from "react"; import axios from "axios"; function Demo() { const [list, setList] = useState([]); useEffect(() => { async function getList() { const res = await axios.get("http://localhost:3000/dataList"); setList(res.data); } getList(); }, []); return ( <> {list.map((item) => ( <div key={item.id}>{item.title}</div> ))} </> ); } export default Demo;
依赖项为响应式变量构成的数组
类似 vue 的立即执行侦听器 watch
执行副作用函数的时机
- 组件初次渲染后(组件挂载完成),在开发环境,会执行两次,生产环境仅执行一次
- 响应式变量发生变化触发页面进行更新渲染前
import { useEffect, useState } from "react"; function Demo() { const [name, setName] = useState("朝阳"); useEffect(() => { // 在挂载和 name 更新时,都会执行! console.log("当前name值为:", name); }, [name]); function changeName() { setName("晚霞"); } return ( <> <button onClick={changeName}>改名字</button> </> ); } export default Demo;
清除副作用
最经典的场景即在组件卸载时清除计时器,以免内存泄露
清除副作用的逻辑,写在 useEffect 内回调函数的 return 后面,需要注意的是,return 后必须是一个函数!
father.jsx
import { useState } from "react"; import Child from "./child.jsx"; function Father() { const [ifChild, setIfChild] = useState(true); function removeChild() { setIfChild(false); } return ( <> <button onClick={removeChild}>移除子组件</button> {ifChild && <Child />} </> ); } export default Father; iimport { useState } from "react"; import Child from "./child.jsx"; function Father() { const [ifChild, setIfChild] = useState(true); function removeChild() { setIfChild(false); } return ( <> <button onClick={removeChild}>移除子组件</button> {ifChild && <Child />} </> ); } export default Father;
child.jsx
import { useEffect } from "react"; function Child() { useEffect(() => { // 添加定时器 const timer = setInterval(() => { console.log("执行了定时器"); }, 1000); return () => { // 清除定时器 clearInterval(timer); }; }); return ( <> <div> <h1>我是子组件</h1> </div> </> ); } export default Child;
如何关闭开发环境执行两次 ?
将 src/main.jsx 中的 <React.StrictMode> </React.StrictMode>
标签删除即可。
useEffect 演示代码
可通过下方演示代码,在控制台监听查看父子组件副作用函数和清除副作用函数的执行顺序和时机
father.jsx
import { useEffect, useState } from "react"; import Child from "./Child.jsx"; export default function Father() { const [showChild, setShowChild] = useState(true); const [num, setNum] = useState(0); console.log("父组件渲染"); useEffect(() => { console.log("执行了父组件依赖[]的useEffect内的代码"); return () => { console.log("执行了父组件依赖[]的useEffect中return的函数"); }; }, []); return ( <div style={{ border: "1px solid", padding: "10px", margin: "10px" }}> <h1>父组件</h1> <p>num为:{num}</p> <button onClick={() => { setNum(num + 1); }} > +1 </button> {showChild && <Child />} <button onClick={() => { setShowChild(false); }} > 卸载子组件 </button> <button onClick={() => { setShowChild(true); }} > 挂载子组件 </button> </div> ); }
Child.jsx
import { useEffect, useState } from "react"; function Child() { const [name, setName] = useState("朝阳"); console.log("子组件渲染"); useEffect(() => { console.log("执行了子组件无依赖的useEffect内的代码"); return () => { console.log("执行了子组件无依赖的useEffect中return的函数"); }; }); useEffect(() => { console.log("执行了子组件依赖 [] 的useEffect内的代码"); return () => { console.log("执行了子组件依赖 [] 的useEffect中return的函数"); }; }, []); useEffect(() => { console.log("执行了子组件中依赖name的useEffect内的代码"); return () => { console.log("执行了子组件中依赖name的useEffect中return的函数"); }; }, [name]); return ( <div style={{ border: "1px solid", padding: "10px", margin: "10px" }}> <h1>子组件</h1> <p>名称为:{name}</p> <button onClick={() => setName("张三")}>改名称</button> </div> ); } export default Child;