til
  • README
  • Software Development Roles
  • solid
  • README
    • service-worker
  • docker
    • arg
    • 更新docker版本
  • editor
    • vscode
    • Creating a VS Code Theme
  • english
    • words
  • front-end
    • ==
    • ECMAScript
    • IIFE
    • Label
    • basic
    • html.js.css渲染顺序
    • npm-vs-yarn
    • obj-delete-key-value
    • react
    • split-join-and-replace
    • video
    • 前端自检清单
    • 递归及去重
    • css
      • css换肤
      • flex
      • list
      • nth-child和nth-of-type区别
      • padding
      • position
      • 层叠上下文
      • 层叠样式(+)
      • 正方形
      • 语义化标签
    • dom
      • DOCTYPE
      • HEAD
      • 修改document
      • 自定义表单验证
    • electron
      • basic
    • es6
      • basic-type
      • basic
      • prototype-example
      • defineProperty
      • understanding-es6
        • 0.introduction
        • Appendix A: Smaller Changes
        • Appendix B: Understanding ES7
        • Block-Binding
        • Proxies&Reflection
        • class
        • 解构赋值
        • function
        • improved-array
        • iterators&generators
        • modules
        • object
        • promise
        • Map&Set
        • symbol
    • images
      • 前端角度看图片
    • interview_case
      • lexical_scope
      • redux和localstroage存储位置
    • javascript
      • fuck-the-js
      • js-engine-work
      • js原生操作dom
      • what-is-function-program
      • 执行上下文
      • articles
        • JavaScript中使用函数组合
        • JavaScript中的依赖注入
        • JavaScript作用域链中的标识符解析和闭包
        • JavaScript是何如工作的--概述
        • JavaScript深拷贝
        • JavaScript的全局变量是如何工作的
        • js继承常见的误解
        • node12&chrome中7个新的提案功能
        • 你真的懂JavaScript吗
      • date
        • index
      • engines
        • basic
        • JavaScript引擎基础:外形和内联缓存
        • v8中推测性优化的介绍
        • 优化原型
        • 更快的异步功能和promise
      • events
        • baisc
        • 事件冒泡和捕获
        • 定义事件
        • 页面生命周期
      • higher-order-function
        • curry
        • monad
      • module
        • basic
        • main&module
      • objects
        • iterator
        • spread
        • examples
          • iterator
      • performance
        • blocking-css
        • cache
      • prototype
        • Property-Descriptors
        • basic
        • prototype-shadow
      • you-dont-known-js
        • async&performance
          • Chapter 1: Asynchrony: Now & Later
          • Chapter 2: Callbacks
          • Chapter 3: Promises
          • Chapter 4: Generators
        • scope & closures
          • apA
          • apB
          • apC
          • apD
          • chapter1-what-is-scope
          • chapter2-lexical-scope
          • chapter3-function-vs-block-scope
          • chapter4-hoisting
          • chapter5-scope-closure
        • this & object prototypes
          • chapter1-this-or-that
          • chapter2-this-make-sense
          • chapter3-objects
          • chapter4-mixing(up)-class-object
          • chapter5-prototype
          • chapter6-behavior-delegation
        • types&grammer
          • Chapter1-Types
          • Chapter2-Values
          • Chapter3-Natives
          • Chapter4-coercion
          • Chapter5-grammer
        • up & going
          • chapter1-into-programming
          • chapter2-into-javascript
          • chapter3-into-YDKJS
    • mobile
      • iPhone分辨率终极指南
    • npm
      • arguments
      • build
    • react-native
      • prop-methods
    • react
      • PropTypes
      • basic
      • codebase-overview
      • component-element-instance
      • context
      • how-to-known-component-is-func-or-class
      • overview
      • react16.9
      • react18计划
      • react的设计原则
      • reconciliation
      • setState
      • useMemo
      • why-do-we-write-super-props
      • 从头实现一个react
      • concurrent
        • 引入并发模式(仅试验)
      • conf
        • conf-2019
      • events
        • 合成事件概述
      • hooks
        • custom-hook
        • effect-hook
        • hooks-api
        • intro
        • overview
        • rules
        • state-hook
        • hooks-vs-class
          • thinking-in-react-hooks
      • overreact
        • Development模式是如何工作的
        • How-Does-setState-Know-What-to-Do
        • Why-Do-React-Elements-Have-a-$$typeof-Property
        • Why-Do-React-Hooks-Rely-on-Call-Order
        • how-to-known-component-is-func-or-class
        • preparing-tach-talk-motivation
        • react作为ui运行
        • things-i-dont-known-as-2018
        • ui-element-problem-and-build-yourself
        • why-do-we-write-super-props
        • 一份完整的useEffect指南
        • 为什么X不是Hook
        • 函数组件与类有什么不同?
        • 演讲准备2-what-why-how
        • 编写弹性组件
        • 让setInterval在React-Hooks中为声明式
      • practice
        • render
      • react-dom
        • basic
      • react-redux
        • apiv7.1-hooks
        • connect
        • shallow-equal
      • redux
        • applyMiddleware
        • applyMiddleware2-细节
        • example
    • regex
      • index
    • stories
      • 数组下标
      • 阻止事件冒泡
    • svelte
      • compile-svelte-in-your-head-1
      • compiler-overview
      • parser
        • 写一个解析器-JavaScript的JSON解析器
    • turbopack
      • basic
    • typescript
      • interface和type的区别
    • webpack
      • hash
      • webpack4-for-react
      • webpack4
      • webpack4to5
      • babel
        • babel-parser和acorn的区别
        • babel.7.11
        • family
        • react16.14使用new-transform
        • update-to-7
    • pdf
      • deep-js
        • basic
      • react
        • reintroducing
  • git
    • capital
    • emoji
  • http
    • http2.0
    • response
  • rails
    • api
    • flash
    • middleware-vs-metal
    • model
    • performance
    • routes
    • environment
      • error
    • patterns
      • service
    • sidekiq
      • params
    • deploy
      • capistrano
        • ssh
  • ruby
    • self
    • net
      • http请求携带cookie
  • server
    • ss
    • ssh
    • user
    • crawler
      • puppeteer
    • nginx
      • domain-without-80
      • nginx节省带宽
  • sql
    • rails
    • search
Powered by GitBook
On this page
  • 只需要给我展示代码
  • 还等什么?! 🤔
  • 为什么useInterval()是一个更好的API
  • 第一次尝试
  • Second Attempt
  • 阻抗不匹配
  • 用Refs来拯救
  • 提取一个Hook
  • 加分: 暂停 Interval
  • 加分:有趣的Demo
  • Closing Thoughts
  1. front-end
  2. react
  3. overreact

让setInterval在React-Hooks中为声明式

Previous编写弹性组件Nextpractice

Last updated 6 years ago

原文:

如果你玩有一些时间,你可能会遇到一个有趣的问题:使用setInterval并按照你的预期工作。

用Ryan Florence的说:

我听到有很多人指责hooks里的setInterval,就像往React的脸上扔鸡蛋一样啪啪啪打脸。

老实说,我认为这些人都有一个点,首先就是令人困惑。

但我也认为它不是Hooks的缺陷,而是和setInterval之间的不匹配。钩子比类更接近react编程模型,这使得这种不匹配更加突出。

有一种方法可以使它们很好地协同工作,但这有点不直观。

在这篇文章中,我们将看看如何使间隔和Hooks很好地结合在一起,为什么这个解决方案有意义,以及它可以为你提供哪些新功能。

只需要给我展示代码

不用多说,这是一个每秒递增的计数器:

import React, { useState, useEffect, useRef } from 'react';

function Counter() {
  let [count, setCount] = useState(0);

+  useInterval(() => {    
+      // Your custom logic here
+    setCount(count + 1);
+   }, 1000);
  return <h1>{count}</h1>;
}
import React, { useState, useEffect, useRef } from 'react';

function useInterval(callback, delay) {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

我的useInterval Hook设置一个间隔并在卸载后清除它。 它是与组件生命周期相关的setInterval和clearInterval的组合。

你可以随意将其粘贴到项目中或将其放在npm上。

如果你不在乎它是如何工作的,你现在就可以停止阅读了!文章的其余部分是为那些准备深入了解React Hooks的人们准备的。

还等什么?! 🤔

我知道你在想什么:

丹,这段代码没有任何意义。 “Just JavaScript”发生了什么?承认React用hooks跳过了鲨鱼!

我也这么想,但是我改变了主意,我要改变你的想法。 在解释这个代码为什么有意义之前,我想先展示一下它能做什么。

为什么useInterval()是一个更好的API

提醒你,我的useInterval Hook接受一个函数和一个延迟时间:

useInterval(() => {
  // ...
}, 1000);

这看起来很像setInterval:

setInterval(() => {
  // ...
}, 1000);

那么为什么不直接使用setInterval呢?

这可能一开始并不明显,但是你知道的setInterval和我的useInterval Hook之间的区别在于 它的参数是“动态的”。

我将用一个具体的例子说明这一点。

假设我们希望间隔延迟可调:

虽然你不必用 输入 来控制延迟,但动态调整它可能很有用 - 例如,当用户切换到不同的选项卡时,可以减少对某些Ajax更新的轮询。

那么你如何在类中使用setInterval呢?我最终得到了这个:

class Counter extends React.Component {
  state = {
    count: 0,
    delay: 1000,
  };

  componentDidMount() {
    this.interval = setInterval(this.tick, this.state.delay);
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevState.delay !== this.state.delay) {
      clearInterval(this.interval);
      this.interval = setInterval(this.tick, this.state.delay);
    }
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  tick = () => {
    this.setState({
      count: this.state.count + 1
    });
  }

  handleDelayChange = (e) => {
    this.setState({ delay: Number(e.target.value) });
  }

  render() {
    return (
      <>
        <h1>{this.state.count}</h1>
        <input value={this.state.delay} onChange={this.handleDelayChange} />
      </>
    );
  }
}

这不是太糟糕!

Hook版本的看起来会如何?

🥁🥁🥁

function Counter() {
  let [count, setCount] = useState(0);
  let [delay, setDelay] = useState(1000);

  useInterval(() => {
    // Your custom logic here
    setCount(count + 1);
  }, delay);

  function handleDelayChange(e) {
    setDelay(Number(e.target.value));
  }

  return (
    <>
      <h1>{count}</h1>
      <input value={delay} onChange={handleDelayChange} />
    </>
  );
}

是的,这就是全部。

与类版本不同,在“升级”useInterval hook示例时,没有复杂度差距,可以动态调整延迟:

  // Constant delay
  useInterval(() => {
    setCount(count + 1);
  }, 1000);

  // Adjustable delay
  useInterval(() => {
    setCount(count + 1);
  }, delay);

当useInterval Hook看到不同的延迟时间时,它会再次设置间隔。

我可以声明一个具有特定延迟的间隔,而不是编写代码来设置和清除间隔 - 我们的useInterval Hook使它成为可能。

如果我想暂时 暂停 间隔怎么办?我也可以用状态这样做:

const [delay, setDelay] = useState(1000);
  const [isRunning, setIsRunning] = useState(true);

  useInterval(() => {
    setCount(count + 1);
  }, isRunning ? delay : null);

这让我对Hooks和React再次感到兴奋。我们可以包装现有的命令式API并创建能表达我们意图的声明性API。就像渲染一样,我们可以 在所有时间点同时描述该过程 ,而不是小心地发出命令来操纵它。

我希望通过这个你在useInterval()上出售Hook是一个更好的API - 至少当我们从一个组件做它时。

我希望通过这一点,你可以在useInterval() Hooks上,让API有个更好的卖相 ——至少当我们从一个组件进行此操作时。

但是为什么使用setInterval()和clearInterval()会让Hook很讨厌呢? 让我们回到我们的计数器示例并尝试手动实现它。

第一次尝试

我将从一个简单的示例开始,它只是呈现初始状态:

function Counter() {
  const [count, setCount] = useState(0);
  return <h1>{count}</h1>;
}
function Counter() {
  let [count, setCount] = useState(0);

  useEffect(() => {
    let id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  });

  return <h1>{count}</h1>;
}

看起来很简单?这种工作。

但是,这段代码有一种奇怪的行为。

这通常很好,因为许多订阅API可以随时愉快地删除旧的和添加新的监听器。但是,setInterval不是其中之一。当我们运行clearInterval和setInterval时,它们的时序会发生变化。如果我们重新渲染和重新应用效果太频繁,则间隔永远不会有机会发射!

我们可以通过在 较小 的间隔内重新渲染我们的组件来查看错误:

setInterval(() => {
  // Re-renders and re-applies Counter's effects
  // which in turn causes it to clearInterval()
  // and setInterval() before that interval fires.
  ReactDOM.render(<Counter />, rootElement);
}, 100);

Second Attempt

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);

如果我们想只在挂载的时候运行效果,在卸载的时候移除效果,我们可以传递一个空的[]依赖数组。

在第一次尝试中,我们的问题是重新运行效果导致我们的超时被过早清除。我们可以尝试通过永不重新运行来修复它:

function Counter() {
  let [count, setCount] = useState(0);

  useEffect(() => {
    let id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);
  return <h1>{count}</h1>;
}

发生了什么!?

问题是useEffect从第一次渲染中获得count。 它等于0。我们从不重新应用效果,因此setInterval中的闭包始终引用第一个渲染的count,而count + 1始终为1.糟糕!

我能听到你磨牙的声音。Hooks太烦人了吧?

但为什么它变得如此复杂?

阻抗不匹配

有人可能会说数据库来自火星,对象来自金星。数据库不能自然地映射到对象模型。这很像是把两块磁铁的北极推到一起(笔: 互斥)。

我们的“阻抗不匹配”不在数据库和对象之间。它位于React编程模型和命令式setInterval API之间。

一个react组件可能被挂载一段时间并经历许多不同的状态,但是它的渲染结果同时描述了所有这些状态。

// Describes every render
  return <h1>{count}</h1>

Hooks让我们对效果应用相同的声明方法:

// Describes every interval state
  useInterval(() => {
    setCount(count + 1);
  }, isRunning ? delay : null);

我们不设置间隔,但指定是否设置间隔以及延迟时间。我们的Hooks让它发生。以离散术语描述连续过程。

相比之下,setInterval没有及时描述过程 - 一旦设置了间隔,除了清除它之外,你不能改变它的任何内容。

这是React模型和setInterval API之间的不匹配。

props和React组件的状态是可变的。 React将重新渲染它们并“忘记”有关前一个渲染结果的所有内容。它变得无关紧要。

但是setInterval()不会“忘记”。 它将永远引用旧的props和状态直到你更换它 - 如果不重置时间你就做不到。

或者等等,可以吗?

用Refs来拯救

问题归结为:

  • 我们使用第一次渲染的callback1进行setInterval(callback1, delay)。

  • 我们有来自下一个渲染的callback2,它关闭了新的props和状态。

  • 但是我们不能在不重置时间的情况下替换现有的间隔!

那么如果我们根本没有替换间隔,而是引入一个指向 最新 间隔回调的可变savedCallback变量呢?

现在我们可以看到解决方案:

  • 我们 setInterval(fn, delay) , fn调用 savedCallback.

  • 在第一次渲染后将savedCallback设置为callback1。

  • 在下一次渲染后将savedCallback设置为callback2。

  • ???

  • 收益

这个可变的savedCallback需要在重新渲染中“持久”。所以它不能是常规变量。我们想要更像实例字段的东西。

const savedCallback = useRef();
// { current: null }

useRef()返回一个普通对象,该对象具有在渲染之间共享的可变current属性。我们可以将最新的间隔回调保存到它:

function callback() {
    // Can read fresh props, state, etc.
    setCount(count + 1);
  }

  // After every render, save the latest callback into our ref.
  useEffect(() => {
    savedCallback.current = callback;
  });

然后我们可以从我们的间隔内读取并调用它:

useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    let id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, []);

感谢[],我们的效果永远不会重新执行,并且间隔不会被重置。但是,由于savedCallback 这个ref,我们总是可以读取我们在最后一次渲染后设置的回调,并从间隔中调用它。

这是一个完整的可以工作的解决方案:

function Counter() {
  const [count, setCount] = useState(0);
  const savedCallback = useRef();

  function callback() {
    setCount(count + 1);
  }

  useEffect(() => {
    savedCallback.current = callback;
  });

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    let id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, []);

  return <h1>{count}</h1>;
}

提取一个Hook

不可否认,上面的代码可能令人迷惑。混合相反的范例令人费解。还有可能弄乱可变refs。

我认为Hooks提供了比类更低级的原语 - 但它们的美妙之处在于它们使我们能够编写和创建更好的声明性抽象。

理想情况下,我只想写这个:

function Counter() {
  const [count, setCount] = useState(0);

  useInterval(() => {    setCount(count + 1);  }, 1000);
  return <h1>{count}</h1>;
}

我将我的ref机制的主体复制并粘贴到自定义Hook中:

function useInterval(callback) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  });

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    let id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, []);
}

目前,1000延迟是硬编码的。我想把它作为一个参数:

function useInterval(callback, delay) {

我在设置间隔时会使用它:

let id = setInterval(tick, delay);

既然delay可以在渲染之间改变,我需要在我的间隔效果的依赖关系中声明它:

useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    let id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [delay]);

等等,我们不是要避免重置间隔效果,并专门通过[]来避免它吗?不完全是。我们只想避免在 回调 发生变化时重置它。但是当delay改变时,我们想重新启动计时器!

让我们检查一下我们的代码是否有效:

function Counter() {
  const [count, setCount] = useState(0);

  useInterval(() => {
    setCount(count + 1);
  }, 1000);

  return <h1>{count}</h1>;
}

function useInterval(callback, delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  });

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    let id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [delay]);
}

是的!我们现在可以在任何组件中useInterval(),而不必过多地考虑它的实现细节。

加分: 暂停 Interval

假设我们希望能够通过传递null作为delay来暂停我们的间隔:

const [delay, setDelay] = useState(1000);
  const [isRunning, setIsRunning] = useState(true);

  useInterval(() => {
    setCount(count + 1);
  }, isRunning ? delay : null);

我们如何实现这个?答案是:不设置间隔。

useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);

就是这样。此代码处理所有可能的转换:更改延迟、暂停或恢复间隔。useEffect() API要求我们花费更多的前期工作来描述设置和清理 - 但添加新案例很容易。

加分:有趣的Demo

这个useInterval() Hook非常有趣。当副作用是声明性的时,将复杂的行为编排在一起要容易得多。

例如,我们可以delay一个间隔由另一个间隔控制:

function Counter() {
  const [delay, setDelay] = useState(1000);
  const [count, setCount] = useState(0);

  // Increment the counter.
  useInterval(() => {
    setCount(count + 1);
  }, delay);

  // Make it faster every second!
  useInterval(() => {
    if (delay > 10) {
      setDelay(delay / 2);
    }
  }, 1000);

  function handleReset() {
    setDelay(1000);
  }

  return (
    <>
      <h1>Counter: {count}</h1>
      <h4>Delay: {delay}</h4>
      <button onClick={handleReset}>
        Reset delay
      </button>
    </>
  );
}

Closing Thoughts

对于Hooks来说,这是一个早期的阶段,而且我们还需要确定并比较一些模式。如果你习惯于遵循众所周知的“最佳实践”,不要急于采用Hooks。还有很多值得尝试和发现的东西。

我希望这篇文章可以帮助你理解与使用带有Hooks的setInterval()等API相关的常见陷阱,可以帮助你克服它们的模式,以及在它们之上创建更具表现力的声明性API的甜蜜成果。

(这里是一个)

这个useInterval没有内置在React的Hook里;这是我写的一个:

(这里有一个)

(这里是一个)

(这里是)

(在这里)

现在我想要一个每秒递增一次的间隔。这是一个,所以我将使用useEffect()并返回一个清理函数:

(查看)

默认情况下,React会在每次渲染后重新应用效果。这是有意的,有助于避免React类组件中存在的。

(看看关于这个bug得)

你可能知道useEffect()让我们可以 重新应用效果。你可以将依赖关系数组指定为第二个参数,如果该数组中的某些内容发生更改,React将重新运行该效果:

但是,如果你不太熟悉JavaScript闭包,这是常见的错误源。我们现在要犯这个错误! (我们也正在建立一个来尽早展示这些漏洞,但还没有准备好。)

但是,现在我们的计数器更新为1并保持在那里。 (参见。)

的方法就是使用。这种方法给你更多的灵活性。在reducer内部,你可以访问当前状态和新的props。dispatch函数本身永远不会改变,所以你可以从任何闭包中向它注入数据。useReducer()的一个限制是你不能在其中发出副作用。 (但是,你可以返回新状态 - 触发一些效果。)

这个术语有时被滥用,解释如下:

useEffect() Hook“忘记”前一个渲染。它清除最后一个效果并设置下一个效果。下一个效果将关闭新的props和状态。这就是为什么我们的适用于简单的案例。

,useRef()给出了我们的确切结果:

(你可能熟悉React中的。Hooks使用 相同的概念 来保存任何可变值。ref 像一个“盒子”,你可以放 任何东西 。 )

(查看)

(在中尝试)

(看)

(例子请看 )

Hooks需要适应,尤其是在命令和声明性代码的边界。你可以像一样创建强大的声明性抽象,但它们肯定会让你紧张。

Making setInterval Declarative with React Hooks
React Hooks
不会
话
React编程模型
CodeSandbox Demo
自定义的Hook
CodeSandbox的demo
CodeSandbox demo
CodeSandbox Demo
demo
需要清理的副作用
CodeSandbox Demo
整个类存在的错误
demo
选择退出
lint规则
action中的错误
另一个修复
useReducer
Phil Haack
第一次尝试
正如我们可以从Hooks FAQ中学到的那样
DOM refs
CodeSandbox Demo
CodeSandbox
CodeSandbox Demo
CodeSandbox demo
React Spring