使用 useEffect 和 setInterval 一个接一个地打印一个字母
Posted
技术标签:
【中文标题】使用 useEffect 和 setInterval 一个接一个地打印一个字母【英文标题】:print one letter after the other with useEffect and setInterval 【发布时间】:2021-08-12 05:18:49 【问题描述】:我试图了解使用useEffect
和setInterval
创建一个每隔一段时间(如打字机)一个接一个地打印一个字母的组件的最佳方法是什么
function Printer( str )
const [val, setVal] = useState(0);
useEffect(() =>
const interval = setInterval(() =>
setVal((preVal) => preVal + 1);
, 200);
return () =>
clearInterval(interval);
;
, [str]);
return <div>str.slice(0, val)</div>;
虽然这个人会抱怨 val
是 useEffect 依赖数组的缺失依赖:
function Printer( str )
const [val, setVal] = useState(0);
useEffect(() =>
const interval = setInterval(() =>
if (val < str.length)
setVal((preVal) => preVal + 1);
else
clearInterval(interval);
, 200);
return () =>
clearInterval(interval);
;
, [str]);
return <div>str.slice(0, val)</div>;
如果将 val
添加到依赖项数组中 - 我们将设置多个冗余间隔(并删除它们,因此感觉有点糟糕):
function Printer( str )
const [val, setVal] = useState(0);
useEffect(() =>
const interval = setInterval(() =>
if (val < str.length)
setVal((preVal) => preVal + 1);
else
clearInterval(interval);
, 200);
return () =>
clearInterval(interval);
;
, [str, val]);
return <div>str.slice(0, val)</div>;
不确定我是否遗漏了有关 useEffect、依赖数组或这只是 linter 拖钓我的东西。
【问题讨论】:
你的最后一种方法看起来不错,只是尝试不依赖于你的 useEffect,所以它只被调用一次。 【参考方案1】:你可以使用useRef()
来存储结束条件(stop
):
const useState, useRef, useEffect = React;
function Printer( str )
const [val, setVal] = useState(0);
const stop = useRef();
useEffect(() =>
stop.current = val === str.length;
);
useEffect(() =>
const interval = setInterval(() =>
if(stop.current) clearInterval(interval);
else setVal((preVal) => preVal + 1);
, 200);
return () =>
clearInterval(interval);
;
, []);
return <div>str.slice(0, val)</div>;
ReactDOM.render(
<Printer str="cats" />,
root
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>
【讨论】:
【参考方案2】:将字符串 prop 和状态索引都放入依赖数组中,为了使事情更容易,使用超时而不是间隔。还添加一个在字符串 prop 更改时运行的回调,以便您可以将状态索引重置为 0。
const Printer = ( str ) =>
const [index, setIndex] = React.useState(0);
React.useEffect(() =>
if (index < str.length)
const timeoutId = setTimeout(() =>
setIndex(index + 1);
return () => clearTimeout(timeoutId);
, 200);
return () => clearTimeout(timeoutId);
, [index, str]);
React.useEffect(() =>
setIndex(0);
, [str]);
return <div>str.slice(0, index)</div>;
const App = () =>
const [str, setStr] = React.useState('');
return (
<div>
<input value=str onChange=(e) => setStr(e.currentTarget.value) />
<Printer str=str />
</div>
);
;
ReactDOM.render(<App/>, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>
【讨论】:
是的,我想这不是必需的。我正在考虑在道具更改时立即重置索引,而不是在很短的延迟之后。 我可以看到使用setTimeout
而不是setInterval
背后的原因,但没有使用setState
(在我们的例子中为setIndex
)并包括状态(在我们的例子中为@987654327 @) 在依赖数组中是一种不好的做法(通常)?
@mikey.nagler 一般来说,它绝对没有错。由于您希望在先前的状态更改之后发生后续的状态更改,因此将 setter 放入其中非常有意义 - 这是您正在寻找的逻辑,并且没有关于它的代码气味。当然,如果你没有if (index < str.length)
,它会永远持续下去——可能需要,也可能不想要。
@CertainPerformance 所以这没问题,只要我们受到代码本身(或它背后的逻辑)的保护?
(1) 效果要再次运行时调用(这里不重要),(2) 组件卸载时调用(这个很重要,以避免无操作、React 警告和潜在的内存泄漏)以上是关于使用 useEffect 和 setInterval 一个接一个地打印一个字母的主要内容,如果未能解决你的问题,请参考以下文章
使用 useEffect 和 jest.useFakeTimers() 进行测试