如何在useMemo中正确使用元素ref?解决初始渲染ref未定义问题
解决方案
方案一:使用useEffect配合useRef保存动画实例
利用useEffect在DOM更新完成后执行的特性,确保sideNavRef已指向真实DOM元素,再创建动画并通过useRef持久化保存实例,避免重复创建。
import { useRef, useEffect } from 'react'; // ... 组件其他逻辑 const sideNavRef = useRef(null); const animationRef = useRef(null); // 用于持久化保存动画实例 useEffect(() => { if (!sideNavRef.current) return; // 创建关键帧动画 const animations = createSideNavKeyframes({ startingWidth: (7 * document.body.offsetWidth) / 100, endingWidth: 55, }); const animation = sideNavRef.current.animate(animations.animation); animation.pause(); animationRef.current = animation; // 组件卸载时清理动画,避免内存泄漏 return () => { animation.cancel(); }; }, []); // 空依赖确保只执行一次
- 后续可通过
animationRef.current调用play()/pause()/reverse()等方法控制动画状态。 - 清理函数会在组件卸载时取消动画,防止残留实例占用内存。
方案二:使用ref回调函数绑定动画生命周期
将ref赋值与动画创建逻辑合并到回调函数中,精准把控元素挂载/卸载时的动画创建与清理时机。
import { useRef } from 'react'; // ... 组件其他逻辑 const animationRef = useRef(null); const handleSideNavRef = (element) => { // 同步更新sideNavRef指向 sideNavRef.current = element; if (element) { // 元素挂载时创建动画 const animations = createSideNavKeyframes({ startingWidth: (7 * document.body.offsetWidth) / 100, endingWidth: 55, }); const animation = element.animate(animations.animation); animation.pause(); animationRef.current = animation; } else { // 元素卸载时清理动画 if (animationRef.current) { animationRef.current.cancel(); animationRef.current = null; } } }; // 在nav元素上绑定回调ref return <nav ref={handleSideNavRef}>...</nav>;
- 回调函数会在元素挂载(传入DOM节点)和卸载(传入null)时自动触发,逻辑更紧凑。
- 无需额外依赖
useEffect,直接关联元素生命周期。
原useMemo方案失效原因
useMemo的执行时机是组件渲染期间,此时DOM尚未完成更新,sideNavRef.current还未指向真实元素,因此无法创建动画,最终返回undefined。上述两种方案均选择在DOM元素挂载完成后执行动画创建逻辑,从根源解决了ref未定义的问题。
内容的提问来源于stack exchange,提问作者LoyalPotato
相关产品推荐
相关产品推荐

