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>
)
}
我们可以使用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>
</>
)
}
🤔 等等!有什么区别?
我第一次看到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>
</>
)
}
如您所见,refFromUseRef即使组件重新渲染,它的值仍然保持不变,而其他组件则refFromCreateRef不会。
你可以在 Ryan Cogswell 在Stack Overflow上的回答中找到useRef和createRef的比较。
👏 有意思!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
假设用户名始终保持不变。我们预期该效果仅在用户体重增加时才输出警告文本。然而,如果您测试上面的代码,您会发现每次用户点击按钮时,即使属性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
我们刚刚移除了 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
为了跟踪该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
}
为了使其更通用,我将重命名previousUserRef为ref
const usePrevious = (value) => {
const ref = React.useRef()
React.useEffect(() => {
ref.current = value
}, [value])
return ref.current
}
让我们将自定义的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
💪 太棒了!你还可以将深度比较逻辑提取到一个新的自定义 Hook 中。请查看Kent C. Dodds 的use-deep-compare-effect。
3. 结论:
🚀 useRef Hook 的功能远不止管理 DOM ref,它绝对不是createRef 的替代品。useRef可以将一个值持久化到组件的整个生命周期。但是请注意,当useRef的当前值发生变化时,组件不会重新渲染;如果您需要重新渲染的效果,请改用useState Hook 👏👏👏
以下是一些对你有用的资源:
🙏 💪 感谢阅读!
我非常想听听大家的想法和反馈。欢迎在下方留言!
✍️ 作者:
惠贞 🔥 🎩 ♥️ ♠️ ️ ♣️ 🤓
软件开发人员 | 魔术爱好者
打个招呼👋
✅ GitHub
✅中等
文章来源:https://dev.to/dinhhuyams/introduction-to-useref-hook-3m7n

