精读React hooks(四):useRef的多维用途
在 React 里,我们经常听到 "everything is a component" 这样的说法。而为了保持组件的纯净性,React 强调声明式编程,减少直接操作 DOM 的情况。然而,有时我们仍然需要直接与 DOM 交互,或者访问某个组件的具体实例。在这些情况下,Refs 就派上用场了。
useRef 基础知识
这是useRef
的使用示例,useRef
返回一个可变的 ref 对象,通过.current
可以获取保存在useRef
的值。看起来像是一个复杂版的useState
,那么useState
和useRef
有什么区别?为什么需要useRef
呢?
主要原因有两个:
- 持久性:
useRef
的返回对象在组件的整个生命周期中都是持久的,而不是每次渲染都重新创建。
- 不会触发渲染:当
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 的工作方式,并可能导致不可预测的行为。例如:
- 不稳定的 UI:由于 React 在多次渲染中可能使用异步和优化技术,直接在渲染期间修改 refs 可能导致 UI 不一致。
- 依赖更新:如果其他效应或钩子依赖于 ref 的值,它们可能不会在期望的时刻运行,因为直接修改 ref 不会触发重新渲染或其他效应。
这是为什么我们通常在useEffect
内部更新 refs。在useEffect
内部,我们可以确保组件已经完成渲染,并且不会在渲染期间发生任何不期望的副作用。
避免重复创建 ref
如果我们在创建 ref 时,想要通过计算或有副作用的方法获取初值,可能会用下面这种写法。这种写法会导致getInitialCount()
在每次组建渲染的时候都被调用。虽然useRef
的设计让它只从首次渲染的时候获取初值,但这种做法仍然会造成不必要的性能损耗。
解决这种场景下的 ref 创建也很简单,那就是用null
作为初始值,渲染的过程判断仅在null时去计算或调用有副作用的方法。
与 useReducer 使用
当我们需要复杂的状态逻辑且希望避免额外的渲染时,可以考虑将useRef
与useReducer
结合使用。
例如:跟踪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
交个朋友:👉加入「独立全栈交流群」
专栏文章列表:
精读React hooks(一):useState 的几个基础用法和进阶技巧
精读React hooks(二):React状态管理的强大工具——useReducer
精读React hooks(三):useContext从基础应用到性能优化
精读React hooks(四):useRef的多维用途
精读React hooks(五):useEffect使用细节知多少?
精读React hooks(六):useLayoutEffect解决了什么问题?
精读React hooks(七):用useMemo来减少性能开销
精读React hooks(八):我们为什么需要useCallback
精读React hooks(九):使用useTransition进行非阻塞渲染
精读React hooks(十):使用useDeferredValue延迟状态更新
精读React hooks(十一):useInsertionEffect——CSS-in-JS样式注入新方式
精读React hooks(十二):使用useImperativeHandle能获得什么能力
精读React hooks(十三):使用useSyncExternalStore获取实时数据
精读React hooks(十四):总有一天你会需要useId为你生成唯一id
精读React hooks(十五):把useDebugValue加入你的React调试工具库
精读React hooks(十六):一个为代码优雅而生的hook——use