React常用内置Hooks总结

1,123 阅读12分钟

前言

大家好,我是其润丿。由于工作的原因,我也是从vue转到了React.React作为比较主流的前端框架,近几年也是推出了一套全新的Hooks机制。对于 React 开发者而言,这无疑是给了我们多了一个新的选择。因为原来的基于 Class 的组件完全可以继续使用,这两种开发方式完全可以并存。因为我工作中的代码格式是基于Hooks,因此我也是着重学习了一下此部分。
下面给大家展示一下我对Hooks的一些总结与看法,希望能帮助到大家的学习,不足之处也大家请多多包含与谅解

React提供的Hooks一共有10个

  • useState
  • useEffect
  • useCallback
  • useMemo
  • useRef
  • useContext
  • useLayoutEffect
  • useReducer
  • useDebugValue
  • useImperativeHandle
    其中最常用的内置Hooks就是我加粗的前六个,下面我将对最常用的内置Hooks一一进行介绍

1.useState:让函数组件具有维持状态的能力

useState用来管理state,在一个组件的多次渲染中,state是共享的。

整体分析

  1. useState(initialState) 的参数 initialState 是创建 state 的初始值,可以是任意类型的,比如数字、对象、数组等等。

  2. useState() 的返回值是一个有着两个元素的数组。第一个数组元素用来读取 state 的值,第二个是用来设置这个 state 的值。要注意的是,state 的变量(下图例子中的count)是只读的,所以只能通过第二个数组元素 setCount 来设置它的值。

  3. 如果我们要创建多个state,我们就需要多次调用useState。 Image.png

示例代码

我们以具体的例子来进行讲解 Image.png Image.png

代码解析

  • 代码功能为点击button按钮时count值加1
  • 首先通过useState创建一个常量count,以及改变他的方法setCount,同时给count赋初值为0
  • 创建一个函数方法changeCount来调用setCount从而改变count的值
  • 通过button的onClick点击事件来对changeCount函数进行触发
  • 因为函数中传进去的参数为1,所以每次都会加1。
    这就是useState的最基础的用法

注意:

通常来说,要遵循的一个原则就是:state中永远不要保存可以通过计算得到的值

例如:

  1. 从props传递过来的值,有时候无法直接使用,需要计算后再在UI上展示,不应该将结果直接放在state里。

  2. 从URL中读取到的值,我们可以在每次需要用的时候从 URL 中读取,而不是读出来直接放到 state 里。

  3. 从 cookie、localStorage 中读取的值,也是每次要用的时候直接去读取,而不是读出来后放到 state 里。

弊端:

一旦组件有自己状态,意味着组件如果重新创建,就需要有恢复状态的过程,这通常会让组件变得更复杂。

例如:

一个组件想在服务器端请求获取一个用户列表并显示,如果把读取到的数据放到本地的 state 里,那么每个用到这个组件的地方,就都需要重新获取一遍。(这种情况的话可以通过redux 来获取数据)

2.useEffect:执行副作用

副作用:

指一段和当前执行结果无关的代码。比如说要修改函数外部的某个变量,要发起一个请求等

整体分析

useEffect 是每次组件render完后判断依赖并执行

useEffect可以接收两个参数:第一个为要执行的函数 callback,第二个是可选的依赖项数组 dependencies。

其中依赖项是可选的

  • 如果不指定,那么 callback 就会在每次函数组件执行完后都执行;
  • 如果指定了,那么只有依赖项中的值发生变化的时候,它才会执行。

在函数组件的当次执行过程中,useEffect 中代码的执行是不影响渲染出来的 UI 的。

示例代码

我们以具体的例子来进行讲解 Image.png

代码解析

  • 首先整体看下来,useEffect是有两个参数
  • 第一个参数为函数的一些具体逻辑,第二个参数为此useEffect的依赖项,例子中为chekList
  • 整段代码的逻辑是当组件第一次render完后或checkList这个数据源发生变化的时候,使用reduce方法来计算出来一个新的数组,从而调用redux中的一些方法来改变数据源
  • 而checkList数据源没有改变的时候,此段代码逻辑是不会执行的(除了第一次render后)

依赖项的两种特殊情况:

  1. 没有依赖项,则每次 render 后都会重新执行。
    Image.png
  2. 空数组作为依赖项,则只在首次执行时触发。
    Image.png

依赖项的注意点:

  1. 依赖项中定义的值一定是会在回调函数中用到的,否则声明依赖项其实是没有意义的。

  2. 依赖项一般是一个常量数组,而不是一个变量,创建 callback 的时候,要需要知道用到哪些依赖项了。

  3. React 会使用浅比较来对比依赖项是否发生了变化,所以要特别注意数组或者对象类型。如果每次创建一个新对象,即使和之前的值是等价的,也会被认为是依赖项发生了变化。

useEffect中的return

useEffect 还允许你返回一个函数,用于在组件销毁的时候做一些清理的操作。比如移除事件的监听。

具体逻辑代码

Image.png

代码解释

此代码其实很简单,就是当组件render完后,我们调用window的resize事件,当窗口大小触发的时候会调用事件处理函数,但是如果没有return的话,当组件销毁时还会存在,就会导致一些问题。 所以使用return对组件销毁的时候做一些清理的操作是一个很好的习惯
通过这样一个简单的机制,我们能够更好地管理副作用,从而确保组件和副作用的一致性

3.useCallback与useMemo

因为useCallbak与useMemo有异曲同工之处,所以文章中将这两个Hooks以对比的方式描述出来,方便大家的理解与分辨。

示例代码

  1. useMemo的使用:缓存计算的结果 E9A7EE0F-D5F3-4D66-9525-F25D803C33A8.png 使用useMemo后,成为count作为依赖值传递进去,此时仅当count变化时才会重新执行getNum。
  2. useCallback的使用:缓存回调函数 96146FF2-1BFF-40FC-A8B7-90D08B9E52A7.png useCallback这里我写了两种写法
  • 第一种是常规写法,将所依赖的值作为第二个参数数组中的常量传递进去
  • 第二种是经过学习所得,因为useCallback返回的是函数,所以可以传进来参数,当传进来的参数与依赖项相同,就可以将第二个参数依赖项置空 我们一般来说都是采用第一种写法,二者选其一就行

相同点

  1. 两者接收的参数都是一样的,第一个参数表示一个回调函数,第二个表示依赖的数据。

  2. 两者都是仅仅当依赖的数据发生变化时, 才会重新计算结果,也就是起到缓存的作用。

  3. 都是用来作为性能优化

不同点

  1. useMemo 计算结果是 return 回来的值, 主要用于缓存计算结果的值 ,应用场景如: 需要计算的状态 

  2. useCallback 计算结果是函数, 主要用于缓存函数,应用场景如: 需要缓存的函数。
    因为函数式组件每次任何一个 state 的变化, 整个组件都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,减少资源浪费。

注意点

useCallback 的功能其实是可以用 useMemo 来实现的。 image.png
当我们在useMemo中retun的是一个函数的时候,实际上与useCallback的功能是相同的
所以说useMemo与useCallback有异曲同工之处,我们应该多多对比,多多学习。

4.useRef:在多次渲染之间共享数据

整体分析

可以把 useRef 看作是在函数组件之外创建的一个容器空间。在这个容器上,可以通过唯一的 current 属性设置一个值,从而在函数组件的多次渲染之间共享这个值

示例代码

我们以具体的例子来进行讲解 FECDB31D-0AB6-406E-95B6-ECCA73CFBBEA.png E2E2906D-9F9D-4DE0-92A2-7D7A04AEBFD3.png

代码解析

  • 这是一个定时器的例子,功能为点击Start按钮时,每隔一秒time值加1,点击Pause按钮时,进行暂停。点击start时,count值继续增加,如此往复。
  • 我们可以注意到,开始和暂停的时间处理函数都被useCallback进行了包裹,且第二个参数为空。按照我们上方的讲解,他只有第一次的时候会进行加载。在代码执行过程中,虽然time值时刻在变,这两个函数也不会再变了。而函数不会跟着time的值改变,我们是怎么拿到timer的值的呢?
  • 这就是useRef的用法:因为useRef可以看作是在函数组件之外创建的一个容器空间,尽管例子中通过useCallback缓存出来的函数是一直不变的,但是timer.current的值一直是最新的值
  • 所以我们可以通过timer.current拿到最新的计时器的id,通过clearInterval将其进行清除,实现代码效果

注意

使用 useRef 保存的数据一般是和 UI 的渲染无关的,因此当 ref 的值发生变化时,是不会触发组件的重新渲染的,这也是 useRef 区别于 useState 的地方。

useRef另一个重要作用

可以通过useRef保存某个 DOM 节点的引用。 结合 React 的 ref 属性和 useRef 这个 Hook,就可以获得真实的 DOM 节点,并对这个节点进行操作

示例代码

功能具体例子:你需要在点击某个按钮时让某个输入框获得焦点 3D44AE7F-4DC6-43B0-868D-341DF96FE894.png 2F90D1AB-D618-4240-B2AB-C2599A7D78D4.png

代码解析

  • ref 这个属性提供了获得 DOM 节点的能力,并利用 useRef 保存了这个节点的应用。
  • 这样的话,一旦 input 节点被渲染到界面上,那就可以通过 inputRef.current 就能访问到真实的 DOM 节点的实例了。
  • 通过inputRef.current.focus()来实现输入框获得焦点的功能

5.useContext:定义全局状态

React 组件之间的状态传递只有一种方式,那就是通过props。这就意味着这种传递关系只能在父子组件之间进行。

而如果需要跨层级的话,React 提供了 Context 这样一个机制。

整体分析

  • Context这个机制,能够让所有在某个组件开始的组件树上创建一个 Context。这样这个组件树上的所有组件,就都能访问和修改这个 Context 了。
  • 一个 Context 是从某个组件为根组件的组件树上可用的,所以需要有 API 能够创建一个 Context,就是React.createContext

C350D287-0684-48DE-B782-C193F6D045BB.png

  • React.createContext 创建出来的值具有一个 Provider 的属性,一般是作为组件树的根组件。
  • 这个根组件中可以通过value属性对数据进行传递

6BAD00A9-E08E-44AE-A053-2742551C3D38.png

  • 这样的话,在后代的组件中通过useContext就可以接收到上方通过value传来的值,从而进行一系列的操作。
    image.png

示例代码

我们以具体的例子来进行讲解 28E82561-45A2-4ABA-AC93-41985B307C1E.png 66B14159-F6A9-4E4A-8639-56B512F47240.png

代码分析

  • 整体代码的功能为通过在APP根组件中修改对themes数据key的引用,来改变组件树中根组件的一些效果
  • 首先先使用React.createContext创建一个Context。
  • 在根组件引入子组件的外部通过Provider属性进行包裹,并且通过value将数据源传递下去
  • 可以看到Toolbar这个子组件中没有任何操作,但是在Toolbar组件中的子组件ThemedButton中,通过useContext对传递下来的value值进行了引用。
  • 从而完成了想要实现的效果。这也证明了useContext的功能:定义全局状态,跨层级传递数据

为什么使用useContext

  • 使用useContext,是为了能够进行数据的绑定。 当这个 Context 的数据发生变化时,使用这个数据的组件就能够自动刷新。但如果没有 Context,而是使用一个简单的全局变量,就很难去实现了。

  • Context 更多的是提供了一个强大的机制,让 React 应用具备定义全局的响应式数据的能力。

Hooks的使用规则

1. 只能在函数组件的顶级作用域使用;

顶层作用域就是,Hooks 不能在循环、条件判断或者嵌套函数内执行,而必须是在顶层。并且Hooks 在组件的多次渲染之间,必须按顺序被执行。

正确示例

Image.png
此时的Hooks一定会被执行到

错误示例

image.png

结论

  1. 所有 Hook 必须要被执行到。
  2. 必须按顺序执行。
    所以要在函数的顶级作用域使用

2. 只能在函数组件或者其他 Hooks 中使用。

可以使用的情况只有两种,一种是在函数组件内,另外一种则是在自定义的 Hooks 里面

如何在Class组件中使用呢?

如果一定要在 Class 组件中使用,那就是利用高阶组件的模式,将Hooks 封装成高阶组件,从而让类组件使用。

结尾

这是我的第一篇文章,总结了一下React常用的6个Hooks的知识点,Hooks的出现帮助React变得更加简单,更加容易理解,所以熟练掌握Hooks还是很有必要的。希望能帮助到大家的学习,如果文章中出现了一些错误也欢迎大家指出,不足之处也大家请多多包涵与谅解。
a831425b2afcf75bea7ea48db452e334.jpeg