链接: 文档地址.
Effect Hook
目前我们已经通过hook函数式组件中定义state,那么类似于生命周期这些呢?
- Effect Hook可以让你来完成一些类似于class中生命周期的功能;
- 事实上,类似于网络请求,手动更新DOM,一些事件的监听,都是React更新DOM的一些副作用;
- 所以对于完成这些功能的Hook被称之为Effect Hook
Effect的基本使用
假如我们现在有一个需求:页面的title总是显示counter数字
使用class组件如何实现呢?
- 我们会发现document.title的设置必须在两个生命周期中完成;
- 这是因为React的class的组件并没有给我们提供一个统一的生命周期函数,可以让无论是否是第一次渲染都会执行生命周期的函数;
import React, { Component } from 'react';
class ClassCom extends Component {
state={
counter:96
}
componentDidMount(){
document.title=this.state.counter
}
componentDidUpdate(){
document.title=this.state.counter
}
render() {
return (
<div>
<p>当前counter的是{this.state.counter}</p>
<button onClick={()=>{
this.setState({
counter:this.state.counter+1
})
}}>+</button>
<button onClick={()=>{
this.setState({
counter:this.state.counter-1
})
}}>-</button>
</div>
);
}
}
export default ClassCom;
这个时候,我们可以使用useEffect的Hook来完成;
import React, { useEffect, useState } from 'react';
export default function About() {
let [counter,setCounter]=useState(66)
useEffect(()=>{
document.title=counter;
})
return (
<div>
<p>当前counter的计数是{counter}</p>
<button onClick={()=>{
setCounter(counter+1)
}}>+</button>
<button onClick={()=>{
setCounter(counter-1)
}}>-</button>
</div>
);
}
useEffect的解析;
- 通过useEffect的Hook,可以告诉React需要在渲染后执行某些操作;
- useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数;
- 默认情况下,无论是第一次渲染之后,还是每次更新之后,都会执行这个回调函数;
需要清除useEffect
在class组件在编写的过程中,某些副作用代码,我们需要在componentWillUnmount中进行清除;
- 比如我们之前的事件总线或Redux中手动调用subscribe
- 都需要在componentWillUnmount有对应的取消订阅
- Effect Hook通过什么方式来模拟componentWillUnmount
useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B
type EffectCallback = () => (void | (() => void | undefined));
我们可以这样来编写Effect Hook
import React, { useEffect, useState } from 'react';
export default function About() {
let [counter,setCounter]=useState(66)
useEffect(()=>{
document.title=counter;
console.log('每次DOM更新时会回调')
return ()=>{
console.log('DOM被移除时会回调');
}
})
return (
<div>
<p>当前counter的计数是{counter}</p>
<button onClick={()=>{
setCounter(counter+1)
}}>+</button>
<button onClick={()=>{
setCounter(counter-1)
}}>-</button>
</div>
);
}
为什么要在effect中返回一个函数?
- 这是Effect可选的清除机制。每个effect都可以返回一个清除函数;
- 如此可以将添加和移除订阅的逻辑放在一起;
- 它们都属于effect的一部分;
React何时清除effect
- React会在组件更新和卸载的时候执行清除操作;
- 正如之前学到的,Effect在每次渲染的时候都会执行;
使用多个Effect
使用Hook的其中一个目前就是解决class中生命周期经常将很多的逻辑放在一些的问题
- 比如网络请求,事件监听,手动修改DOM,这些往往都会在componentDidMount中;
使用Effect Hook,我们可以将它们分离到不同的useEffect中;
import React, { useEffect } from 'react';
export default function MultiUseEffect() {
useEffect(() => {
console.log("网络请求");
});
useEffect(() => {
console.log("修改DOM");
})
useEffect(() => {
console.log("事件监听");
return () => {
console.log("取消监听");
}
})
return (
<div>
<h2>MultiUseEffect</h2>
</div>
)
}
Hook允许我们按照代码的用途分离它们,而不是像生命周期函数那样;
- React将按照effect生命的顺序依次调用组件中每一个effect
Effect性能优化
默认情况下,useEffect的回调函数都会在每次渲染的时候都重新执行,但是这会导致两个问题;
- 某些代码我们只希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网络请求,订阅和取消订阅)
- 另外,多次执行也会导致一定的性能问题;
我们如何决定useEffect在什么时候应该执行和什么时候不应该执行呢?
- useEffect实际上有两个参数;
- 参数一:执行的回调函数
- 参数二:该useEffect在哪些state发生变化时,才重新执行;(收谁的影响)
我们来看下面一个案例:
- 在这个案例中,我们修改show的值,是不会让useEffect重新被执行的;
import React, { useState, useEffect } from 'react';
export default function EffectPerformance() {
const [count, setCount] = useState(0);
const [show, setShow] = useState(true);
useEffect(() => {
console.log("修改DOM");
}, [count])
return (
<div>
<h2>当前计数: {count}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
<button onClick={e => setShow(!show)}>切换</button>
</div>
)
}
但是,如果一个函数我们不希望依赖任何的内容时,也可以传入一个空数组[]
;
- 那么这里两个回到函数分别对应的就是componentDidMount和componentWillUnmount生命周期函数了
useEffect(() => {
console.log("监听事件");
return () => {
console.log("取消监听");
}
}, [])