问题
有时React应用程序需要响应一天中的时间变化。它可能只需要显示当前时间,或者可能需要定期轮询服务器,或者在日夜交替时更新界面。但是,如何才能使代码在时间变化时触发重新渲染?如何避免过于频繁地渲染组件?如何在不使代码过于复杂的情况下实现这些目标呢?
解决方案
我们将创建一个useClock Hook。useClock使我们能够访问格式化的日期和时间,并在时间改变时自动更新界面。下面是一个真实环境下的代码示例,图3-13显示了正在运行的程序界面:
图3-13:SimpleTicker三秒内的变化
time变量包含格式为HH:mm:ss的当前时间。当时间发生变化时,isTick状态的值在true和false之间切换,然后用来显示“ Tick! ”或“ Tock! ”,我们使用ClockFace组件展示当前时间。
除了接受日期和时间格式,useClock还可以接受一个数字,指定以毫秒为单位的更新间隔(如图3-14所示):
图3-14:IntervalTicker每三秒重新渲染一次组件
如果你希望定期执行某些任务,例如轮询网络服务,那么这个版本会更好用。
如果要轮询网络服务,可以考虑使用本书5.1节的时钟。如果时钟的当前值作为依赖传递给进行网络调用的Hook,则每次时钟的值发生改变时,都会发起网络调用。
如果你传递一个数值类型的参数给useClock,它将返回一个ISO格式的时间字符串,如2021-06-11T14:50:34.706。
为了创建这个Hook,我们将使用第三方库Moment.js( https://momentjs.com )来处理日期和时间格式。如果你更喜欢使用另一个库,比如Day.js( https://day.js.org ),也可以方便地进行切换:
下面是useClock的代码:
我们从传递给该Hook的formatOrInterval参数中得到日期和时间的格式(format变量),以及执行任务的时间间隔(interval变量)。然后,我们用setInterval创建一个定时器,每interval毫秒更新response变量的值。当我们将response字符串设置为新的时间时,依赖于useClock的组件都将重新渲染。
我们需要确保取消任何不再使用的定时器。我们可以使用useEffect Hook的一个特性来做到这一点。如果在useEffect代码的末尾返回一个函数,那么该函数将在useEffect下一次需要运行时被调用。因此,我们可以在创建新定时器之前使用该函数来清除旧定时器。
如果我们传递一个新的时间格式或者时间间隔给useClock,useClock会取消旧的定时器,并使用新的定时器来更新response的值。
讨论
这个解决方案是一个关于如何使用Hook便捷地解决一个简单问题的示例。正如“React”这一单词含义所指,React代码会对依赖项的变更作出响应,而不是思考“如何能每秒运行这段代码?”,useClock Hook让你可以编写依赖于当前时间的代码,并隐藏所有关于创建定时器、更新状态和清除定时器的烦琐细节。
如果在组件中多次使用useClock Hook,那么时间变化可能导致多次渲染。例如,如果你有两个时钟,它们将当前时间格式化为12小时格式(04:45)和24小时格式(16:45),那么当分钟改变时,组件将渲染两次。每分钟一次的额外渲染对性能应该没有太大的影响。
你也可以在其他Hook中使用useClock Hook。如果你创建了一个useMessages Hook来从服务器检索消息,那么你可以在它内部调用useClock来定期轮询服务器。
你可以从GitHub网站( https://oreil.ly/hohKK )下载本解决方案的源代码。