React Hooks系列】之useRef

24,588 阅读5分钟

前言

由于React的函数式组件使用起来方便(对比class组件),我将重点使用函数组件来运行开发。在这系列博客中,我将分享我所学到Hook系列API的知识。 Hooks系列主要分以下内容:

useRef是什么

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

这个ref对象只有一个current属性,你把一个东西保存在内,它的地址一直不会变。

简单示例

import React, { useRef } from "react";
export default function App() {
  const r = useRef(0);
  console.log(r);
  const add = () => {
    r.current += 1;
    console.log(`r.current:${r.current}`);
  };
  return (
    <div className="App">
      <h1>r的current:{r.current}</h1>
      <button onClick={add}>点击+1</button>
    </div>
  );
}

我在上面设置了一个r保存useRef的结果,并把它log打出来,我们来看看内部是什么

>  {current: 0}

内部是一个current属性,保存了我传入的0。

useRef变化不会主动使页面渲染

我们点击上方的按钮,让current+1,可以看到页面没有主动渲染,但是新的current的值已经变成了1

说明current属性的值发生变化,不会跟useState或者useReducer一样触发页面变化。要怎样解决这个问题呢?我们先来个小结再解决

小结

  • 我们使用useCurrent传入initialValue,可以得出其变成了一个内含current属性的对象,current属性对应的值为initialValue
  • 根据react文档,可以知道这个对象的地址从头到位都不会变化
  • useRef变化不会主动使页面渲染

应用解决

我们来使用useRef配合useEffect模拟一个ComponentDidUpdate

import React, { useEffect, useRef, useState } from "react";
export default function App() {
  const r = useRef(0);
  const [n, setN] = useState(0);
  useEffect(() => {
    r.current += 1;
    if (r.current > 1) {
      console.log("r.current:" + r.current);
      console.log("n:" + n);
    }
  });
  return (
    <div className="App">
      <h1>n:{n}</h1>
      <h1>r.current{r.current}</h1>
      <button
        onClick={() => {
          setN(n + 1);
        }}
      >
        {" "}
        +1
      </button>
    </div>
  );
}

第一次渲染结果

当点击+1按钮时, 会发现执行了代码,这样就可以实现ComponentDidUpdate

current发生变化了

上面的代码中,可以看出页面上的current发生变化了,这是由于我们的state发生改变,所以将新的current也进行了渲染。

你可以理解为react的机制:

这里有个落单的current,我不管。

这个state发生变化了,快更新页面。等等,把这个current也带上吧。

所以我们可以通过设置一个不实用的state来手动设置current变化主动引发页面更新的情况。

如果你细心一点,就会发现,页面上渲染的值并不是最新的current的值。原因是Effect总是在render后才执行,而这时候current刚刚变成最新的,对react来说,它不会单单为了current再一次执行更新视图。所以页面上依然是旧的current。这也验证了我们的一开始的结论

useRef变化不会主动使页面渲染

我们一般不会让current显示在视图中,上面的例子只是希望你能注意到current的变化和视图之间的微妙关系

小结

由于current不会主动让页面渲染,所以我们最好设置一个假的state,来手动设置让current随着这个假的state一起被视图所更新就行。

不得不说forwardRef

ref

在hook组件上,是不能直接使用ref的,官网甚至不推荐在class组件上使用ref。但是我们也许会用到ref,ref主要用于传递给子组件,并拿到对应子组件的dom。这里是官方文档Refs和函数组件

何时使用 Refs

下面是几个适合使用 refs 的情况:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

解读官方例子

function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 才可以引用它
  const textInput = useRef(null);//创建一个包含current属性的对象

  console.log(textInput);
  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input type="text" ref={textInput} />//挂到内部dom上
      <input type="button" value="Focus the text input" onClick={handleClick} />
    </div>
  );
}

tips:注意这个例子没有用到fowardRef,因为ref没有在子组件上用,而是在函数组件内部用而已

我们可以发现只要把ref挂到某个react元素上,就可以拿到它的dom。

一共分两个步骤

  • useRef创建一个ref对象
  • ref={xx}挂到react元素上

然后就可以使用这个元素了。官方例子是取到这个元素并且通过点击按钮让元素聚焦。我们目前大概理解了ref是怎样用的。

子组件上使用ref

上面的方法不能直接在子组件上使用,也许你会这样写

<Child ref={textInput} />

但是这样还拿不到子组件的DOM,我们需要使用forwardRef配合使用,上面的例子可以写成这样

function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 才可以引用它
  const textInput = useRef(null);

  console.log(textInput);
  function handleClick() {
    textInput.current.focus();
  }
  return (
    <div>
      <Child ref={textInput} />  //**依然使用ref传递**
      <input type="button" value="Focus the text input" onClick={handleClick} />
    </div>
  );
}
const Child = forwardRef((props, ref) => {  //** 看我 **
  return <input type="text" ref={ref} />;//** 看我挂到对应的dom上 **
});

上面是通过forwardRefChild函数包起来,然后传入第二个参数ref最后挂载ref={ref} 这样就可以拿到对应的DOM了,控制台打印一下看看

current: <input type="text"></input>

拿到子组件的DOM元素了。

小结

我们通过上面的例子得出在函数组件内部和子组件上使用ref是不同的

  • 如果要在函数内部使用,那就直接创建后挂ref属性给react元素就行了。
  • 如果要在子组件上使用,除了上面的步骤,还需要使用forwardRef把子组件的函数包起来,然后再传入第二个参数ref,最后挂载ref就可以正常取到DOM了。

总结

我们现在已经知道useRef会生成一个包含current属性的对象,它能够在react渲染全生命周期内保持地址不变,我们可以用它来做一些操作。

例如创建一个全局的始终不变的数据,拿来模拟ComponentDidUpdate

也可以用来挂载到react元素上,拿到这个元素的DOM。(这里区分函数内部组件直接使用ref和子组件配合forwardRef

github

最后推广一下本人长期维护的 github 博客

1.从学习到总结,记录前端重要知识点,涉及 Javascript 深入、HTTP 协议、数据结构和算法、浏览器原理、ES6、前端技巧等内容。

2.在 README 中有目录可对应查找相关知识点。

如果对您有帮助,欢迎star、follow。

地址在这里