为什么我的打字机效果中的 setInterval 会跳过第二个字符(索引 1)并正确显示剩余字符?

问题描述 投票:0回答:2

我正在我的 React 应用程序中实现打字机效果,我从 URL 获取字符串并一次显示一个字符。我希望效果从第一个字符(索引 0)开始,并按顺序处理所有字符。但是,第二个字符(索引 1)会被跳过,其余字符会正确显示。

当前行为: enter image description here

预期行为:enter image description here

附加信息: 获取数据后文本状态会正确更新。 我在每个字符的显示之间添加了 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)开始,但它跳过第二个字符并正确显示其余字符。

javascript reactjs react-hooks setinterval
2个回答
2
投票

问题

这里的问题是效果/间隔回调在处理状态更新之前正在改变索引。 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" />


0
投票

我修改了您的代码以使用超时而不是间隔,并使用 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% 确定您的问题是什么。

© www.soinside.com 2019 - 2024. All rights reserved.