我正在我的 React 应用程序中实现打字机效果,我从 URL 获取字符串并一次显示一个字符。我希望效果从第一个字符(索引 0)开始,并按顺序处理所有字符。但是,第二个字符(索引 1)会被跳过,其余字符会正确显示。
附加信息: 获取数据后文本状态会正确更新。 我在每个字符的显示之间添加了 500 毫秒的延迟。
任何有关改进逻辑或调试此问题的建议将不胜感激!
import "./styles.css";
import React, { useEffect, useState } from "react";
export default function App() {
const [text, setText] = useState(""); // To hold the fetched text
const [displayText, setDisplayText] = useState(""); // For the typewriter effect
const [loading, setLoading] = useState(true); // Loading state
// Fetching text from the URL
useEffect(() => {
const fetchText = async () => {
try {
const response = await fetch("https://example-url.com/text"); // Fetch from URL
const data = await response.text();
setText(data); // Save the text in the state
setLoading(false); // Loading done
} catch (error) {
console.error("Error fetching the text:", error);
setLoading(false);
}
};
fetchText();
}, []);
// Typewriter effect logic
useEffect(() => {
if (!loading && text) {
let index = 0;
const interval = setInterval(() => {
setDisplayText((prevText) => prevText + text[index]);
index++;
if (index === text.length) {
clearInterval(interval); // Stop when all characters are shown
}
}, 500); // 500ms delay between characters
return () => clearInterval(interval); // Cleanup
}
}, [text, loading]);
// Rendering the text with a typewriter effect
return (
<div className="App">
{loading ? (
<p>Loading...</p> // Loading text
) : (
<ul>
{displayText.split("").map((char, index) => (
<li key={index}>{char}</li> // Render each character in a list
))}
</ul>
)}
</div>
);
}
我在
setInterval
中使用 useEffect
从获取的字符串中一一追加字符。我预计打字机效果从第一个字符(索引 0)开始,但它跳过第二个字符并正确显示其余字符。
这里的问题是效果/间隔回调在处理状态更新之前正在改变索引。 React 状态更新被排队,然后在函数作用域和调用堆栈完成时进行处理。
let index = 0;
const interval = setInterval(() => {
setDisplayText((prevText) => prevText + text[index]); // <--called later
index++; // <-- mutated now!
if (index === text.length) {
clearInterval(interval);
}
}, 500);
在处理第一个状态更新以使用 index
值之前,
text[0]
突变为 1。
我建议将索引存储在状态中并递增该索引,而不是将部分
text
状态复制到 displayText
状态。您可以从 text
和 index
状态计算派生的“显示文本”值。
实施示例:
export default function App() {
const [text, setText] = useState("");
const [index, setIndex] = useState(0);
const [loading, setLoading] = useState(true);
const timerRef = useRef();
// Fetching text from the URL
useEffect(() => {
const fetchText = async () => {
try {
const response = await fetch("https://example-url.com/text"); // Fetch from URL
const data = await response.text();
const data = "uncloak";
setText(data);
} catch (error) {
console.error("Error fetching the text:", error);
} finally {
setLoading(false);
}
};
fetchText();
}, []);
// Typewriter effect logic
useEffect(() => {
if (!timerRef.current && text) {
timerRef.current = setInterval(() => {
setIndex((i) => i + 1);
}, 500); // 500ms delay between characters
// Cleanup
return () => {
clearInterval(timerRef.current);
timerRef.current = null;
};
}
}, [text]);
useEffect(() => {
if (timerRef.current && index === text.length) {
clearInterval(timerRef.current);
timerRef.current = null;
}
}, [index, text]);
// Rendering the text with a typewriter effect
return (
<div className="App">
{loading ? (
<p>Loading...</p> // Loading text
) : (
<ul>
{text
.split("")
.slice(0, index)
.map((char, index) => (
<li key={index}>{char}</li> // Render each character in a list
))}
</ul>
)}
</div>
);
}
演示:
function App() {
const [text, setText] = React.useState(""); // To hold the fetched text
const [index, setIndex] = React.useState(0);
const [loading, setLoading] = React.useState(true); // Loading state
const timerRef = React.useRef();
// Fetching text from the URL
React.useEffect(() => {
const fetchText = async () => {
try {
await new Promise((resolve) => {
setTimeout(resolve, 4000);
});
const data = "uncloak";
setText(data); // Save the text in the state
} catch (error) {
console.error("Error fetching the text:", error);
} finally {
setLoading(false); // Loading done
}
};
fetchText();
}, []);
// Typewriter effect logic
React.useEffect(() => {
if (!timerRef.current && text) {
timerRef.current = setInterval(() => {
setIndex((i) => i + 1);
}, 500); // 500ms delay between characters
// Cleanup
return () => {
clearInterval(timerRef.current);
timerRef.current = null;
};
}
}, [text]);
React.useEffect(() => {
if (timerRef.current && index === text.length) {
clearInterval(timerRef.current);
timerRef.current = null;
}
}, [index, text]);
// Rendering the text with a typewriter effect
return (
<div className="App">
{loading ? (
<p>Loading...</p> // Loading text
) : (
<ul>
{text
.split("")
.slice(0, index)
.map((char, index) => (
<li key={index}>{char}</li> // Render each character in a list
))}
</ul>
)}
</div>
);
}
const rootElement = document.getElementById("root");
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<div id="root" />
我修改了您的代码以使用超时而不是间隔,并使用 ref 跟踪索引。 我修改了您的代码,以便我们可以通过手动设置要显示的字符串来进行测试
import React, { useEffect, useState, useRef } from 'react';
export function App() {
const [text, setText] = useState(''); // To hold the fetched text
const [displayText, setDisplayText] = useState([]); // For the typewriter effect
const inputRef = useRef();
const indexRef = useRef(0);
// Fetching text from the URL
// Typewriter effect logic
useEffect(() => {
if (text.length === 0 || displayText.length === text.length) return;
const timeout = setTimeout(() => {
setDisplayText(p => [...p, text[indexRef.current]]);
}, 500);
return () => {
clearTimeout(timeout);
indexRef.current += 1;
};
}, [text, displayText]);
function set() {
console.dir(inputRef.current.value);
indexRef.current = 0;
setText(inputRef.current.value);
setDisplayText([])
}
// Rendering the text with a typewriter effect
return (
<div className='App'>
<input ref={inputRef} />
<button onClick={set}>set</button>
<ul>
{displayText.map((char, index) => (
<li key={index}>{char}</li> // Render each character in a list
))}
</ul>
</div>
);
}
我不能 100% 确定您的问题是什么。