前言
大家好,我是其润丿。由于工作的原因,我也是从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是共享的。
整体分析
-
useState(initialState) 的参数 initialState 是创建 state 的初始值,可以是任意类型的,比如数字、对象、数组等等。
-
useState() 的返回值是一个有着两个元素的数组。第一个数组元素用来读取 state 的值,第二个是用来设置这个 state 的值。要注意的是,state 的变量(下图例子中的count)是只读的,所以只能通过第二个数组元素 setCount 来设置它的值。
-
如果我们要创建多个state,我们就需要多次调用useState。
示例代码
我们以具体的例子来进行讲解
代码解析
- 代码功能为点击button按钮时count值加1
- 首先通过useState创建一个常量count,以及改变他的方法setCount,同时给count赋初值为0
- 创建一个函数方法changeCount来调用setCount从而改变count的值
- 通过button的onClick点击事件来对changeCount函数进行触发
- 因为函数中传进去的参数为1,所以每次都会加1。
这就是useState的最基础的用法
注意:
通常来说,要遵循的一个原则就是:state中永远不要保存可以通过计算得到的值
例如:
-
从props传递过来的值,有时候无法直接使用,需要计算后再在UI上展示,不应该将结果直接放在state里。
-
从URL中读取到的值,我们可以在每次需要用的时候从 URL 中读取,而不是读出来直接放到 state 里。
-
从 cookie、localStorage 中读取的值,也是每次要用的时候直接去读取,而不是读出来后放到 state 里。
弊端:
一旦组件有自己状态,意味着组件如果重新创建,就需要有恢复状态的过程,这通常会让组件变得更复杂。
例如:
一个组件想在服务器端请求获取一个用户列表并显示,如果把读取到的数据放到本地的 state 里,那么每个用到这个组件的地方,就都需要重新获取一遍。(这种情况的话可以通过redux 来获取数据)
2.useEffect:执行副作用
副作用:
指一段和当前执行结果无关的代码。比如说要修改函数外部的某个变量,要发起一个请求等
整体分析
useEffect 是每次组件render完后判断依赖并执行
useEffect可以接收两个参数:第一个为要执行的函数 callback,第二个是可选的依赖项数组 dependencies。
其中依赖项是可选的
- 如果不指定,那么 callback 就会在每次函数组件执行完后都执行;
- 如果指定了,那么只有依赖项中的值发生变化的时候,它才会执行。
在函数组件的当次执行过程中,useEffect 中代码的执行是不影响渲染出来的 UI 的。
示例代码
我们以具体的例子来进行讲解
代码解析
- 首先整体看下来,useEffect是有两个参数
- 第一个参数为函数的一些具体逻辑,第二个参数为此useEffect的依赖项,例子中为chekList
- 整段代码的逻辑是当组件第一次render完后或checkList这个数据源发生变化的时候,使用reduce方法来计算出来一个新的数组,从而调用redux中的一些方法来改变数据源
- 而checkList数据源没有改变的时候,此段代码逻辑是不会执行的(除了第一次render后)
依赖项的两种特殊情况:
- 没有依赖项,则每次 render 后都会重新执行。
- 空数组作为依赖项,则只在首次执行时触发。
依赖项的注意点:
-
依赖项中定义的值一定是会在回调函数中用到的,否则声明依赖项其实是没有意义的。
-
依赖项一般是一个常量数组,而不是一个变量,创建 callback 的时候,要需要知道用到哪些依赖项了。
-
React 会使用浅比较来对比依赖项是否发生了变化,所以要特别注意数组或者对象类型。如果每次创建一个新对象,即使和之前的值是等价的,也会被认为是依赖项发生了变化。
useEffect中的return
useEffect 还允许你返回一个函数,用于在组件销毁的时候做一些清理的操作。比如移除事件的监听。
具体逻辑代码
代码解释
此代码其实很简单,就是当组件render完后,我们调用window的resize事件,当窗口大小触发的时候会调用事件处理函数,但是如果没有return的话,当组件销毁时还会存在,就会导致一些问题。 所以使用return对组件销毁的时候做一些清理的操作是一个很好的习惯
通过这样一个简单的机制,我们能够更好地管理副作用,从而确保组件和副作用的一致性。
3.useCallback与useMemo
因为useCallbak与useMemo有异曲同工之处,所以文章中将这两个Hooks以对比的方式描述出来,方便大家的理解与分辨。
示例代码
- useMemo的使用:缓存计算的结果 使用useMemo后,成为count作为依赖值传递进去,此时仅当count变化时才会重新执行getNum。
- useCallback的使用:缓存回调函数 useCallback这里我写了两种写法
- 第一种是常规写法,将所依赖的值作为第二个参数数组中的常量传递进去
- 第二种是经过学习所得,因为useCallback返回的是函数,所以可以传进来参数,当传进来的参数与依赖项相同,就可以将第二个参数依赖项置空 我们一般来说都是采用第一种写法,二者选其一就行
相同点
-
两者接收的参数都是一样的,第一个参数表示一个回调函数,第二个表示依赖的数据。
-
两者都是仅仅当依赖的数据发生变化时, 才会重新计算结果,也就是起到缓存的作用。
-
都是用来作为性能优化。
不同点
-
useMemo 计算结果是 return 回来的值, 主要用于缓存计算结果的值 ,应用场景如: 需要计算的状态
-
useCallback 计算结果是函数, 主要用于缓存函数,应用场景如: 需要缓存的函数。
因为函数式组件每次任何一个 state 的变化, 整个组件都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,减少资源浪费。
注意点
useCallback 的功能其实是可以用 useMemo 来实现的。
当我们在useMemo中retun的是一个函数的时候,实际上与useCallback的功能是相同的
所以说useMemo与useCallback有异曲同工之处,我们应该多多对比,多多学习。
4.useRef:在多次渲染之间共享数据
整体分析
可以把 useRef 看作是在函数组件之外创建的一个容器空间。在这个容器上,可以通过唯一的 current 属性设置一个值,从而在函数组件的多次渲染之间共享这个值。
示例代码
我们以具体的例子来进行讲解
代码解析
- 这是一个定时器的例子,功能为点击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 节点,并对这个节点进行操作
示例代码
功能具体例子:你需要在点击某个按钮时让某个输入框获得焦点
代码解析
- ref 这个属性提供了获得 DOM 节点的能力,并利用 useRef 保存了这个节点的应用。
- 这样的话,一旦 input 节点被渲染到界面上,那就可以通过 inputRef.current 就能访问到真实的 DOM 节点的实例了。
- 通过inputRef.current.focus()来实现输入框获得焦点的功能
5.useContext:定义全局状态
React 组件之间的状态传递只有一种方式,那就是通过props。这就意味着这种传递关系只能在父子组件之间进行。
而如果需要跨层级的话,React 提供了 Context 这样一个机制。
整体分析
- Context这个机制,能够让所有在某个组件开始的组件树上创建一个 Context。这样这个组件树上的所有组件,就都能访问和修改这个 Context 了。
- 一个 Context 是从某个组件为根组件的组件树上可用的,所以需要有 API 能够创建一个 Context,就是React.createContext
- React.createContext 创建出来的值具有一个 Provider 的属性,一般是作为组件树的根组件。
- 这个根组件中可以通过value属性对数据进行传递
- 这样的话,在后代的组件中通过useContext就可以接收到上方通过value传来的值,从而进行一系列的操作。
示例代码
我们以具体的例子来进行讲解
代码分析
- 整体代码的功能为通过在APP根组件中修改对themes数据key的引用,来改变组件树中根组件的一些效果
- 首先先使用React.createContext创建一个Context。
- 在根组件引入子组件的外部通过Provider属性进行包裹,并且通过value将数据源传递下去
- 可以看到Toolbar这个子组件中没有任何操作,但是在Toolbar组件中的子组件ThemedButton中,通过useContext对传递下来的value值进行了引用。
- 从而完成了想要实现的效果。这也证明了useContext的功能:定义全局状态,跨层级传递数据
为什么使用useContext
-
使用useContext,是为了能够进行数据的绑定。 当这个 Context 的数据发生变化时,使用这个数据的组件就能够自动刷新。但如果没有 Context,而是使用一个简单的全局变量,就很难去实现了。
-
Context 更多的是提供了一个强大的机制,让 React 应用具备定义全局的响应式数据的能力。
Hooks的使用规则
1. 只能在函数组件的顶级作用域使用;
顶层作用域就是,Hooks 不能在循环、条件判断或者嵌套函数内执行,而必须是在顶层。并且Hooks 在组件的多次渲染之间,必须按顺序被执行。
正确示例
此时的Hooks一定会被执行到
错误示例
结论
- 所有 Hook 必须要被执行到。
- 必须按顺序执行。
所以要在函数的顶级作用域使用
2. 只能在函数组件或者其他 Hooks 中使用。
可以使用的情况只有两种,一种是在函数组件内,另外一种则是在自定义的 Hooks 里面
如何在Class组件中使用呢?
如果一定要在 Class 组件中使用,那就是利用高阶组件的模式,将Hooks 封装成高阶组件,从而让类组件使用。
结尾
这是我的第一篇文章,总结了一下React常用的6个Hooks的知识点,Hooks的出现帮助React变得更加简单,更加容易理解,所以熟练掌握Hooks还是很有必要的。希望能帮助到大家的学习,如果文章中出现了一些错误也欢迎大家指出,不足之处也大家请多多包涵与谅解。