扔掉你的Class吧,Hooks太香了
Posted 小旭
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了扔掉你的Class吧,Hooks太香了相关的知识,希望对你有一定的参考价值。
简介
Hook 是React16.8中新增的特性,它可以让你不编写class组件的情况下使用诸如state,生命周期等特性。
动机
在组件之间复用状态逻辑很难
为了解决这类问题,也有出现过一些解决方案,如render props
、高阶组件
、Context
等,它们都会导致组件树形成嵌套地狱
(有点像回调函数和flutter的写法)。
有了 Hook 之后,就可以从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑
。
复杂组件变得难以理解
随着业务扩张,组件内的逻辑状态和副作用也越来越多。我们在写 class 组件时,通常会将数据的获取放到compoentDidMount
和componentDidUpdate
中,涉及到定时器或者数据订阅时,还需要在生命周期函数compoentWillUnmount
去清除定时器或者取消数据订阅。这使得完全不相关的代码组合到一起,很容易产生bug,并且导致逻辑不清晰。
为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分
。
难以理解的 class
在之前,如果你要使用诸如state
、生命周期
等特性,就必须引入 class 组件,如果你要使用这些就必须花时间去理解 javascript 中this
的工作方式。
为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性
。
概览
Hook 可以分为内置 Hook
和自定义 Hook
。
内置 Hook
有:
- useState
- useEffect
- useContext
- useReducer
- useCallback
- useMome
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
自定义 Hook
就是将上述内置 Hook
进行组合,导出为一个以use
开头的函数。
这里先写一个基于class
的 Counter组件,后面让我们来用Hook
将它进行改造。
`import React from \'react\'
class Counter extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0 }
this.setCount = this.setCount.bind(this)
}
setCount() {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<>
<h2>{ this.state.count }</h2>
<button onClick={ this.setCount }>+</button>
</>
)
}
}
`
useState
让我们先来看一个最基础也同样重要的 Hook,现在我们开始改造上面的class
组件。
- 首先将
class
组件转为function
组件,也就是函数式组件
。
`- class Counter extends React.Component {}
+ function Counter() {}
`
- 接下来让我们将
state
等相关代码删掉,改用useState
Hook,因为此处改动较多,直接上最终代码
`import React, { useState } from \'react\'
const Counter = () => {
// 数组解构语法
// 声明一个count变量,并声明一个setCount函数用于改变count的值
const [count, setCount] = useState(0)
return (
<>
<h2>{ count }</h2>
<button onClick={ () => setCount(count + 1) }>+</button>
</>
)
}
`
乍一看代码简洁了很多,这难道不正是我们想要的嘛。(Hook 真香)
如果我们有多个state变量
,可以写成下面这种形式
`const [count, setCount] = useState(0)
const [num, setNum] = useState(0)
const [name, setName] = useState(\'\')
...
`
如果本次更新依赖于上一次的state值
,可以这么写,此时setCount
接收一个函数作为参数
`const [count, setCount] = useState(0)
const handleClick = () => {
setCount(prev => prev + 1)
}
<button onClick={ handleClick }>+</button>
`
useEffect
useEffect 可以让你在函数式组件
执行一些副作用操作,它接收两个参数,第一个参数为必选的副作用函数,第二个为可选的参数(它是副作用函数执行的依赖项数组)
还是刚才的例子,如果我们希望组件更新后执行一些副作用,比如打印当前count
变量的值,又或者改变当前页面的 title
``// ...
const [count, setCount] = useState(0)
const [num, setNum] = useState(0)
useEffect(() => {
console.log(当前count值,${count}
)
document.title = 当前count值,${count}
})
// ...
``
上面的代码我们可以看到我们并没有添加第二个可选参数,这意味着,只要组件内状态发生改变(不仅仅是count,甚至num发生改变时),这个副作用函数都会执行。如果我们希望副作用函数只在 count 改变
时候执行,这时候我们的第二个参数就派上用场了()
``// ...
// 当前effect函数只依赖于 count 的改变,在 num 改变的时候并不会被执行
// 相当于 class 组件中的 compoentDidMount 和 componentDidUpdate
useEffect(() => {
console.log(当前count值,${count}
)
document.title = 当前count值,${count}
}, [count])
// ...
``
如果我们仅仅希望副作用函数在组件挂载时候执行(compoentDidMount
)
``// useEffect 约定,在第二个参数传递空数组时,当前副作用函数只在组件挂载时被执行
useEffect(() => {
console.log(当前count值,${count}
)
document.title = 当前count值,${count}
}, [])
``
有些时候我们可能会用到定时器,比如每秒钟更新一次时间
`const [time, setTime] = useState(new Date())
const timer = null
useEffect(() => {
timer = setInterval(() => {
setTime(new Date())
}, 1000)
})
`
现在的代码有一个问题,想必大家都知道,那就是timer 没有被清除
,我们在工作中会遇到很多这种场景,那么我们应该怎么去清除 timer 呢,答案是useEffect的第一个参数返回一个函数,这个被返回的函数会在组件被卸载时候自动被执行,可以清除诸如定时器之类的东西
`const [time, setTime] = useState(new Date())
const timer = null
useEffect(() => {
timer = setInterval(() => {
setTime(new Date())
}, 1000)
// 类似于 compoentWillUnMount
// 当然这里也可以是一个具名函数
return () => {
clearInterval(timer)
timer = null
}
})
`
以上是关于扔掉你的Class吧,Hooks太香了的主要内容,如果未能解决你的问题,请参考以下文章
Jetpack简直太香了!最新Android jetpack架构组件入门到精通