useEffect Hook 使用总结(可能是史上最强)
你真的懂闭包吗?
看下面这段代码,会打印什么?如果下面的代码没看懂,说明,说明你和我一样,没弄懂或者理解错了。。。
js
function TestFunctionComponent() {
let num = 0
function effect() {
num ++
const msg = `第${num}条消息`
return () => {
console.log(num);
console.log(msg)
}
}
return effect
}
const effect = TestFunctionComponent()
const unmount = effect()
// 模拟 react 组件的更新时,会重新执行 useEffect 函数。
effect()
effect()
effect()
unmount()答案:
如果 num 和 msg 分别是什么呢?如果是答案是 4 和第1条消息,那么恭喜你回答正确!
了解闭包后,再来看 React 函数式组件
定义一个 React 函数式组件
js
import { useState, useEffect } from "react";
export function ProjectList(props: any) {
const [count, setCount] = useState(0);
/*
接下来的代码
*/
return (
<>
<button onClick={() => setCount(count + 1)}> 改变 count 的值按钮 </button>
<button>{count}</button>
</>
);
}其实 useEffect 钩子的用法就 6 种,全都看懂了也就不那么难了。
它们分别是:
- 只传第一个回调参数 (函数类型)
- 第一个参数无返回函数
- 第一个参数有返回函数
- 传两个参
- 第一个是回调函数(无返回函数、有返回函数)
- 第二个是数组(空数组、有值数组)
下面,就对这些情况写一个小 demo,来加深理解。
1 只传第一个回调参数,不返回函数
不传第二个参数,也就说明每次组件更新时 不用比较更新前后的值是否相同,直接执行传入 useEffect 的回调参数。这种写法应用场景比较少,不过需要知道其表现。
js
// 执行 1+n 次(1 代表第一次挂载,n 代表组件更新的次数)
useEffect(() => {
// count 每次都会输出 0
console.log("打印 1+"+count+"次");
});2 只传第一个回调参数,返回函数
应用场景较少
js
// 执行 n+1 次(n 代表组件更新的次数,1 代表组件卸载了)
useEffect(() => {
return () => {
// count 每次都会输出 0
console.log("打印 1+n 次", count);
};
});3 传两个参,第一个参数不返回函数,第二个参为空数组
如果加了第二个参数数组, 也就意味着每次组件更新时,都要比较前后的值是否相同。只有不同时才会执行传入 useEffect 的回调参数。
应用场景:在组件挂载后发请求,将数据渲染到页面上。
js
// 执行 1 次。
// 如果第二个参数是空数组,意味着前后值的肯定是一样的,只会在组件挂载时执行一次,之后无论组件更新多少次,都不会触发。
useEffect(() => {
console.log("参数二 是空数组,只会在挂载时执行一次");
}, []);4 传两个参,第一个参数返回函数,第二个参为空数组
应用场景:组件卸载(更新不会触发)时发一个请求。
js
// 只会在组件卸载时打印一次。
useEffect(() => {
return () => {
console.log(count,'这里一定是 0,无论 count 加了多少');
}
},[])5 传两个参,第一个参数不返回函数,第二个参不为空数组
应用场景:监视响应式变化,触发回调
js
// 执行 1+n 次。
// 第二个参数有值,如果组件更新时传入数组前后的值有一个不相同,回调才会执行。基础数据比较值,引用数据会进行完全比较。
useEffect(() => {
console.log("参数二 是有值数组,会执行 1 + "+ count +" 次");
}, [count]);6 传两个参,第一个参数返回函数,第二个参不为空数组
应用场景:还没想到
js
// 打印 n+1 次。
useEffect(() => {
return () => {
console.log(count, '如果是更新,打印的是更新前的值(因为更新前要先卸载)。如果是卸载,获取到的是最新的值');
}
}, [count]) // 每当 count 变化,回调都会重新执行一遍,返回的函数也就形成了新的闭包光看不行,还得做个小案例
需求如下,写一个钩子,能改变网页的 title。当组件销毁时,网页的 title 需要恢复到原先的名字。
js
import { useEffect, useRef } from "react";
// export function usePageTitle(title: string) {
// const oldTitle = document.title;
// // 相当更新钩子
// useEffect(() => {
// document.title = title;
// }, [title])
// // 相当于卸载钩子
// useEffect(() => () => {
// document.title = oldTitle
// }, [])
// }
// 有一个钩子,能改变网页的 title。当组件销毁时,网页的 title 需要恢复到原先的名字。
export function usePageTitle(title: string) {
// 由于闭包,不使用 useRef 也可以。但是 useRef 更直观易懂。
// const oldTitle = document.title
// useRef 的作用就是保存参数,当函数式组件更新,被重新定义执行后,其值也不会改变
const oldTitle = useRef(document.title).current
// 相当更新钩子
useEffect(() => {
document.title = title;
}, [title])
// 相当于卸载钩子
useEffect(() => () => {
document.title = oldTitle
}, [])
}js
const [count, setCount] = useState(0);
usePageTitle(count + ' title')最后总结
如果要在组件的首次挂载、或卸载时执行某些操作(副作用),可以这样用 useEffect
js
useEffect(() => {
// ... 挂载操作
return () => {
// ... 卸载操作
}
}, [])如果要监视一些响应式数据,可以这样使用
js
useEffect(() => {
// ...
}, [param])