龙空技术网

Timer:React 经验和结论

启辰8 52

前言:

现时咱们对“响应头 date”可能比较重视,我们都需要分析一些“响应头 date”的相关资讯。那么小编也在网摘上收集了一些有关“响应头 date””的相关知识,希望我们能喜欢,我们快快来了解一下吧!

欢迎大家来到本次小编的总结。 我们将创建一个 React 计时器,并将其与本系列第一篇文章中我们在 Svelte 中所做的进行比较。

请注意,我决定不为该计时器设置动画。 我进行了一些关于如何使用 React 制作动画的搜索,与学习在 Svelte 中制作动画相比,学习曲线令人望而生畏。 我只是不想浪费时间学习一些我知道我不会使用的东西。

我们会很快(希望)完成这一任务,因为我们已经知道如何制作计时器以及我们需要什么。

第 1 步:基本标记、样式和数字格式

让我们复制上一篇文章中的格式化函数,并返回一个简化的标记,因为我们不会制作动画。

请注意,变量 hh、mm 和 ss 现在是根据 React 的要求进行状态的。 否则,UI 将不会更新。

import { useState } from 'react';export type TimerProps = {  countFrom?: number;};function f(value) {  if (value < 10) {    return `0${value}`;  }  return value.toString();}function Timer({ countFrom = 0 }: TimerProps) {  const [hh, setHh] = useState(countFrom);  const [mm, setMm] = useState(countFrom);  const [ss, setSs] = useState(countFrom);  return (    <span className="timer">      {f(hh)}:{f(mm)}:{f(ss)}    </span>  );}export default Timer;

这会生成一个显示为 00:00:00 的计时器组件。

第 2 步:计数功能

据我所知,React 没有与 Svelte 存储相当的东西,而且我不会使用 Redux 作为计时器,因此我们将在不使用外部库的情况下以我所知道的最佳方式实现该功能。

我们的算法需要一个响应式 endDate 值,只要 countFrom 发生变化,该值就会发生变化。 让我们从这里开始:

// Outside the Timer function:function addToDate(secs: number) {  const c = Date.now() + secs * 1000;  return new Date(c);}  // ... Inside the Timer function:  const [endDate, setEndDate] = useState(addToDate(countFrom));  useEffect(() => {    setEndDate(addToDate(countFrom));  }, [countFrom]);

现在,我们希望每次 endDate 更改时设置一个新计时器,因此我们添加另一个依赖于 endDate 的 useEffect() 调用:

  const [remaining, setRemaining] = useState(countFrom);  useEffect(() => {    setEndDate(addToDate(countFrom));  }, [countFrom]);  useEffect(() => {    const interval = setInterval(() => {      let r = Math.round((endDate.getTime() - new Date().getTime()) / 1000);      r = Math.max(r, 0);      setRemaining(r);      if (r === 0) {        clearInterval(interval);      }    }, 1000);    return () => clearInterval(interval);  }, [endDate]);

好吧,这个有点复杂。 我们为剩余的几秒创建了另一个状态,这相当于(我希望)上一篇文章中的可读 Svelte 存储。 如果达到零,计时器回调将终止计时器,并且效果的清理函数也会清除计时器。

在这一点上,我的 React 技能似乎还不够好:在我看来,每当 endDate 发生变化时,我应该再调用一次clearInterval()来杀死之前的计时器。 所以各位专家:请纠正我这个问题。

接下来,我们根据剩余的值编写另一个 useEffect() 调用,这将计算我们想要的最终值:

  useEffect(() => {    const hours = Math.floor(remaining / 3600);    const minutes = Math.floor((remaining - hours * 3600) / 60);    const seconds = remaining - hours * 3600 - minutes * 60;    setHh(hours);    setMm(minutes);    setSs(seconds);  }, [remaining]);

请注意此处额外变量的使用:我们不能根据状态变量进行计算,因为它们会在下一个周期中更新,因此在该效果运行时将不同步。

如果我们在这里使用 hh 和 mm,我们会在每分钟转动时在计时器的秒部分看到 -1。

有了这个,我们就可以测试了。 我们看到了什么? 好吧,我们看到计时器工作了,但初始值从未显示。 如果我们将 countFrom 设置为 20 秒,我们会看到第一次更新为 19 秒。 Svelte 计时器可以毫无困难地立即显示初始值。

解决这个问题需要我们在 useEffect() 中计算并设置 endDate 的显示值。 这将覆盖初始值。 计时器将覆盖其余部分。 这是经过修改的整个 Timer.tsx 文件:

import { useEffect, useState } from 'react';export type TimerProps = {  countFrom?: number;};function f(value) {  if (value < 10) {    return `0${value}`;  }  return value.toString();}function addToDate(secs: number) {  const c = Date.now() + secs * 1000;  return new Date(c);}function Timer({ countFrom = 0 }: TimerProps) {  const [hh, setHh] = useState(countFrom);  const [mm, setMm] = useState(countFrom);  const [ss, setSs] = useState(countFrom);  const [endDate, setEndDate] = useState(addToDate(countFrom));  function calculateRemainingTime() {    const r = Math.round((endDate.getTime() - new Date().getTime()) / 1000);    return Math.max(r, 0);  }  useEffect(() => {    setEndDate(addToDate(countFrom));  }, [countFrom]);  useEffect(() => {    updateTimeParts(calculateRemainingTime());    const interval = setInterval(() => {      let r = calculateRemainingTime();      updateTimeParts(r);      if (r === 0) {        clearInterval(interval);      }    }, 1000);    return () => clearInterval(interval);  }, [endDate]);  function updateTimeParts(x: number) {    const hours = Math.floor(x / 3600);    const minutes = Math.floor((x - hours * 3600) / 60);    const seconds = x - hours * 3600 - minutes * 60;    setHh(hours);    setMm(minutes);    setSs(seconds);  }  return (    <span className="timer">      {f(hh)}:{f(mm)}:{f(ss)}    </span>  );}export default Timer;

我们创建了一个新函数calculateRemainingTime()来封装剩余时间计算,因为我们不是复制/粘贴穴居人。 此更改释放了剩余状态。 我们已将其删除。

测试一下。 现在显示的是初始值。 最后一步是timesup 事件。 好吧,这并不是真正的事件,因为 React 中没有事件,而是回调。

我们需要做 3 次更新:

更新道具类型。

将新的 prop 添加到 Timer 函数的参数中。

一旦计时器达到零就调用回调。

再次,这是经过这些修改的 Timer.tsx:

import { useEffect, useState } from 'react';export type TimerProps = {  countFrom?: number;  onTimesup?: () => void};function f(value) {  if (value < 10) {    return `0${value}`;  }  return value.toString();}function addToDate(secs: number) {  const c = Date.now() + secs * 1000;  return new Date(c);}function Timer({ countFrom = 0, onTimesup }: TimerProps) {  const [hh, setHh] = useState(countFrom);  const [mm, setMm] = useState(countFrom);  const [ss, setSs] = useState(countFrom);  const [endDate, setEndDate] = useState(addToDate(countFrom));  function calculateRemainingTime() {    const r = Math.round((endDate.getTime() - new Date().getTime()) / 1000);    return Math.max(r, 0);  }  useEffect(() => {    setEndDate(addToDate(countFrom));  }, [countFrom]);  useEffect(() => {    updateTimeParts(calculateRemainingTime());    const interval = setInterval(() => {      console.log('Tick from interval ID %d !!', interval);      let r = calculateRemainingTime();      updateTimeParts(r);      if (r === 0) {        clearInterval(interval);        (onTimesup ?? (() => {}))();      }    }, 1000);    return () => clearInterval(interval);  }, [endDate]);  function updateTimeParts(x: number) {    const hours = Math.floor(x / 3600);    const minutes = Math.floor((x - hours * 3600) / 60);    const seconds = x - hours * 3600 - minutes * 60;    setHh(hours);    setMm(minutes);    setSs(seconds);  }  return (    <span className="timer">      {f(hh)}:{f(mm)}:{f(ss)}    </span>  );}export default Timer;

好吧,这比 Svelte 版本更难。 好了吗? 不行,我已经测试过了,此时有两个问题:

timesup 回调在我的测试页面加载时触发,然后为 countFrom 设置值。

每当我创建的用于设置 countFrom 值的输入框被清空时,计时器就会显示为 NaN:NaN:NaN。

第一个也揭示了一些事情:即使没有设置计数,计时器也会触发一次。 这两个是通过 IF 语句固定的:

  useEffect(() => {    updateTimeParts(calculateRemainingTime());    let interval = 0;    if (countFrom > 0) {      interval = setInterval(() => {        console.log('Tick from interval ID %d !!', interval);        let r = calculateRemainingTime();        updateTimeParts(r);        if (r === 0) {          clearInterval(interval);          (onTimesup ?? (() => {}))();        }      }, 1000);    }    return () => clearInterval(interval);  }, [endDate]);

要解决#2,我们需要了解问题所在。 为此,我已将 countFrom 的值和类型记录到控制台:每当输入框被清空时,countFrom 仍保持数字类型,但其值为 NaN。

因此,一种可能的解决方法是,在任何计算中只要 countFrom 为 NaN,就将其替换为零。

修复如下所示:

  function safeCountFrom() {    return isNaN(countFrom) ? 0 : countFrom;  }  const [hh, setHh] = useState(safeCountFrom());  const [mm, setMm] = useState(safeCountFrom());  const [ss, setSs] = useState(safeCountFrom());  const [endDate, setEndDate] = useState(addToDate(safeCountFrom()));  // Later in the file:  useEffect(() => {    setEndDate(addToDate(safeCountFrom()));  }, [countFrom]);

我们创建了 safeCountFrom() 函数,用零替换 NaN。 每当我们初始化状态和计算结束日期时,我们都会使用它。

现在我们真正完成了。 我们已经达到了里程碑 1。让我们来数一下行数:总共 72 行 - 12 个空白行 = 60 行代码。

Svelte 与 React 比较

好吧,是时候比较一下了。 让我们来谈谈:

最终结果。

投入的时间。

开发定时器时遇到的问题。

最终结果

我们在 React 中达到了等效计时器的总行数为 60 行。在上一篇文章中,我们使用 Svelte 在 42 行内达到了这一点。 React 需要额外 18 行代码。 听起来不多,对吧? 错误的。 我们谈论的是,当我们使用 Svelte 作为参考时,代码增加了 43%,或者当我们使用 React 时,代码减少了 30%。

现在这只是一个组成部分。 您的项目中有多少个组件? 大多数中型项目大约有 15 到 25 个组件。 你做你的数学。

我们没有冒险进入 React 中的 Milestone 2(动画计时器),但在 Svelte 中,我们通过额外的 11 行实现了这一点,将 Svelte 源代码增加到 53 行。 这仍然低于 React 中的里程碑 1。

投入时间

我当然在 React 版本上投入了更多时间,因为它的语法和机制更加复杂:我们必须在任何地方使用状态,我们必须使用效果钩子来计算派生值,而且我们当然花了时间解决 Svelte 版本中从未出现过的问题。 第一名。

就学习这两项技术投入的时间而言,我可以告诉你,我大约在 3 年半前开始了解 React,大约在 4、5 个月前开始了解 Svelte。 我现在已经尝试掌握 React 3 次了,尝试在 udemy.com 上完成 400 课时的课程。 我从来没有超过第 100 课。另一方面,我一天之内就学会了 100% 的 Svelte。

问题

回顾 Svelte 文章,我记录了开发时的一个问题:我需要根据 countFrom 中的更改重新创建可读存储。

另一方面,本文记录了开发过程中的这些问题:

计时器未显示初始 countFrom 值。

timesup 回调在组件创建时触发。

只要 countFrom 值为 NaN,计时器就会显示 NaN 值。

请注意,即使在获得 Svelte 计时器(它为我们提供了计时器算法)的经验之后也会发生这种情况。 我们不必考虑实施; 我们只需要把它移植到 React 上。 我真的希望能更快地完成它。

结论

Svelte 承诺的代码减少是非常真实的。 从任何标准来看,它都不是无足轻重的,并且在维护大型代码库时应该有很大的帮助。

Svelte 使用反应变量和反应块的更自然的编程方式效果非常好,并且似乎对前沿情况更具弹性。 当绑定的输入框被清空时,Svelte 计时器不会受到影响,并且不会在组件创建时触发 timesup 事件,所有这些都不需要我们真正考虑这一点。

由于 Svelte 不需要钩子或状态,而且它的编译器足够智能,可以使变量具有反应性,因此查看它的代码库比查看 React 代码库要愉快得多。 这应该转化为更容易的维护,并且它应该使经验或专业知识较少的程序员能够比使用 React 更频繁地成功使用它。

标签: #响应头 date