发布于 2026-01-05 8 阅读
0

React useRef 简介 useRef Hook DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

React useRef简介 useRef Hook

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

前提条件:具备React、 Refs 和React DOM的基础知识

本文将讨论useRef hook 是什么以及何时可以使用它。

我第一次学习 Hooks 时,有很多问题需要寻找答案。其中一个问题是如何比较当前状态/属性与之前的状态/属性,或者如何在useEffect Hook中处理深层对象比较。直到我学习了useRef Hook,一切才豁然开朗。

💪 让我们开始吧!

1. 什么是 useRef hook?

Refs 提供了一种访问 render 方法中创建的 DOM 节点或 React 元素的方法。

我们的示例演示了如何在用户点击按钮时管理输入框的焦点。为此,我们将使用createRef API。

createRef API

import {createRef} from 'react' 

const FocusInput = () => {
   const inputEl = createRef()

   const focusInput = () => {
      inputEl.current.focus()
   }

   return (
      <>
         <input ref={inputEl} type="text" />
         <button onClick={focusInput}>Focus input</button>
      </div>
   )
}
Enter fullscreen mode Exit fullscreen mode

我们可以使用useRef hook实现完全相同的结果。

useRef 钩子

const FocusInput = () => {
   const inputEl = React.useRef()

   const focusInput = () => {
      inputEl.current.focus()
   }

   return (
      <>
         <input ref={inputEl} type="text" />
         <button onClick={focusInput}>Focus input</button>
      </>
   )
}
Enter fullscreen mode Exit fullscreen mode

🤔 等等!有什么区别?

多珀尔格尔

我第一次看到useRef 的时候也有同样的疑问。既然可以用createRef API 来管理输入框的焦点,为什么还要用useRef hook 呢?难道 React 团队只是为了让代码看起来更统一,才在 React 16.8 引入 Hooks 的时候创建了一个类似的函数吗?

区别在于,createRef每次渲染都会返回一个新的 ref,而useRef每次都返回同一个 ref 。

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

const Test = () => {
    const [renderIndex, setRenderIndex] = React.useState(1)
   const refFromUseRef = React.useRef()
   const refFromCreateRef = createRef()

   if (!refFromUseRef.current) {
      refFromUseRef.current = renderIndex
   }

   if (!refFromCreateRef.current) {
      refFromCreateRef.current = renderIndex
   }

   return (
      <>
         <p>Current render index: {renderIndex}</p>
         <p>
            <b>refFromUseRef</b> value: {refFromUseRef.current}
         </p>
         <p>
            <b>refFromCreateRef</b> value:{refFromCreateRef.current}
         </p>

         <button onClick={() => setRenderIndex(prev => prev + 1)}>
            Cause re-render
         </button>
      </>
   )
}
Enter fullscreen mode Exit fullscreen mode

如您所见,refFromUseRef即使组件重新渲染,它的值仍然保持不变,而其他组件则refFromCreateRef不会。

比较 useRef 和 createRef

你可以在 Ryan Cogswell 在Stack Overflow上的回答中找到useRefcreateRef的比较。

👏 有意思!useRef可以在其属性中保存值.current,并且在组件重新渲染后该值仍然存在。因此,useRef 的用途远不止管理组件引用。

2. 超越 Ref 属性

除了ref属性之外,我们还可以使用useRef hook 来进行自定义比较,而不是像useEffect hook 那样使用默认的浅比较。看看我们的示例吧😇

const Profile = () => {
   const [user, setUser] = React.useState({name: 'Alex', weight: 40})

   React.useEffect(() => {
      console.log('You need to do exercise!')
   }, [user])

   const gainWeight = () => {
      const newWeight = Math.random() >= 0.5 ? user.weight : user.weight + 1
      setUser(user => ({...user, weight: newWeight}))
   }

   return (
      <>
         <p>Current weight: {user.weight}</p>
         <button onClick={gainWeight}>Eat burger</button>
      </>
   )
}

export default Profile
Enter fullscreen mode Exit fullscreen mode

假设用户名始终保持不变。我们预期该效果仅在用户体重增加时才输出警告文本。然而,如果您测试上面的代码,您会发现每次用户点击按钮时,即使属性weight保持不变,我们的效果也会运行。这是因为useEffect Hook 默认使用浅比较,而我们的userState属性是一个对象。🐛🐛🐛

🔧 要修复这个错误,我们需要编写自己的比较函数,而不是使用默认的比较函数。

👉第一步:使用 lodashisEqual方法进行深度比较

const Profile = () => {
   const [user, setUser] = React.useState({name: 'Alex', weight: 40})

   React.useEffect(() => {
       if (!_.isEqual(previousUser, user) {
           console.log('You need to do exercise!')
       }
   })

    ...
}

export default Profile
Enter fullscreen mode Exit fullscreen mode

我们刚刚移除了 effect 中的依赖数组,并改用 lodashisEqual方法进行深度比较。不幸的是,由于缺少值,我们遇到了一个新问题。如果我们在ComponentDidUpdatepreviousUser生命周期中对类组件执行相同的操作,则可以轻松获取之前的状态值。

🔥 useRef来救场了

👉步骤 2:使用引用保存先前的状态

const Profile = () => {
   const [user, setUser] = React.useState({name: 'Alex', weight: 20})

   React.useEffect(() => {
       const previousUser = previousUserRef.current
       if (!_.isEqual(previousUser, user) {
           console.log('You need to do exercise!')
       }
   })

   const previousUserRef = React.useRef()
   React.useEffect(() => {
      previousUserRef.current = user
   })

    ...
}

export default Profile
Enter fullscreen mode Exit fullscreen mode

为了跟踪该previousUser值,我们将其保存到useRef.current hook的属性中,因为即使组件重新渲染,该值也能保留。为此,我们将使用另一个 effect 在每次渲染后更新该值。最后,我们可以从该属性中提取值,然后将其与先前的值进行深度比较,以确保我们的 effect 仅在新旧值不同时才运行。previousUserRef.currentpreviousUserpreviousUserRef.current

👉第三步:将效果提取到自定义 Hooks 中

如果你想重用这段代码,我们可以创建一个新的自定义钩子。我只是把上面的代码提取到一个名为usePrevious 的函数中。

const usePrevious = (value) => {
    const previousUserRef = React.useRef()
   React.useEffect(() => {
      previousUserRef.current = value
   }, [value])

   return previousUserRef.current
}
Enter fullscreen mode Exit fullscreen mode

为了使其更通用,我将重命名previousUserRefref

const usePrevious = (value) => {
    const ref = React.useRef()

    React.useEffect(() => {
      ref.current = value
    }, [value])

    return ref.current
}
Enter fullscreen mode Exit fullscreen mode

让我们将自定义的usePrevious钩子应用到代码中。

const Profile = () => {
    const initialValue = {name: 'Alex', weight: 20}
   const [user, setUser] = React.useState(initialValue)

    const previousUser = usePrevious(user)

   React.useEffect(() => {
       if (!_.isEqual(previousUser, user) {
           console.log('You need to do exercise!')
       }
   })

   const gainWeight = () => {
      const newWeight = Math.random() >= 0.5 ? user.weight : user.weight + 1
      setUser(user => ({...user, weight: newWeight}))
   }

   return (
      <>
         <p>Current weight: {user.weight}</p>
         <button onClick={gainWeight}>Eat burger</button>
      </>
   )
}

export default Profile
Enter fullscreen mode Exit fullscreen mode

💪 太棒了!你还可以将深度比较逻辑提取到一个新的自定义 Hook 中。请查看Kent C. Dodds 的use-deep-compare-effect。

3. 结论:

🚀 useRef Hook 的功能远不止管理 DOM ref,它绝对不是createRef 的替代品。useRef可以将一个值持久化到组件的整个生命周期。但是请注意,当useRef当前值发生变化时,组件不会重新渲染;如果您需要重新渲染的效果,请改用useState Hook 👏👏👏

以下是一些对你有用的资源:

🙏 💪 感谢阅读!

我非常想听听大家的想法和反馈。欢迎在下方留言!

✍️ 作者:

惠贞 🔥 🎩 ♥️ ♠️ ️ ♣️ 🤓

软件开发人员 | 魔术爱好者

打个招呼👋

GitHub

LinkedIn

中等

文章来源:https://dev.to/dinhhuyams/introduction-to-useref-hook-3m7n