React Hook 那些事儿

268 阅读7分钟

最近笔者学习了 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 必须遵守以下两个原则

  • 需要以 use 开头的函数
  • 必须在 Hooks 中使用自定义 Hook
  • 如果不在自定义 hooks 中使用其他 hook,那么这就不是一个 hook 而是一个普通的函数。

    应用场景

  • 解耦业务逻辑
  • 监听浏览器状态,如滚动窗口大小变化等
  • 拆分复杂组件代码逻辑
  • Hooks 的使用规则

  • Hooks 不能在 return, if 或者循环语句中执行。
  • Hooks 只能在组件函数或其他 Hooks 中执行
  • Hooks 内部使用到的变量必须写在依赖项中
  • Hooks 组件的生命周期

    生命周期在 class 组件中是非常重要的一部分,但是函数组件和 class 组件不能一概而论,也不能将所有的 class 生命周期来考虑函数组件的生命周期。

    首先就是 useEffect 中的回调函数,useEffect 中的回调函数不能等价于 ComponentWillunmount, 首先是因为 useEffect 的回调时机只发生在依赖项改变的时候,并不是每次组件销毁的时候都会调用。其次 useEffect 的作用是用来清理 effect 上一次的后果。