React Hooks 系列:useRef
第三部分 - useRef
介绍
请务必先查看我的定时器代码沙箱。您可以随意使用定时器,fork 这个沙箱,查看代码,甚至可以重构代码使其更好!
我的 React Hooks 系列前两篇文章分别讲解了 useState 和 useEffect。本文将重点介绍 useRef,这是我最喜欢的 Hook 之一。我坦白承认,我绝非 useRef 专家,本文仅介绍我如何在 Timer 示例中实现 useRef Hook。
短暂绕道
我们来讨论一下为什么我的计时器应用需要 useRef hook。
这与按钮及其行为有关PAUSE。最初,我的暂停功能没有绑定 useRef。当用户尝试暂停时,经常会出现延迟,而且计时器还会再倒计时一秒。
我们应该研究这种特定行为,因为我们还可以更好地了解 useEffect 和 setTimeout。
提醒一下,PAUSE当两个start === trueANDcounter都不完全相等时,我会有条件地渲染按钮0。
{
start === true && counter !== 0
?
<button style={{fontSize: "1.5rem"}} onClick={handlePause}>PAUSE</button>
:
null
}
换句话说,计时器运行时,暂停按钮会显示出来。
const handlePause = () => {
setStart(false)
}
如您所见,handlePause设置start后false暂停按钮消失(显示为 null),开始按钮则显示在暂停按钮的位置。
状态start已从 true 变为 false,触发了我们的第一个 useEffect(pauseTimer.current暂时忽略它):
useEffect(() => {
if (start === true) {
pauseTimer.current = counter > 0 && setTimeout(() => setCounter(counter - 1), 1000)
}
return () => {
clearTimeout(pauseTimer.current)
}
}, [start, counter, setCounter])
当用户按下按钮时PAUSE,useEffect 会检查是否已关闭start === true(现在不再检查了),但之前渲染的 setTimeout 仍在运行,直到 useEffect 确定已start关闭true才会再次运行 setTimeout。然而,延迟的出现是因为之前的 setTimeout 已经运行完毕。到那时往往为时已晚,又过了一秒钟。
想亲眼看看这个现象吗?打开Timer CodeSandbox,删除pauseTimer.current =第 19 行的代码,运行计时器并尝试暂停几次。你会发现计时器不会立即暂停。
既然我们已经了解了问题所在,我们就可以解决它了!
使用 useRef 钩子来解决问题!
第三部分 - useRef
理解 useRef 可能需要一些时间。我知道我当初也是这样。首先,让我们看看React 文档是怎么说的:
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的对象将在组件的整个生命周期内保持不变。
你说什么?
如果你不确定这些话是什么意思,你并不孤单!
我发现Lee Warrick写的这篇博客文章很有帮助,特别是他对 useRef 的解释:
refs 存在于重新渲染周期之外。
你可以把 refs 想象成一个你暂时设置好的变量。当你的组件重新运行时,它会忽略这个 ref,直到你在某个地方使用 `.current` 调用它为止。
那一刻我恍然大悟。ref 是一个可以基于状态中的对象定义的变量,即使状态发生变化,它也不会受到影响。它会一直保持其值,直到你告诉它执行其他操作为止!
让我们在计时器应用程序中看看它的实际效果。
将 useRef 添加到我们的 React 导入中:
import React, { useState, useEffect, useRef } from "react";
文档原文:
const refContainer = useRef(initialValue);
定义一个对象实例以便稍后“引用”。
我们的长这样:
const pauseTimer = useRef(null)
务必给它起一个有意义的名字,尤其是在使用多个 useRefs 时。我的 useRefs 之所以叫这个名字,是pauseTimer因为这就是我希望它在被调用时执行的操作。`is`null是我的初始值,useRef()因为在我的函数中,`is` 的初始状态并不重要pauseTimer。我们只关心计时器开始倒计时后对 `pauseTimer` 的引用是什么。
pauseTimer是一个具有属性的对象current。通过 useRef 创建的每个 ref 都将是一个具有属性的对象current。pauseTimer.current将是一个我们可以设置的值。
让我们再来看一下 useEffect,这次特别注意一下pauseTimer.current。这里我们将条件(counter大于0?)setTimeout 的值设置为pauseTimer.current。这样我们就可以在任何地方访问 setTimeout 的值了!
useEffect(() => {
if (start === true) {
pauseTimer.current = counter > 0 && setTimeout(() =>
setCounter(counter - 1), 1000)
}
return () => {
clearTimeout(pauseTimer.current)
}
}, [start, counter, setCounter])
从这里开始就很简单了。当用户选择PAUSE“立即”时,start更新会执行useEffect 函数false,但 useEffect 无法运行 setTimeout 函数,因此会运行清理函数:
return () => {
clearTimeout(pauseTimer.current)
}
如果我们不在pauseTimer.currentclearTimeout 中使用 setTimeout,计时器会像以前一样继续运行一秒钟,因为if (start === true)即使我们提前将 setTimeout 设置start为false一秒,条件块中的 setTimeout 也会运行完整个周期。
但是!由于我们pauseTimer.current在 clearTimeout 中引用了当前的 setTimeout 值,useEffect 将跳过该引用if (start === true),立即运行其清理函数,从而阻止 setTimeout 的执行!
这就是 useRef 的强大之处!它允许你在任何地方访问对某个值的引用(你甚至可以将它们从父级传递给子级!),而且这些引用不会改变,直到你告诉它改变为止(就像我们每秒更新一次计时器一样)。
奖金
这只是 useRef 的冰山一角。您可能更熟悉 useRef 以及如何与 DOM 元素交互。
在我的作品集网站中,useRef 决定了我如何打开和关闭动画导航屏幕。
在我的组件函数 SideNavBar 中:
我定义我的参考
const navRef = useRef()
创建用于关闭和打开导航的函数
function openNav() {
navRef.current.style.width = "100%"
}
function closeNav() {
navRef.current.style.width = "0%"
}
并将 Reactref属性设置div为navRef
<div id="mySidenav" className="sidenav" ref={navRef}>
我的 CSS 文件中包含sidenav该类
.sidenav {
height: 100%;
width: 0;
position: fixed;
z-index: 2;
top: 0;
left: 0;
background-color: #212121;
overflow-x: hidden;
transition: 0.6s;
padding-top: 5rem;
}
很酷吧?
navRef它与 DOM 元素交互,div className="sidenav"因为它具有该属性ref={navRef},并且当openNav()调用时,navRef.current.style.width会被更新为"100%"。
反之亦然,当调用“closeNav()”时也是如此。
总结
希望您喜欢我的 React Hooks 系列文章的第三篇!如果您已经读到这里,首先……
其次
我计划继续推出 React Hooks 系列文章。我可能会讲解同一个 Hooks 的不同方面,也可能探索全新的 Hooks。敬请期待!一如既往,再次感谢大家的支持。有人愿意阅读我的文章,对我来说意义非凡。
请留下评论、反馈或更正。我确信我遗漏了某些内容,或者可能对某些概念的解释有误。如果您发现任何问题,请告诉我!我这样做也是为了自我学习。
下次再见……
祝您编程愉快!
文章来源:https://dev.to/jamesncox/react-hooks-series-useref-27mk


