美文网首页
hooks 文档学习笔记 — useRef

hooks 文档学习笔记 — useRef

作者: 盐酸洛美沙星 | 来源:发表于2020-12-25 17:45 被阅读0次

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

    • useRefcreateRef 的区别?
    • useRef 为什么可以用来封装成 usePrevious?

    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 属性不会导致页面重新渲染,因为引用地址始终是同一个。

    相关文章

      网友评论

          本文标题:hooks 文档学习笔记 — useRef

          本文链接:https://www.haomeiwen.com/subject/wgfinktx.html