钩子是什么
在类组件中,我们可以定义 state
来管理组件的状态,使用生命周期函数来管理组件的生命周期。
但函数组件就没有这些功能。因此 React
16.8 推出了 钩子 (Hook),让函数组件也拥有了类似的功能。
函数组件默认情况下,是无法使用 state
和生命周期函数的。但 钩子 让用户有能力通过间接的方式,将 state
和 生命周期函数勾入函数组件中,让函数组件拥有了类似的能力。
状态钩子
状态钩子 (State Hook),可以让函数组件定义状态。[1]
定义状态钩子
1 | function Counter() { |
在这里,我们在函数组件内调用 useState
,来定义一个状态。
函数括号里面的 0
是这个状态的初始值,返回两个对象,一个是当前的状态,一个是更新状态的函数。
除此之外,我们还可以定义很多的状态:
1 | let [count, setCount] = React.useState(0); |
同步还是异步
通过 useState
返回的状态更新函数 setXxxx
,和 setState
的行为是一致的。
副作用钩子
副作用和副作用钩子
提前副作用,首先我们要了解函数式编程(Functional Programming)的一个基本概念 纯函数(Pure Function)。
通俗的讲,纯函数是一种特殊的函数,它的返回值只跟参数有关。例如,数学中的 sin
、cos
都是纯函数,它的返回值都只跟参数有关。
如果一个函数除了做与返回值有关的事情,还做了一些例如修改全局变量、网络请求等与函数外部数据互动的事情,那么这些事便称为 副作用(Side Effect)。副作用就会使得函数的输出结果变得不太确定。
而 副作用钩子(Effect Hook),则是用来为函数组件引入副作用的,例如网络请求,修改状态、监听生命周期、操作DOM
。
定义副作用钩子
我们在上面例子的基础上,添加了副作用钩子:[1:1]
1 | function Counter() { |
在这里,我们在函数组件内调用 useEffect
,来定义一个副作用钩子。
副作用钩子跟 class
组件中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。它会在UI
创建、更新或即将销毁时触发。[1:2]
清除副作用钩子
有一些副作用钩子,是不需要清除的,例如网络请求,请求结束后会自动终止。
但还有一些副作用钩子是需要手动清除的,比如 setInterval
,它不会自动停止。
为了解决这个问题,我们可以定义一个清除副作用的钩子。
定义它很简单,只需要让传入的函数返回一个函数:
1 | function Counter() { |
当页面更新时,React
会清除上一次的副作用,再执行新的副作用。
跳过某些状态
前面说过了, useEffect
会在 componentDidUpdate
的时候调用。如果在 useEffect
代码中调用了 setXxxx
函数,那么组件会再次更新,componentDidUpdate
会被再次调用。这样就陷入了无限循环。看下面的例子:
1 | function Counter() { |
这个例子下,count
就会陷入了无休止的更新,尽管我们没有做任何操作。
那有没有办法,我可以跳过某些状态的更新,来避免这个问题呢?
答案是有的,那就是 useEffect
的第二个参数,它是一个数组,用来指定哪些状态改变时会进行更新。
1 | function Counter() { |
再例如网络请求。通常情况下,我们不需要每次组件更新就发送一次请求,我们可以设置第二个参数为空数组
1 | function ApiList() { |
其他钩子
上下文钩子
上下文钩子(Context Hook)是一种不太常用的钩子。它可以让函数组件引用上下文对象。
使用上下文钩子的函数:
1 | 上下文对象数据 = React.useContext(上下文实例); |
例如,我们在上下文那一节中定义的设置主题的钩子:
1 | const config = { theme: "light", setTheme: () => null }; |
我们把上下文那一节的 ThemedButton
改成函数组件:
1 | function ThemedButton(props) { |
引用钩子
引用钩子(Reference Hook)是一种不太常用的钩子。
useRef
返回一个可变的 ref
对象,可以将其挂载到 ref
,通过其 .current
属性便可以访问组件实例或DOM对象。
它的用法和 createRef
函数是类似的。
如何使用钩子
自定义钩子
除了内置的钩子,我们还可以自己定义钩子。
自定义 Hook 是一个函数,其名称以 use
开头,函数内部可以调用其他的 Hook。
例如,我们可以自定义一个只触发一次的副作用钩子:
1 | function useEffectOnce(func) { |