前言
react hooks 是 React 16.8 的新增特性。 它可以让我们在函数组件中使用 state 、生命周期以及其他 react特性,而不仅限于 class 组件。react hooks 的出现,标示着 react中不会在存在无状态组件了,只有类组件和函数组件。具体可查看官网。
优势:
- 函数组件不能使用state,遇到交互更改状态等复杂逻辑时不能更好地支持,hooks让函数组件更靠近class组件,拥抱函数式编程。
- 解决副作⽤问题,hooks出现可以处理数据获取、订阅、定时执行任务、手动修改 ReactDOM这些⾏为副作用,进行副作用逻辑。比如useEffect。
- 更好写出有状态的逻辑重用组件。
- 让复杂逻辑简单化,比如状态管理:useReducer、useContext。
- 函数式组件比class组件简洁,开发的体验更好,效率更⾼,性能更好。
- 更容易发现无用的状态和函数。
useEffect介绍
useEffect(didUpdate);
拆解
useEffect(callback,array)
useEffect也是componentDidMount、componentDidUpdate和componentWillUnmount这⼏个⽣命周期方法的统⼀,该 Hook 接收一个包含命令式、且可能有副作用代码的函数。
- callback: 回调函数,作⽤是处理副作⽤逻辑
- array(可选参数):数组,⽤于控制useEffect的执⾏
在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性,useEffect就是为了处理这些副作用⽽生的。
使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。
默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候才执行。
useEffect使用
简单例子,有这样一个需求,需要我们在组件在状态更新的时候改变 document.title,如下:
import {useState,useEffect} from 'react'
const App=() => {
const [count,setState]=useState(0)
useEffect(() =>{ //更新⻚面标题
document.title=`您点击了了${count}次了了哦` },[count])
return (
<div>
<div>你点击了了{count}次</div>
<button onClick={()=>setState(count+1)}>点 击</button>
</div>
)
})
重点:useEffect 会返回一个回调函数,作用于清除上一次副作用遗留下来的状态,如果该 useEffect 只调用一次,该回调函数相当于 componentWillUnmount 生命周期。
useEffect(() =>{
//副作⽤逻辑xxxxxx
return ()=>{
//清理副作用需要清理的内容 //类似于componentWillUnmount,组件渲染和组件卸载前执⾏的代码
}
},[])
比如:通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数。
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});
为防止内存泄漏,清除函数会在组件卸载前执行。另外,如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除。
effect 的条件执行
条件执行其实array(可选参数)数组有关,用于控制useEffect的执行。
- 什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate。
- 传入一个空数组 [], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount。
- 传入一个数组,其中包括变量,只有这些变量变动时,useEffect 才会执行。
具体看下面例子
function App () {
const [ count, setCount ] = useState(0)
const [ width, setWidth ] = useState(document.body.clientWidth)
const onChange = () => {
setWidth(document.body.clientWidth)
}
useEffect(() => {
window.addEventListener('resize', onChange, false)
return () => {
window.removeEventListener('resize', onChange, false)
}
})
useEffect(() => {
document.title = count
})
return (
<div>
名称: { count }
宽度: { width }
<button onClick={() => { setCount(count + 1)}}>点我</button>
</div>
)
}
这例子要处理两种副作用逻辑,这里我们既要处理 title,还要监听屏幕宽度改变,按照 class 的写法,我们要在生命周期中处理这两种逻辑,但在 hooks 中,我们只需要两个 useEffect 就能解决这些问题,我们之前提到,useEffect 能够返回一个函数,用来清除上一次副作用留下的状态,这个地方我们可以用来解绑事件监听,这个地方存在一个问题,就是 useEffect 是每次 render 之后就会调用,比如 title 的改变,相当于 componentDidUpdate,但我们的事件监听不应该每次 render 之后,进行一次绑定和解绑,就是我们需要 useEffect 变成 componentDidMount, 它的返回函数变成 componentWillUnmount,这里就需要用到 useEffect 函数的第二个参数了。
改写:
function App () {
const [ count, setCount ] = useState(0)
const [ width, setWidth ] = useState(document.body.clientWidth)
const onChange = () => {
setWidth(document.body.clientWidth)
}
useEffect(() => {
// 相当于 componentDidMount
console.log('add resize event')
window.addEventListener('resize', onChange, false)
return () => {
// 相当于 componentWillUnmount
window.removeEventListener('resize', onChange, false)
}
}, [])
useEffect(() => {
// 相当于 componentDidUpdate
document.title = count
})
useEffect(() => {
console.log(`count change: count is ${count}`)
}, [ count ])
return (
<div>
名称: { count }
宽度: { width }
<button onClick={() => { setCount(count + 1)}}>点我</button>
</div>
)
}
第一个 useEffect 中的 ‘add resize event’ 只会在第一次运行时输出一次,无论组件怎么 render,都不会在输出,第二个 useEffect 会在每次组件 render 之后都执行,title 每次点击都会改变, 第三个 useEffect, 只要有在第一次运行和 count 改变时,才会执行,屏幕发生改变引起的 render 并不会影响第三个 useEffect。
官方推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps
规则。此规则会在添加错误依赖时发出警告并给出修复建议。