type
status
date
slug
summary
category
tags
password
icon
在 React 里,我们经常听到 "everything is a component" 这样的说法。而为了保持组件的纯净性,React 强调声明式编程,减少直接操作 DOM 的情况。然而,有时我们仍然需要直接与 DOM 交互,或者访问某个组件的具体实例。在这些情况下,Refs 就派上用场了。

useRef 基础知识

这是useRef的使用示例,useRef返回一个可变的 ref 对象,通过.current可以获取保存在useRef的值。看起来像是一个复杂版的useState,那么useStateuseRef有什么区别?为什么需要useRef呢?
主要原因有两个:
  1. 持久性useRef的返回对象在组件的整个生命周期中都是持久的,而不是每次渲染都重新创建。
  1. 不会触发渲染:当useState中的状态改变时,组件会重新渲染。而当useRef.current属性改变时,组件不会重新渲染。
总结来说,useRef既能保存状态,还不会在更新时触发渲染。本文我们就来盘点一下useRef的使用场景。

useRef 的常见用途

访问 DOM 元素

当我们需要直接与 DOM 元素进行交互(例如,手动获取焦点或测量元素尺寸)时,可以使用 useRef
我们还可以在组件嵌套的场景使用useRef

保存状态但不触发渲染

有时,你可能需要在组件中保存某些值,而不希望每次该值更改时都重新渲染组件。在这种情况下,useRef很有用。
这个示例完美说明了可以把useRef视为一个能够在组件的整个生命周期中持久保存数据的“盒子”,而不会引起组件的重新渲染。

保存上一次的 props 或 state

在某些情况下,你可能需要知道 props 或 state 的上一次值。这时可以使用useRef结合useEffect来达到目的。
当组件首次渲染时,previousValue.current会被初始化为value的当前值。随后,每当value发生变化时,useEffect都会运行并更新previousValue.current为新的value
但这里有一个微妙之处:由于useEffect是在组件渲染之后运行的,因此在组件的渲染过程中,previousValue.current的值是从前一次渲染中保持不变的。只有当useEffect被调用并执行完毕后,previousValue.current才会更新为新的value

高级技巧

避免在渲染期间读/写 ref

这里,我们尝试在组件的渲染期间更新previousValue.current。这违反了 React 的工作方式,并可能导致不可预测的行为。例如:
  1. 不稳定的 UI:由于 React 在多次渲染中可能使用异步和优化技术,直接在渲染期间修改 refs 可能导致 UI 不一致。
  1. 依赖更新:如果其他效应或钩子依赖于 ref 的值,它们可能不会在期望的时刻运行,因为直接修改 ref 不会触发重新渲染或其他效应。
这是为什么我们通常在useEffect内部更新 refs。在useEffect内部,我们可以确保组件已经完成渲染,并且不会在渲染期间发生任何不期望的副作用。

避免重复创建 ref

如果我们在创建 ref 时,想要通过计算或有副作用的方法获取初值,可能会用下面这种写法。这种写法会导致getInitialCount()在每次组建渲染的时候都被调用。虽然useRef的设计让它只从首次渲染的时候获取初值,但这种做法仍然会造成不必要的性能损耗。
解决这种场景下的 ref 创建也很简单,那就是用null作为初始值,渲染的过程判断仅在null时去计算或调用有副作用的方法。

与 useReducer 使用

当我们需要复杂的状态逻辑且希望避免额外的渲染时,可以考虑将useRefuseReducer结合使用。
例如:跟踪useReducer的 action 数量。

与第三方库集成

在使用非 React 库(如 D3、jQuery)时,我们可能需要使用useRef来获得对真实 DOM 节点的引用。
例如:结合D3

动画处理

通过useRef获取元素并使用 Web API 如requestAnimationFrame可以实现复杂的动画效果。

事件监听

使用useRef监听不由 React 管理的 DOM 事件。
例如:窗口大小变化

结语

在本篇文章中,我们从基本的 DOM 引用出发,探讨了各种实际的应用场景,包括性能优化和动画方面。通过深入了解并有效使用 useRef,我们可以更灵活地管理组件内部的状态,而不必担心触发不必要的渲染。希望这篇文章能帮助你更好地理解useRef并能让你有所启发。
以上多个重要示例的实际效果都可以在我的示例站查看,TypeScript版的源码也已发布到我的Github:useRef分支

专栏资源

专栏博客地址:精读React Hooks
专栏演示站:React Hooks Demos
专栏源码仓库:👉Github - Source Code
国内镜像仓库:👉Gitee — Source Code
 
专栏文章列表:
精读React hooks(五):useEffect使用细节知多少?精读React hooks(三):useContext从基础应用到性能优化