hooks 文档学习笔记 — useRef

2020-12-25  本文已影响0人  盐酸洛美沙星

主要围绕这两个问题做一些记录:

useRef 和 createRef 的区别

两者效果相同的情景

学习 useRef 的时候,冒出了一个疑问,这个和 createRef 有什么区别呢,一开始用下面的这个例子,两者的效果是相同的:

import React, { useRef } from "react";

const FocusInput = () => {
    const inputElement = useRef();
    // 或者 const inputElement = React.createRef(), 效果一样

  const handleFocusInput = () => {
    inputElement.current.focus();
  };
  return (
    <>
      <input ref={inputElement} />
      <button onClick={handleFocusInput}>focus</button>
    </>
  );
};

export default FocusInput;

需要注意的是,上述的组件没有状态更新,相当于挂载之后就不会重新渲染。

两者效果不同的情景

但是当组件涉及到重新渲染的时候,就可以看出两者的区别了,如下所示:

import React, { useRef } from "react";

const FindDiff = () => {
  const [count, setCount] = React.useState(1);

  // 分别创建两个 ref
  const refByUseRef = useRef();
  const refByCreateRef = React.createRef();

  if (!refByUseRef.current) {
    refByUseRef.current = count;
  }

  if (!refByCreateRef.current) {
    refByCreateRef.current = count;
  }

  return (
    <>
      <p>count: {count}</p>
      {/* useRef 实时值 */}
      <p>refByUseRef count: {refByUseRef.current}</p>
      {/* createRef 实时值 */}
      <p>refByCreateRef count: {refByCreateRef.current}</p>
      <button onClick={() => setCount(count + 1)}>render</button>
    </>
  );
};

export default FindDiff;

上述代码执行的结果如图1所示:

图1

为什么出现不同的结果

组件随着 count 的值的改变重新渲染,每次 count 改变,DOM 重新渲染之后,createRef 就会重新创建,生成一个新的引用地址,新的 ref 初始值为 null,页面渲染时被赋予当前 count 的值。

但是 useRef 一旦被创建,在组件的整个生命周期中,react 都会给它保持同一个引用useRef 返回的 {current: xxx} 对象也将在整个生命周期中保持同一个值,除非手动改变 current 的值。

所以 refByCreateRef count 会随着 count 改变,但是 refByUseRef count 将始终保持在第一次渲染时的值

useRef 更进一步的应用

使用useRef 封装成 usePrevious

有些场景下,我们可能需要获取到当前状态的上一个状态的值,使用 useRef 可以实现这个需求。

如下所示,可以获取到上一次 count 的值:

import React, { useRef, useState, useEffect } from "react";

const GetPrevStatus = () => {
  const [count, setCount] = useState(0);
  const prevCount = useRef();

  useEffect(() => {
    prevCount.current = count;
  });

  return (
    <>
      <p>prevCount: {prevCount.current}</p>
      <p>currentCount: {count}</p>
      <button onClick={() => setCount(count + 1)}>click</button>
    </>
  );
};

export default GetPrevStatus;
图2
图3
通过 useRef 是怎么获取到上一次状态的值的呢?

首先需要明确 useEffect 中的副作用函数会在第一次挂载之后,以及每一次重新渲染之后被调用,也就是 class 组件的 componentDidMount componentDidUpdate 这两个阶段中,这就导致 effect 函数会在浏览器完成画面渲染之后延迟调用

在上面的代码中就是,在第一次挂载之后,也就是页面渲染成功之后,此时 prevCount.current = 0 这个副作用函数才被调用,但此时页面早已经渲染成功了,<p> 标签中的 {prevCount.current} 的值已经被渲染到页面上了,如图2所示,这个值是为 null,也就是说这一次渲染 {prevCount.current} 引用的是初始值,而不是执行 prevCount.current = 0 之后拿到的值。

同理看图3,当 count 变成 5 的时候,触发页面重新渲染,此时prevCount.current 的值是上一次渲染成功之后,执行 prevCount.current = 4 得到的值,此时引用这个值,4 被渲染到页面上,页面渲染成功之后再次延迟调用 prevCount.current = 5, 得到新的值,供下一个引用使用。如此,便可以拿到上一次状态的值。

接着,可以将上面的代码进一步封装成一个 Hook:

const usePrevious = (state) => {
  const ref = useRef();
  useEffect(() => {
    ref.current = state;
  });
  return ref.current;
};

通过 const prevCount = usePrevious(count) 我们可以拿到 count 上一次的状态值,因为 usePrevious 中的 return 是同步的,而 useEffect 是延迟执行的,所以当我们调用 usePrevious 时,return ref.current 返回的始终是上一次执行 ref.current = state 得到的值。

简言之就是:useEffect 是在每次渲染之后才会触发副作用函数的,是延迟执行的。而 return 语句是同步的,所以 return 的时候, ref.current 还是旧值。

另外需要注意:
当 useRef 的 current 发生变化时,页面不会收到通知,也就是说更改 .current 属性不会导致页面重新渲染,因为引用地址始终是同一个。

上一篇下一篇

猜你喜欢

热点阅读