1. 认识useEffect
image.png◼ 目前我们已经通过hook在函数式组件中定义state,那么类似于生命周期这些呢?
Effect Hook 可以让你来完成一些类似于class中生命周期的功能;
事实上,类似于网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(Side Effects);
所以对于完成这些功能的Hook被称之为 Effect Hook;
import React, { memo } from 'react'
import { useState, useEffect } from 'react'
const App = memo(() => {
const [count, setCount] = useState(200)
//
document.title = count
return (
<div>
<h2>当前计数: {count}</h2>
<button onClick={e => setCount(count+1)}>+1</button>
</div>
)
})
export default App
//每次点击按钮文档名字都会改变。
//这个代码是放在整个函数组件的基本逻辑里面是不合适的,因为函数式组件的目的是要告诉react你要帮我渲染的是一个什么样的内容。上面的useState是为了return里面的jsx里面来服务的,来提供数据的。
但是 document.title = count是属于当前组件在完成渲染任务之外,额外的副作用的东西,这里,要告诉react你要渲染的东西,还要额外的修改document的标题。
这个东西是这个执行主逻辑之外的一个副作用的东西。放在主逻辑里面不是不可以,只是不太合适。希望把它放在一个独立的地方,去给他完成一个对应的操作。
2.使用
把副作用的东西放到useEffct里面。
useEffct是一个函数。本身接受一个回调函数。
一旦组件渲染完成,就会执行里面的回调函数。
import React, { memo } from 'react'
import { useState, useEffect } from 'react'
const App = memo(() => {
const [count, setCount] = useState(200)
useEffect(() => {
// 当前传入的回调函数会在组件被渲染完成后, 自动执行
// 网络请求/DOM操作(修改标题)/事件监听
document.title = count
})
return (
<div>
<h2>当前计数: {count}</h2>
<button onClick={e => setCount(count+1)}>+1</button>
</div>
)
})
export default App
这个组件的目的是告诉react你要渲染的内容, const [count, setCount] = useState(200)是直接给我渲染的内容来服务的,提供数据的。副作用的东西用useEffct包裹起来了。执行完return后面,渲染以后,再执行useEffect里面的回调函数(副作用的逻辑)。
3.解析
useEffect的解析:
通过useEffect的Hook,可以告诉React需要在渲染后执行某些操作;
useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数;
默认情况下(useEffct里面只有一个参数,就算一个回调函数),无论是第一次渲染之后,还是每次更新之后,都会执行这个 回调函数;
相当于componentDidMount+componentDidUpdate
4. 需要清除Effect
image.png◼ 在class组件的编写过程中,某些副作用的代码,我们需要在componentWillUnmount中进行清除:
比如我们之前的事件总线或Redux中手动调用subscribe;
都需要在componentWillUnmount有对应的取消订阅;
Effect Hook通过什么方式来模拟componentWillUnmount呢?
import React, { memo, useEffect } from 'react'
import { useState } from 'react'
const App = memo(() => {
const [count, setCount] = useState(0)
// 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码
useEffect(() => {
// 1.监听事件
// const unubscribe = store.subscribe(() => {
// })
// function foo() {
// }
// eventBus.on("why", foo)
console.log("监听redux中数据变化, 监听eventBus中的why事件")
// 返回值: 回调函数 => 组件被重新渲染或者组件卸载的时候执行
return () => {
console.log("取消监听redux中数据变化, 取消监听eventBus中的why事件")
}
})
return (
<div>
<button onClick={e => setCount(count+1)}>+1({count})</button>
</div>
)
})
export default App
componentWillUnmount
useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B:
type EffectCallback = () => (void | (() => void | undefined));
◼ 为什么要在 effect 中返回一个函数?
这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数;
如此可以将添加和移除订阅的逻辑放在一起;
它们都属于 effect 的一部分;
◼ React 何时清除 effect?
React 会在组件更新和卸载的时候执行清除操作;
正如之前学到的,effect 在每次渲染的时候都会执行;
useEffect(fn)
fn会在第一次渲染组件和每次更新渲染的时候被回调。
里面的fn有回调的化,依然是和fn搭配执行,
fn里面打印1,retun的函数打印2,
首次组件渲染,只会打印1.
每次渲染的时候两个都会执行。21 21 21 21 总是成对出现的.
因为每次监听的时候我都需要把以前的监听取消掉。
但是返回值的回调也会在组件卸载的时候被执行。
返回值: 回调函数 => 组件被重新渲染或者组件卸载的时候执行
组件一般只会mount一次,就是第一次渲染的时候,而且是不会unmount的,除非强制ReactDOM.unmountComponentAtNode(document.getElementById("test"))
父组件重新渲染的时候,子组件不会重新mount,只是发生了更新,哪怕是里面retrun的东西什么都没变。每次父组件重新渲染的时候,子组件的componentDidUpdate就会被调用。
以前类组件里面,监听放在didmount里面,取消componentWillUnmount里面,没放在一起。现在用了hook,都放在useEffect里面。提高了内聚性。方便对代码进行管理。
多个useEffct的使用
image.png会按照书写顺序,依次执行。
import React, { memo, useEffect } from "react";
import { useState } from "react";
import EntireWrapper from "./style";
const Entire = memo(() => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("1");
return () => {
console.log("2");
};
});
useEffect(() => {
console.log("1-1");
return () => {
console.log("2-2");
};
});
useEffect(() => {
console.log("1-1-1");
return () => {
console.log("2-2-2");
};
});
return (
<EntireWrapper>
<div>
<button onClick={(e) => setCount(count + 1)}>+1({count})</button>
</div>
</EntireWrapper>
);
});
export default Entire;
首次渲染
1
1-1
1-1-1
点击按钮
2
2-2
2-2-2
1
1-1
1-1-1
每个useEffect里面写不同的逻辑
第一个写对dom的监听
第二个写对redux的监听
第三个写eventbus的监听
好处:
1.每个有单独的逻辑,好管理
2.可以把每个逻辑抽取到单独的自定义hook里面。就可以进行复用。以前的类组件是没办法分离的。
useEffect性能优化
image.png我们有些副作用,只需要执行一次。每次更新数据,都去拿,并不需要。对服务器有压力。
某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网络请求、订阅和取消订阅);如何让一个回调只能执行一次呢?
需要传入第二个参数
参数二:该useEffect在哪些state发生变化时,才重新执行;(受谁的影响)
如果传入空数组的化,就代表我第一个回调函数谁的影响都不收,只在第一次渲染的时候执行一次。
里面return的函数也只有再组件被卸载的时候才会执行。
这个时候就是 componentDidMount和componentWillUnmount这两个函数。
第二次参数写了[count],就代表只有count改变的时候,这个回调函数才会执行。
useEffect可以模拟以前的声明周期,但是比以前的声明周期更强大。
总结
useEffect里面的函数,
只有一个参数
1.第一次渲染和每次更新都会执行。
2.return的函数,每次更新的时候执行,而且是先执行输出2,后执行监听(回调函数里面的)的东西输出1。不会再首次渲染的时候执行。
3.return的函数会在组件被卸载的时候执行。
有第二个参数
1.参数是一个空数组,函数只有再第一次渲染的时候才会执行。里面return的函数也只有在卸载的时候才会执行。
2.参数是一个state的化,就只有在state更新的时候会执行。
总结:useEffect执行副作用,可以模拟生命周期,比生命周期更强大。
网友评论