为什么在函数组件中状态不更新?

huangapple go评论80阅读模式
英文:

Why doesn't state update in functional component

问题

我创建了一个简单的组件,并且在状态更新时遇到了问题。我知道正确的解决方案,但我的问题是:为什么这段代码不起作用?

import * as React from "react";
import { useEffect, useState } from "react";

type CountdownProps = {
    start: number;
}

export const Countdown: React.FunctionComponent<CountdownProps> = ({ start }: CountdownProps) => {
    const [seconds, setSeconds] = useState<number>(start);

    useEffect(() => {
        setInterval(() => setSeconds(seconds - 1), 1000);
    }, []);

    return <>{seconds}</>;
};
英文:

I created a simple component and i have problem with state update. I know correct solution, but my question is: Why this code doesn't work?

<!-- begin snippet: js hide: false console: true babel: true -->

<!-- language: lang-js -->

import * as React from &quot;react&quot;;
import {useEffect, useState} from &quot;react&quot;;

type CountdownProps = {
    start: number;
}

export const Countdown: React.FunctionComponent&lt;CountdownProps&gt; = ({start}: CountdownProps) =&gt; {
    const [seconds, setSeconds] = useState&lt;number&gt;(start);

    useEffect(() =&gt; {
        setInterval(() =&gt; setSeconds(seconds - 1), 1000);
    }, []);

    return (&lt;&gt;{seconds}&lt;/&gt;);
};

<!-- end snippet -->

答案1

得分: 4

这是React钩子的一个已知问题,特别是涉及到Dan在他的博客文章中提到的useInterval时。

问题在于useEffect捕获了从第一次渲染传递给CountDown的initValue的初始值。我们从未重新应用该效果,因此setInterval中的闭包始终引用第一次渲染中的initValue,而initValue - 1始终是initValue

有趣的是
你实际上可以通过向语句添加console.log来检查间隔是否每秒都更新,但它始终获取initValue。 在此处尝试一下:https://repl.it/repls/MedicalMealyPentagon

要获取解决方案,请阅读上面的文章。

英文:

This is a known issue with React hooks when it comes to useInterval that Dan talked about in one of his blog posts in Overreacted

> The problem is that useEffect captures the the initial value passed to CountDown initValue from the first render. We never re-apply the effect so the closure in setInterval always references the initValue from the first render, and initValue - 1 is always initValue.

Fun fact
> You can actually check that the interval is actually updating every second by adding a console.log to the statement but it's always getting initValue.
Try it out here https://repl.it/repls/MedicalMealyPentagon

For a solution, please read the article above.

答案2

得分: 2

在setTimeout或setInterval中,您无法访问全局上下文,您必须调用秒数回调函数,该函数将提供给您最新的值,并且您需要返回新的秒数值如下所示:

import * as React from "react";
import { useEffect, useState } from "react";

type CountdownProps = {
    start: number;
}

export const Countdown: React.FunctionComponent<CountdownProps> = ({ start }) => {
    const [seconds, setSeconds] = useState<number>(start);

    useEffect(() => {
        setInterval(() => setSeconds(preSeconds => preSeconds - 1), 1000);
    }, []);

    return <>{seconds}</>;
};

请注意,这是一段React组件的代码,用于创建一个倒计时功能。

英文:

Into the setTimeout or setInterval, you have not accessed to the global context and you have to call the seconds callback function which will give you the latest value and you need to return new seconds value as below:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

import * as React from &quot;react&quot;;
import {useEffect, useState} from &quot;react&quot;;

type CountdownProps = {
    start: number;
}

export const Countdown: React.FunctionComponent&lt;CountdownProps&gt; = ({start}) =&gt; {
    const [seconds, setSeconds] = useState&lt;number&gt;(start);

    useEffect(() =&gt; {
        setInterval(() =&gt; setSeconds(preSeconds =&gt; preSeconds - 1), 1000);
    }, []);

    return (&lt;&gt;{seconds}&lt;/&gt;);
};

<!-- end snippet -->

答案3

得分: 0

问题在于在你提供给setInterval的函数中,seconds的值从未被更新。虽然你的函数每1000毫秒运行一次,但seconds的值没有变化,因为提供的函数中从未更新seconds

你可以使用setTimeout,并让useEffect 依赖于 seconds,如下所示:

import React, { useEffect, useState } from "react";

export default function App() {
  const [seconds, setSeconds] = useState(100);

  useEffect(() => {
    setTimeout(() => setSeconds(seconds - 1), 1000);
  }, [seconds]);

  return <>{seconds}</>;
}
英文:

The problem is that the value of seconds is never updated in the fonction you provide to setInterval. Your function is run every 1000ms but the value does not change because seconds in the provided function is never updated.

Instead of setInterval you could use setTimeout and have useEffect depend on seconds like this:

import React, { useEffect, useState } from &quot;react&quot;;

export default function App() {
  const [seconds, setSeconds] = useState(100);

  useEffect(() =&gt; {
    setTimeout(() =&gt; setSeconds(seconds - 1), 1000);
  }, [seconds]);

  return &lt;&gt;{seconds}&lt;/&gt;;
}

答案4

得分: -2

需要将构造函数值添加到useState(我添加了0)
另外,useEffect的第二个参数应该是seconds

还将React.FunctionComponent更改为React.FC

关于第二个参数的更多信息:

> React的useEffect钩子的第二个参数是一个依赖数组,用于您的useEffect回调函数。当该数组中的任何值更改时,效果回调将重新运行。

import * as React from "react";
import { useEffect, useState } from "react";

type CountdownProps = {
  start: number;
};

const Countdown: React.FC<CountdownProps> = ({ start }: CountdownProps) => {
  start = 0;
  const [seconds, setSeconds] = useState<number>(start);

  useEffect(() => {
    const id = setInterval(() => {
      setSeconds(seconds - 1);
    }, 1000);
    return () => clearInterval(id);
  }, [seconds]);

  return <>{seconds}</>;
};

export default Countdown;
英文:

You need to add constructor value to usestate(I Added 0)
Also the second argument of useEffect should be seconds

Also i change React.FunctionComponent to React.FC

The more information about second argument:

> The second argument to React's useEffect hook is an array of
> dependencies for your useEffect callback. When any value in that array
> changes, the effect callback is re-run.

import * as React from &quot;react&quot;;
import {useEffect, useState} from &quot;react&quot;;

type CountdownProps = {
    start: number;
}

const Countdown: React.FC&lt;CountdownProps&gt; = ({start}: CountdownProps) =&gt; {
    
    start = 0;
    const [seconds, setSeconds] = useState&lt;number&gt;(start);

    useEffect(() =&gt; { const id = setInterval(() =&gt; { setSeconds(seconds - 1) }, 1000); return () =&gt; clearInterval(id); }, [seconds])

    return (&lt;&gt;{seconds}&lt;/&gt;);
};

export default Countdown;

huangapple
  • 本文由 发表于 2020年1月6日 22:13:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/59613617.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定