前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Hooks】:setInterval 与 React Hooks

【Hooks】:setInterval 与 React Hooks

作者头像
WEBJ2EE
发布2021-02-26 16:07:21
1K0
发布2021-02-26 16:07:21
举报
文章被收录于专栏:WebJ2EEWebJ2EE
代码语言:javascript
复制
目录
1. setInterval 失效了?
2. 正确姿势?
3. 为什么?
  3.1. The Impedance Mismatch
  3.2. 问题根源
  3.3. Refs to the Rescue!
4. 总结

1. setInterval 失效了?

Talk is cheap. Show me the code.

代码语言:javascript
复制
import React, { useState, useEffect } from 'react';

export default function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timerId = setInterval(() => {
      console.log(new Date().toLocaleTimeString(), "=>", count);
      setCount(count + 1)
    }, 1000);

    return () => clearInterval(timerId);
  }, []);

  return (
    <h2>Count: {count}</h2>
  );
}

运行结果:

迷之结果?

  • 定时器运行正常,咋结果一直是 0 啊?
    • setCount 怎么不更新数据了?

2. 正确姿势?

Talk is cheap. Show me the code.

代码语言:javascript
复制
import React, { useState, useEffect, useRef } from 'react';

function useInterval(callback: () => void, delay: number | null) {
  const savedCallback = useRef<() => void>(callback);

  // 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]);
}

export default function App() {
  const [count, setCount] = useState(0);

  useInterval(() => {
    console.log(new Date().toLocaleTimeString(), "=>", count);
    setCount(count + 1)
  }, 1000);

  return (
    <h2>Count: {count}</h2>
  );
}

3. 为什么?

3.1. The Impedance Mismatch

Our “impedance mismatch” is between the React programming model and the imperative setInterval API.

A React component may be mounted for a while and go through many different states, but its render result describes all of them at once.

代码语言:javascript
复制
// Describes every render
return <h1>{count}</h1>

Hooks let us apply the same declarative approach to effects:

代码语言:javascript
复制
// Describes every interval state
useInterval(() => {
  setCount(count + 1);
}, isRunning ? delay : null);

We don’t set the interval, but specify whether it is set and with what delay. Our Hook makes it happen. A continuous process is described in discrete terms.

By contrast, setInterval does not describe a process in time — once you set the interval, you can’t change anything about it except clearing it.

That’s the mismatch between the React model and the setInterval API.

3.2. 问题根源

Props and state of React components can change. React will re-render them and “forget” everything about the previous render result. It becomes irrelevant.

The useEffect() Hook “forgets” the previous render too. It cleans up the last effect and sets up the next effect. The next effect closes over fresh props and state. This is why our first attempt worked for simple cases.

But setInterval() does not “forget”. It will forever reference the old props and state until you replace it — which you can’t do without resetting the time.

3.3. Refs to the Rescue!

The problem boils down to this:

  • We do setInterval(callback1, delay) with callback1 from first render.
  • We have callback2 from next render that closes over fresh props and state.
  • But we can’t replace an already existing interval without resetting the time!

So what if we didn’t replace the interval at all, and instead introduced a mutable savedCallback variable pointing to the latest interval callback?

Now we can see the solution:

  • We setInterval(fn, delay) where fn calls savedCallback.
  • Set savedCallback to callback1 after the first render.
  • Set savedCallback to callback2 after the next render.
  • ???
  • PROFIT

This mutable savedCallback needs to “persist” across the re-renders. So it can’t be a regular variable. We want something more like an instance field.

useRef() gives us exactly that:

代码语言:javascript
复制
const savedCallback = useRef();
  // { current: null }

useRef() returns a plain object with a mutable current property that’s shared between renders. We can save the latest interval callback into it:

代码语言:javascript
复制
  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;
  });

4. 总结

Hooks take some getting used to — and especially at the boundary of imperative and declarative code. You can create powerful declarative abstractions with them like React Spring but they can definitely get on your nerves sometimes.

参考:

Making setInterval Declarative with React Hooks: https://overreacted.io/making-setinterval-declarative-with-react-hooks/ react-use: https://github.com/streamich/react-use

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档