React Hooks大全—useRef

本文将重点介绍useRef这个Hook,它可以让你在组件的整个生命周期中访问一个可变的引用对象。

useRef的主要用途是直接访问DOM子元素,但这并不是它的唯一用途。useRef也可以用来保存一个在不同渲染中不变的可变值,例如在使用一些非React的外部库时很有用。本文将介绍useRef的基本使用,实现原理,最佳实践和一些常见的问题。

公众号:Code程序人生,个人网站:https://creatorblog.cn

基本使用

要在组件中创建一个新的引用,可以使用以下代码:

// 创建一个引用
const yourRef = useRef();

你也可以选择用一个默认值来初始化它,只需将它作为参数传递给useRef即可:

// 创建一个引用
const yourRef = useRef('hello world');

useRef是一个Hook,因此只能在函数组件中使用!

要获取和设置引用的值,你可以访问对象的.current属性,如下所示:

// 创建一个引用
const exampleRef = useRef();

// 设置引用的值
exampleRef.current = "Hello World";

// 访问引用的值:
// 这会在控制台打印"Hello World"
console.log(exampleRef.current);

要访问一个DOM元素,你可以创建一个引用,然后使用它的ref属性将它分配给你想要目标的DOM元素,然后你就可以使用它了。

例如,假设你想要获取一个DOM元素的像素高度。要做到这一点,你必须访问DOM元素的offsetHeight属性。但是如何获取DOM元素呢?当然是用一个引用。

import { useEffect, useRef } from "react";

export default function App() {
  // 创建一个引用
  const divElement = useRef();

  // 在组件第一次渲染时触发
  useEffect(() => {
    // 获取div元素的高度
    console.log("The height of the div is: ", divElement.current.offsetHeight);
  }, []);

  return (
    <div ref={divElement}>
      <h1>Learn about useRef!</h1>
    </div>
  );
}

在上面的例子中,我使用了useEffect来确保只有在组件第一次渲染后才调用divElement.current.offsetHeight。事实上,一开始,引用是undefined的,只有当组件渲染并且div被创建后,它才会持有它的DOM元素值。你可以看到,使用引用可能有些棘手,你必须记住一些事情。

实现原理

要理解useRef的工作原理,我们需要先了解一些React的基础知识。

React是一个用于构建用户界面的库,它通过一个虚拟的DOM来管理真实的DOM。虚拟DOM是一个JavaScript对象,它表示真实的DOM结构,但是更轻量级和高效。React使用虚拟DOM来跟踪组件的状态和属性,然后根据需要更新真实的DOM

当你在组件中使用useStateuseReducerHook来管理状态时,你实际上是在更新虚拟DOM中的状态。每当状态发生变化时,React都会重新渲染组件,并将新的虚拟DOM与旧的虚拟DOM进行比较,找出差异,然后将这些差异应用到真实的DOM上。这个过程被称为协调(reconciliation)

但是,有时候你可能不想让React管理你的状态,而是想直接操作真实的DOM。这可能是因为你需要使用一些非React的库,或者你需要访问一些React无法提供的DOM属性或方法。这时候,你就可以使用引用(ref)来实现。

useRef的基本实现原理其实很简单,它就是利用了JavaScript的闭包和对象引用的特性,来创建一个只有一个current属性的对象,然后将这个对象保存在一个变量中,这个变量在每次渲染时都会被复用,而不会被重新赋值。我们可以用下面的伪代码来模拟useRef的实现:

// 定义一个变量,用来存储ref对象
let ref;

// 定义useRef函数,接收一个初始值作为参数
function useRef(initialValue) {
  // 如果ref不存在,就创建一个新的对象,将初始值赋给它的current属性
  if (!ref) {
    ref = { current: initialValue };
  }
  // 返回ref对象
  return ref;
}

可以看到,useRef函数只会在第一次调用时创建一个新的对象,然后将它赋值给ref变量,之后的调用都会返回这个变量,而不会创建新的对象。这样,我们就可以在组件的不同渲染之间保持一个可变的值,而不用担心它会被重新创建或销毁。

引用是一个对象,它有一个.current属性,可以指向任何你想要的值。当你创建一个引用时,React会将它保存在一个内部的数据结构中,这个数据结构不会随着组件的重新渲染而改变。这意味着,你可以在组件的整个生命周期中访问同一个引用对象,而不用担心它会丢失或改变。

当你将一个引用分配给一个DOM元素时,React会在渲染时将该元素的实例赋值给引用的.current属性。这样,你就可以通过引用来直接访问该元素,而不需要通过虚拟DOM或其他方式。你也可以在任何时候修改引用的值,而不会触发组件的重新渲染,因为引用不是一个状态,而是一个可变的容器。

最佳实践

useRef是一个非常有用的Hook,但是也有一些注意事项和最佳实践,我们应该遵守,以避免一些潜在的问题和错误。下面列举了一些常见的最佳实践:

  • 尽量避免直接操作DOM,除非React没有提供相应的API。直接操作DOM会破坏React的声明式编程范式,导致组件的状态和DOM的状态不一致,可能引发一些难以发现和解决的bug。如果我们需要对DOM元素进行一些操作,我们应该优先考虑使用React提供的API,比如ref、state、props、effect等,只有当这些API都不能满足我们的需求时,我们才可以使用useRef来直接操作DOM。
  • 使用useRef来存储一些不会触发组件重新渲染的值,比如定时器、订阅、外部库的实例等。这些值在组件的不同渲染之间保持不变,而且不会影响组件的渲染,因此使用useRef来存储它们是合适的。如果我们使用useState来存储这些值,那么每次修改它们的值都会触发组件的重新渲染,这是不必要的,而且会影响性能。
  • 不要使用useRef来存储一些会触发组件重新渲染的值,比如状态、数据、计算结果等。这些值在组件的不同渲染之间可能会发生变化,而且会影响组件的渲染,因此使用useRef来存储它们是不合适的。如果我们使用useRef来存储这些值,那么当它们的值发生变化时,组件不会重新渲染,这会导致组件的状态和界面不一致,可能引发一些难以发现和解决的bug。我们应该使用useState或useReducer来存储这些值,这样当它们的值发生变化时,组件会重新渲染,保持状态和界面的一致性。
  • 在组件卸载时,清除使用useRef存储的一些需要清理的值,比如定时器、订阅、外部库的实例等。这些值在组件卸载后,可能会继续运行或占用资源,导致内存泄漏或其他问题。我们应该使用useEffect来在组件卸载时,清除这些值,释放资源,避免潜在的问题。例如,如果我们使用useRef来存储一个定时器,我们应该在useEffect的返回函数中,清除这个定时器,代码如下:
// 使用useRef存储一个定时器
const timer = useRef();

// 在组件卸载时,清除定时器
useEffect(() => {
  return () => {
    clearInterval(timer.current);
  };
}, []);
  • 不要混淆useRef和createRef。useRef和createRef都可以用来创建ref对象,但它们的行为和用途有一些区别。useRef是一个Hook,只能在函数组件中使用,它会在每次渲染时返回同一个ref对象,因此可以用来在组件的不同渲染之间保持一个可变的值。createRef是一个方法,可以在类组件和函数组件中使用,它会在每次调用时返回一个新的ref对象,因此只能用来访问DOM元素或子组件的实例,不能用来在组件的不同渲染之间保持一个可变的值。我们应该根据不同的场景,选择合适的方法来创建ref对象,避免混淆它们的行为和用途。

总结

useRef是一个非常强大和有用的Hook,它可以让我们直接访问和操作DOM元素,以及在组件的不同渲染之间保持一个可变的值。

它的实现原理很简单,就是利用了JavaScript的闭包和对象引用的特性,来创建一个只有一个current属性的对象,然后将这个对象保存在一个变量中,这个变量在每次渲染时都会被复用,而不会被重新赋值。

我们在使用useRef时,应该遵守一些注意事项和最佳实践,以避免一些潜在的问题和错误。useRef与其他Hook,比如useStateuseEffectuseCallback等,有着不同的功能和优势,我们应该根据不同的场景,选择合适的Hook来实现我们的需求,从而提高我们的代码的可读性、可维护性和性能。

你可能感兴趣的:(react,前端,面试,react.js,javascript,前端)