:::info 目前类组件存在的问题:
- 类组件中还存在比较冗余的写法,比如在进行事件监听的时候,需要在类组件的生命周期函数componentDidMount()中进行事件的订阅,在类组件的生命周期componentWillUnmount中移除相关的订阅函数,这个过程是比较复杂的。
- useEffect Hook 可以完成一些类似于class类组件中生命周期的功能。
- 事实上,类似于网络请求、手动更新DOM元素、事件的监听、事件的订阅都是React更新DOM的一些副作用(Side Effects)。
- 执行时机:在组件第一次挂载时执行useEffect hook和组件的状态发生更新的时候执行useEffect hook函数。简单的理解代替我们手动操作DOM,我们只需要管理我们的状态数据,更新状态即可,而不再担心DOM结构发生的变化。
所以对于完成这些功能的Hook被称之为Effect Hook(副作用hook)。 :::
1、useEffect的使用原理
原理:模拟生命周期函数 作用:可以让我们在函数式组件中执行副作用(用于模拟类组件中的生命周期函数) React中的副作用:
- 发送网络请求/获取数据
- 事件的订阅与取消、定时器开启与关闭
- 手动更改真实DOM结构
1.1 useEffect的语法说明
# useEffect的语法说明与使用
// 先引入 useEffect Hook 钩子函数
import React, { useEffect } from 'react';
useEffect(() => {
// 执行相应的逻辑代码 模拟类组件componentDidMount与componentDidUpdate生命周期函数
return () => { // 组件卸载前执行
// 执行相应的逻辑 模拟类组件 componentWillUnmount生命周期函数
// 在此作一些收尾的工作,比如清除定时器/取消事件的订阅
}
}, [])
# 这个函数 最后的参数有三种形式
1、不传递参数 表示监听组件中所有的状态,一旦组件内部的状态发生变化,组件都会执行这个钩子函数。相当于componentDidUpdate生命周期函数。(第一个回调函数会执行、回调函数的返回值也会执行)
2、传递一个空数组,不管组件中的状态发生了什么变化,都不会执行这个函数。只会在组件第一次挂载时执行,相当于componentDidMount生命周期函数。如果是一个空数组,这个Hook函数只会在组件第一次render的时候执行一次,如果只需要执行一次的话,那么可以直接传入一个空数组即可,比如说我们的网络请求,只需要发送一次网络请求即可。
3、在数组中传入组件内部的状态。表示的是这个钩子的执行与传入的状态相互关联,当组件的状态发生变化时,这个钩子将会执行,组件将会重新进行渲染。如果传入的状态的值不发生变化。那么我们的组件将不会再进行渲染。只要我们依赖的状态发生了变化,对应组件的 useEffect Hook函数就会被执行。
# 可以把useEffect看作三个函数的组合、配置和使用更加的灵活多变
componentDidMount()
componentDidUpdate()
componentWillUnmount()
2、实时修改浏览器Title的值
案例:在组件创建的时候,可以修改浏览器title的值,在组件更新的时候,也可以修改title的值。就是需要区分在不同的时刻修改title的值。
2.1 类组件的实现
# 使用类组件 来修改title的值
import React, { PureComponent } from 'react';
export default class ChangeTitle extends PureComponent {
constructor(props) {
super(props)
this.state = {
count: 100
}
}
// 将初始化counter的值显示在浏览器标签页的title上面
componentDidMount() {
document.title = this.state.count
}
// 组件更新的时候 也需要进行展示 每当组件的状态发生变化的时候,组件就会进行重新render()、更新
componentDidUpdate() {
// 组件重新进行渲染的时候 该生命周期函数就会执行
document.title = this.state.count
}
render() {
return (
<div>
<h2>hook的案例演示</h2>
<h2>当前计数: { this.state.count }</h2>
<button onClick={ () => this.setState({ count: this.state.count + 10 })}>+10</button>
</div>
)
}
}
2.2 类组件实现的优化-函数的抽取
在上述的案例中,我们可以发现在componentDidMount生命周期和conponentWillUnmont生命周期中都在执行一个操作,如果是同一个操作,在两个地方都进行执行,把相同的代码抽取到函数中,直接调用函数,避免出现重复性的代码。
# 改造优化后的代码
import React, { PureComponent } from 'react';
export default class ChangeTitle extends PureComponent {
constructor(props) {
super(props)
this.state = {
count: 100
}
}
// 将初始化counter的值显示在浏览器标签页的title上面
componentDidMount() {
this.updateCounterAction()
}
// 组件更新的时候 也需要进行展示 每当组件的状态发生变化的时候,组件就会进行重新render()、更新
componentDidUpdate() {
this.updateCounterAction()
}
// 封装公用的函数 在需要的时候进行调用
updateCounterAction() {
document.title = this.state.count
}
# 进行简单的抽取以后,发现类组件的写法较之前的写法简洁 体现代码的封装性
render() {
return (
<div>
<h2>hook的案例演示</h2>
<h2>当前计数: { this.state.count }</h2>
<button onClick={() => this.setState({ counter: this.state.count + 10 })}>+10</button>
</div>
)
}
}
2.3 函数式组件实现-使用useEffect hook函数
import React, { useState, useEffect } from 'react';
export default function UpdateDocumentTitle() {
// 使用useState hook
const [count, setCount] = useState(110)
// 表示的是 一旦我们的count状态发生了变化 组件就会重新进行相应的渲染
useEffect(() => {
document.title = count
console.log('组件挂载了')
return () => {
console.log('组件即将被卸载了');
}
}, [count])
// 当我们传入的参数count的值发生变化的时候 useEffect会自动进行调用 相当于监听count的值是否发生变化。
return (
<div>
<p>hook案例的演示</p>
<h2>当前计数{count}</h2>
<button onClick={ () => setCount(count+ 10) }>修改数据</button>
</div>
)
}
3、模拟生命周期函数中事件的订阅与取消
:::info
useEffect可用于事件的订阅,useEffect的第一个参数是一个箭头函数,当我们组件第一次挂载和组件的状态发生更新的时机,那么useEffect的回调函数就会被执行,这个函数有一个返回值,当我们组件被卸载的时候,这个返回值的回调函数就会被执行。
useEffect钩子的第二个参数是一个数组,当我们传一个空数组的时候,表示这个useEffect钩子不依赖任何状态,只要我们的组件一更新,那么useEffect的回调函数就会执行。第二个数组参数 实际上可以传入依赖的状态,当依赖的状态发生改变的时候,我们useEffect的回调函数才会执行。
:::
// 订阅与取消订阅的事件
import { useEffect } from 'react';
export default function Demo() {
useEffect(() => {
// 订阅事件 模拟类组件挂载的生命周期 相当于componentDidMount生命周期
console.log('事件被订阅了')
return () => {
// 相当于componentWillUnmount组件生命周期
console.log('事件取消订阅了')
}
// 第一个参数是 箭头函数 第二个参数为只读的数组类型, 这里为了做性能优化的测试
// 如果不传递参数的化 每当我们组件的状态发生了变化 组件都会重新进行渲染 但是实际上我们组件的状态并没有发生相应的变化 不需要重新进行渲染。这是性能可以优化的一个点。
// 自己组件内部发生变化的时候 组件也会进行渲染 需要进行性能优化
}, [])
return (
<>
<p>useEffect hook的具体使用</p>
<div>测试事件的订阅和取消</div>
</>
)
}
4、多个useEffect一起进行使用
import React, { useState, useEffect } from 'react';
export default function MultiuseEffectHookDemo() {
// 定义初始化的状态
const [counter, setCounter] = useState(200);
// 可定义使用多个useEffect钩子 依赖conter状态的变化 counter状态发生了变化 这个useEffect将会重新执行
useEffect(() => {
console.log('操作dom');
}, [counter])
// 第二个参数是空数组 只会在第一次组件挂载的时候执行这个hook 不管组件的状态发生什么变化 这个hook都不会再执行
useEffect(() => {
console.log('订阅事件');
}, [])
// 如果不传递第二个参数的话 第一次组件挂载的时候会执行这个hook
在组件的其他状态发生改变的时候,不管组件的什么状态发生改变 都会执行这个hook函数
useEffect(() => {
console.log('网络请求');
})
// 对于只执行一次的操作 我们可以在useEffect函数的第二个参数中 传入一个空数组 便于性能优化
// 数组中可传入相应的状态 以此来决定我们的组件是否进行更新渲染
return (
<div>
<h2>MultiuseEffectHookDemo</h2>
<h2>当前计数: { counter }</h2>
<button onClick={ () => setCounter(counter + 10) }>修改数据</button>
</div>
)
}
# 注意:上述多个useEffect的执行顺序是按照useEffect定义的顺序进行执行的 定义在前面 就先进行回调
# 如果传入的是一个空的数组 那么这个useEffect钩子函数只会在初始化的时候执行一次
后面的参数是一个数组,当我们传入的值 发生变化的时候 此时的useEffect将会被执行 由传入的参数决定
简单的说 就是由我们传入的参数来决定 传入的参数的值发生变化 那么对应的useEffect将会被执行
如果传入的参数的值 没有发生变化 那么对应useEffect将不会被执行。
5、useEffect Hook第二个参数的意义
:::info
- 当我们不传递第2个参数的时候,表示这个hook函数依赖组件所有的状态,一旦组件的状态发生改变,这个hook函数重新执行并进行相应的数据渲染。
- 当我们传一个空数组的时候,表示我们当前这个useEffect什么都不依赖,只会在组件第一次挂载的时候执行一次,后来不会再执行了。
- 当我们向数组里面传入一个状态变量的时候,表明我们只依赖这个状态,只有当我们这个状态发生改变的时候,我们这个hook函数才会执行,那么这个hook函数的第一个参数回调函数才会执行,我们可以在回调函数里面做相应的逻辑处理。
- 主要作用:用于react的性能优化,希望在某些时候我们在更改状态的时候,不会执行不相关的hook函数,这样的话,对于我们对组件内部状态的控制更加细腻,操作的颗粒度更小。 :::