最近笔者学习了 React Hook 相关的课程,也查阅了一遍文档,现在在这里做一个总结,希望能够给大家起到帮助和建议的作用,话不多说,现在开讲。
自从 React 16.8 之后呢,React 提出了 hooks 的概念。Hooks,那么英文的含义呢,就是钩子,顾名思义,是起连接作用的函数。React官方推出这个概念的背景呢,是函数式组件的广泛使用。在函数式组件推出之前,React 的开发主要是以类的方式定义的组件,那么类组件和函数式组件有什么区别呢?为什么要用函数式组件呢?
首先,其实 React 的本质就是 state 驱动 UI去进行更新,那么就着这个本质,我们就会发现一些 Class 中具备的特性,在 React 中的重要性不是那么明显。
第一:React 的设计理念更适合函数。那其实我们都知道,React 应用是由组件构成的,而组件基本上都是可重用的,因此,使用函数来写 React 更符合它的设计理念。
第二:Class 的很多特性其实 React的组件都没有用到。因为组件之间的功能大多数是相互隔离的,因此 Class 相关的继承等特性其实在 React 中没有特别大的作用。
基于以上两点,再加上函数组件的便捷和可复用性强等特点,React 决定使用函数来进行组件的组织和编写。那么其实 Hooks 也可以理解为是函数。
useState 和 useEffect
在 React Hooks 中,最常见的两个 hooks 就是 useState 和 useEffect。
useState 是用来设置组件内的状态的,与之前类组件设置的状态相比来说,useState 更加的语义化,每一个 state 都有一个对应的改变 state 状态的函数。在此之前,state在 Class 组件中只是一个对象,单个state只能作为一个属性保存在这个对象上,用 setState 来统一进行处理。
官方使用示例如下:
const [state, setState] = useState(initialState);
如果需要改变 state,则使用如下的方法:
setState(newState);
useEffect 又叫做副作用,这个 hooks 是在组件渲染完成之后进行调用的,因此这个 hooks 可以进行一些 dom 元素上的操作。而这个 hooks 为什么要叫作副作用呢,因为在这个 hooks 的第二个参数中是一个数组,useEffect 中的回调函数被调用的次数是由数组中依赖的值来决定的。如果数组是空的,那么每次组件被重新渲染的时候副作用都会被调用。如果数组中有值,那么每当数组中的值改变时,副作用会被调用。因此,在副作用中使用的值都要声明在依赖的数组里。useEffect 中可以返回一个回调函数,这个回调函数会在组件销毁时被调用,因此在组件销毁时的一些操作可以在这个回调函数中去做。
官方使用示例如下:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; });
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useCallback 和 useMemo
useCallback 和 useMemo 在某些情况下有些类似。它们都是为了保持状态而设立的 hooks。useCallback 是为了保持函数状态而设置的,而 useMemo 则是为了保持数据的状态。
由于函数每次重新定义,会让接受函数的组件重新渲染。为了避免组件的重新渲染,可以使用 useCallback。useCallback会返回一个memorized 回调函数。useCallback 的结构和 useEffect 很像,第二个参数都是一个数组的依赖项。只有在依赖项中的值发生变化时,useCalback 中的函数才会被重新声明,在这里,如果某个状态在被 useCallback 中的的函数中使用了,但是没有在第二个参数中声明时,即使这个状态改变了,在这个函数中的这个状态值并不会被改变。
useCallback 给出的示例如下
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo 同样是为了保持数据的不可变性,因为有些值定义在组件上,如果数据一直发生变化,那么就会触发组件的重复渲染。useMemo 的形式同样会在第二个参数中传入一个数组当作依赖项但不同的是 useMemo 中的依赖项是保持数据不变的。useMemo 内部的函数会在渲染期间执行,因此与渲染无关的操作不要在此中进行。而同样的,如果useMemo 没有提供依赖项,useMemo在每次渲染时都会执行。
代码示例如下:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useRef
useRef 会返回一个可变的 ref 对象,并且这个 ref 对象在整个生命周期中会持续存在。因此,我们常常利用 useRef 来获取某个具体的 dom 元素,而 useRef 的最好的一点就是会避免数据的重复渲染。而且useRef实际上可以保存任何的可变值,并且 useRef可以在每次渲染时返回同一个 ref 对象,所以我们可以在多个渲染之间共享数据。
在 Dom 上的使用方法如下
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useContext
useContext 接收一个 context 对象并且返回该 context 的当前值,当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。在我们日常的编码中,useContext 可以用来设置全局的状态,主要是为了满足全局状态管理的这样一个要求。当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。我们之所以不用一个全局变量而是使用 useContext 的原因就在于 useContext 可以触发组件的重新渲染,并且可以进行数据的绑定。但是这样的使用方法有一个很大的问题就是,很难去通过调试来捕捉到问题并且会让组件复用变得困难。
自定义 Hook
如果需要用好 hook, 那么就需要从 Hooks 的角度去思考问题。那么自定义 hook 呢,其实就是将功能中的逻辑抽取出来成为独立的 Hooks。我们将业务逻辑拆成单独的 Hooks, 这样有助于实现代码的模块化和解耦,也就是方便逻辑进行复用和帮助关注分离。
创建自定义 hooks 必须遵守以下两个原则
如果不在自定义 hooks 中使用其他 hook,那么这就不是一个 hook 而是一个普通的函数。
应用场景
Hooks 的使用规则
Hooks 组件的生命周期
生命周期在 class 组件中是非常重要的一部分,但是函数组件和 class 组件不能一概而论,也不能将所有的 class 生命周期来考虑函数组件的生命周期。
首先就是 useEffect 中的回调函数,useEffect 中的回调函数不能等价于 ComponentWillunmount, 首先是因为 useEffect 的回调时机只发生在依赖项改变的时候,并不是每次组件销毁的时候都会调用。其次 useEffect 的作用是用来清理 effect 上一次的后果。