Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。同时,它是100% 向后兼容的,不包含任何破坏性改动。

在我们讲解React Hook之前,先讲一下为什么要使用它,就好比谈对象,你得先搞清楚你喜欢他什么。我们知道,在没有 hook 之前,我们写一个 react 项目总是避免不了下面的代码:

我想,对于很多做前端的朋友而言,如果学过后端语言还好,因为他们对类有一个明确的概念,对构造器和继承的理解也很容易,但对于很多直接就开始做前端的朋友(比如:非科班出生的一些自学者),突然让他们接受一个类的概念是很难适应的,虽然 ES6 已经有了 class。但是,如果你用 hook,就可以摆脱 class 了,上面的代码就变成了下面这样:

少了extends,class,constuctor,super等关键字,是不是感觉一下子轻松了很多,同时,onClick 的代码也精简了很多。并且,对于前端来说,一个组件实现的是一个功能,用 function 来定义一个组件,比起用 class 来说,更符合语境。

当然,这只是其中的一个点,它的好处还有很多:

Hook 使你在无需修改组件结构的情况下复用状态逻辑,这使得在组件间或社区内共享 Hook 变得更便捷;

Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。

Hook 使你在非 class 的情况下可以使用更多的 React 特性,让你的代码远离class。

废话到此结束,接下来,就分别讲讲各个 hook 的用法和作用。不过要记住一点,使用每个 hook 都得先从 react (16.8+版本) 中引入

1、useState

上面的代码已经展示过它的功能了,useState 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state。useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。

useState 支持我们在调用的时候直接传入一个值,来指定 state 的默认值,他支持可以作为js参数一切数据,包括函数,但如果是函数的话,函数必须要有返回值。
我们在使用 useState 时,如果想要获取上一轮该 state 的值的话,只需要在 useState 返回的第二个参数,传入一个参数,该函数的参数就是上一轮的 state 的值。
当我们使用多个 useState 的时候,那 react 是如何识别哪个个是哪个呢?其实很简单,它是靠第一次执行的顺序来记录的,就相当于每个组件存放useState 的地方是一个数组,每使用一个新的 useState,就向数组中 push 一个 useState,所以,当我们在运行时改变 useState 的顺序,数据会混乱,增加 useState,程序会报错。

2、useEffect

如果你熟悉 React Class 的生命周期函数,你可以把 useEffect 看做是 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合,并且它会根据你传递的第二个参数达到灵活多变的效果。

useEffect 支持第二个参数,分三种情况:
1、什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate;
2、传入一个空数组 [], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount;
3、’传入一个数组,其中包括变量,只有这些变量变动时,useEffect里的方法才会执行。
如果 useEffect 最后 return 了一个方法,return 的方法会在 componentWillUnmount 阶段执行,比如定时器的清除,就可以通过 return 一个 clearTimer 方法来搞定。
在实际开发中,适当的把逻辑拆分成多个 effect,不仅业务清晰,再配合好第二个参数,生命周期的问题就迎刃而解了。且看下面例子:

3、useMemo

useMemo 主要用于一个变量依赖于另一个变量,有点类似于vue的计算属性,主要用于性能优化。同时它也支持传入第二个参数,用法和 useEffect 类似。不过需要注意的是,它的首次执行是在渲染的时候,而不是渲染完成之后。

4、useCallback

useCallback 可以说是 useMemo 的语法糖,能用 useCallback 实现的,都可以使用 useMemo,在 react 中我们经常面临一个子组件渲染优化的问题,尤其是在向子组件传递函数props时,每次 render 都会创建新函数,导致子组件不必要的渲染,浪费性能,这个时候,就是 useCallback 的用武之地了,useCallback 可以保证,无论 render 多少次,我们的函数都是同一个函数,减小不断创建的开销。

useCallback 和直接使用 useEffect 不同的地方在于使用 useCallback 生成计算的回调后,在使用该回调的副作用中,第二个参数应该是生成的回调。其实这个问题是很好理解的,我们使用 useCallback 生成了一个与 count1 / count2 相关联的回调方法,那么当关联的状态发生变化时会重新生成新的回调,副作用监听到了回调的变化就会去重新执行副作用,此时 useCallback 和 useEffect 是按顺序执行的, 这样就实现了副作用逻辑的抽离。同样,useCallback 的第二个参数和 useMemo一样,没有区别。

5、useRef

useRef 总共有两种用法:
1、获取子组件的实例

2、在函数组件中的一个全局变量,不会因为重复 render 而重复申明, 类似于类组件的 this.xxx。有些情况下,我们需要保证函数组件每次 render 之后,某些变量不会被重复申明,比如说 Dom 节点,定时器的 id 等等,在类组件中,我们完全可以通过给类添加一个自定义属性来保留,比如说 this.xxx, 但是函数组件没有 this,自然无法通过这种方法使用,有的朋友说,我可以使用 useState 来保留变量的值,但是 useState 会触发组件 render,在这里完全是不需要的,我们就需要使用 useRef 来实现了。

6、useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值,说简单点就是,子组件可以选择性的暴露给父组件一些方法,这样可以隐藏一些私有方法和属性,官方建议,useImperativeHandle 应当与 forwardRef 一起使用。

7、useReducer

useReducer 有点类似 redux 中的功能,相较于 useState,它更适合一些逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等特定场景。
useReducer 总共有三个参数:
第一个参数是 一个 reducer,就是一个函数类似 (state, action) => newState 的函数,传入 上一个 state 和本次的 action;
第二个参数是初始 state,也就是默认值,是比较简单的方法;
第三个参数是惰性初始化,这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利。

 

好了,讲了那么多,我们似乎知道了 Hook 究竟是怎么一回事,说白了,其实 Hook 就是返回包含了更多逻辑的 State 以及改变 State 的方法。

接下来我们来自定义一个自己的钩子,以计数器来为例:

 

本文主要参考文章:

https://www.cnblogs.com/ascoders/p/10591832.html

https://blog.csdn.net/landl_ww/article/details/102158814