长话短说:当
React Strict Mode
运行两次useEffect
时,我的组件就搞砸了,并且讨厌陷入不良实践,所以只是不想禁用它,但不知道如何克服它。我知道这是预期的行为,所以我正在寻找解决方案或替代方案。
长话短说:
我正在尝试改编我在 codepen 中找到的普通 JS 代码,以在
p
标签上产生打字效果,这非常简单,但后来注意到我的代码由于 React Strict Mode
而存在错误,因为 的预期行为useEffect
因为它在部署时效果非常好。
我知道有像
TypeIt
这样的库已经解决了这个问题,即使是在开发模式下,但我真的适应了这个,现在我遇到了困难。
既然其他人已经在开发模式下解决了这个问题,那么一定有办法做到这一点,但我找不到它,可能我需要以某种方式重写我的函数。
这是代码(这个组件非常简单,所以你只需将它导入到任何地方即可):
import React, { useEffect, useState } from 'react'
function TestComponent(
{
words,
typeDelay,
eraseDelay,
nextWordDelay,
}
:
{
words: (string)[],
typeDelay: number,
eraseDelay: number,
nextWordDelay: number,
}
)
{
const [text, setText] = useState("");
let wordIndex = 0;
let letterIndex = 0;
const sleep = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay));
const type = async () =>
{
console.log(letterIndex, wordIndex);
if(letterIndex < words[wordIndex].length)
{
const newLetter = words[wordIndex].charAt(letterIndex);
setText(prev => prev + newLetter);
letterIndex++;
await sleep(typeDelay);
type();
return;
}
await sleep(nextWordDelay);
erase();
}
const erase = async () =>
{
if(letterIndex > 0)
{
const newText = words[wordIndex].substring(0, letterIndex - 1);
setText(prev => newText);
letterIndex--;
await sleep(eraseDelay);
erase();
return;
}
wordIndex++;
if(wordIndex >= words.length) wordIndex = 0;
await sleep(nextWordDelay);
type();
}
useEffect(() =>
{
type();
}, [])
return (
<p>
<span className='typed-text'>
{text}
</span>
</p>
)
}
export default TestComponent
我也知道有上千篇关于
useEffect
的帖子,但没有发现有人询问如何在开发中解决这个问题或实现这一目标的替代方案。
解决这个问题最简单的方法就是支持你使用Effect后自行清理。 现在,当函数调用它们自己时,我将很难清理它(即阻止它们互相调用)。
另一种方法是使用 setInterval。因为使用 setInterval 可以很容易地中止它们。 这是一个例子。我删除了多个单词以简化示例,但您应该能够轻松地将其添加回来。
import { useCallback, useEffect, useState } from 'react'
type TypingProps = {
word: string,
typeDelay: number,
eraseDelay: number,
nextWordDelay: number,
}
function TestComponent(props: TypingProps)
{
const { word, typeDelay, eraseDelay } = props;
const [textIndex, setTextIndex] = useState(0);
const [isErasing, setIsErasing] = useState(false);
const text = word.slice(0, textIndex);
const type = useCallback(async () =>
{
if(isErasing) { return }
if(textIndex < word.length)
{
setTextIndex(prev => prev + 1)
return;
}
setIsErasing(true)
}, [textIndex, word, isErasing])
const erase = useCallback(async () =>
{
if(!isErasing) { return }
if(textIndex > 0)
{
setTextIndex(prev => prev - 1)
return;
}
setIsErasing(false)
}, [textIndex, isErasing])
useEffect(() =>
{
const typeInterval = setInterval(type, typeDelay);
const eraseInterval = setInterval(erase, eraseDelay);
return () => {
clearInterval(typeInterval);
clearInterval(eraseInterval);
}
}, [eraseDelay, typeDelay, type, erase])
return (
<p>
<span className='typed-text'>
{text}
</span>
</p>
)
}
export default TestComponent