ค้นหาเว็บไซต์

javascript - วิธีตั้งค่าผลลัพธ์การพิมพ์หลายบรรทัดใน react พร้อมดีเลย์ระหว่างบรรทัด


ฉันได้สร้างคอมโพเนนต์แบบคลาสใน React ที่เริ่มเอฟเฟกต์การพิมพ์หลายบรรทัดเมื่อโหลด

มันใช้งานได้ดี แต่ปัญหาคือสายทั้งสองเริ่มพิมพ์พร้อมกัน ซึ่งสมเหตุสมผลเมื่อพิจารณาจากลักษณะการทำงานของ setTimeout

ดังนั้นฉันจึงกำลังมองหาไอเดียหรือคำแนะนำใด ๆ เกี่ยวกับวิธีทำให้มันรอจนกว่าบรรทัดแรกจะพิมพ์เสร็จก่อนที่จะไปบรรทัดถัดไป

import React from 'react'
import TerminalCursor from 'icons/TerminalCursor'
import TerminalPrompt from 'icons/TerminalPrompt'
import { v4 as uuidv4 } from 'uuid'

type MyProps = {}
type MyState = {
  introArray: string[]
}
class Terminal extends React.Component<MyProps, MyState> {
  state: MyState = {
    introArray: [],
  }

  componentDidMount() {
    // Data to import from sanity
    const starterArray = [
      'The very first line that needs to finish typing before going to the next line',
      'The second line that needs to start being typed after the first line',
    ]

    const createTypingEffect = (text: string, index: number) => {
      for (let i = 0; i < text.length; i++) {
        setTimeout(() => {
          let arrayCopy = this.state.introArray.slice()
          this.setState((state) => ({
            introArray: [...state.introArray.slice(0, index), arrayCopy[index] + text[i], ...state.introArray.slice(index + 1)],
          }))
        }, 100 * i)
      }
    }
    starterArray.forEach((starterText, starterIndex) => {
      // Setting empty string for each line in starterArray so we dont get undefined as first character
      this.setState((state) => ({
        introArray: [...state.introArray, ''],
      }))

      // Need to wait for first line to finish typing before starting the second line
      createTypingEffect(starterText, starterIndex)
    })
  }
  render() {
    return (
      <div className="w-1/2 h-1/2 p-5 flex items-start justify-start bg-clip-padding bg-slate-900 backdrop-filter backdrop-blur-xl bg-opacity-60 border border-gray-900 rounded">
        <div className="flex flex-col">
          {this.state.introArray.map((introLine) => (
            <div className="flex items-center" key={uuidv4()}>
              <TerminalPrompt />
              <p className="text-white">{introLine}</p>
            </div>
          ))}
          {/* Actual prompt starts here */}
          <div className="flex">
            <TerminalPrompt />
            <TerminalCursor />
          </div>
        </div>
      </div>
    )
  }
}

export default Terminal

นี่คือการสาธิต sandbox (ไม่แน่ใจว่าทำไมตัวอักษรบางตัวถึงถูกทำซ้ำใน sandbox แต่ไม่มีปัญหานี้ในเครื่องของตัวเอง...)

https://codesandbox.io/s/recursing-star-iohjw9?file=/src/Terminal.tsx

คำตอบ #1

คุณจำเป็นต้องใช้แนวทางแบบอะซิงโครนัสสำหรับงานประเภทนี้ (ฉันไม่ได้ตรวจสอบตรรกะเอฟเฟกต์การพิมพ์ของคุณ แค่ทำให้กระบวนการทั้งหมดเป็นแบบอะซิงโครนัส)

import React from "react";

type MyProps = {};
type MyState = {
  introArray: string[];
};
class Terminal extends React.Component<MyProps, MyState> {
  state: MyState = {
    introArray: []
  };

  componentDidMount() {
    // Data to import from sanity
    const starterArray = [
      "The very first line that needs to finish typing before going to the next line",
      "The second line that needs to start being typed after the first line"
    ];

    const createTypingEffect = async (text: string, index: number) => {
      return Promise.all(
        text.split("").map(
          (c, i) =>
            new Promise((res) => {
              setTimeout(() => {
                let arrayCopy = this.state.introArray.slice();
                this.setState((state) => ({
                  introArray: [
                    ...state.introArray.slice(0, index),
                    arrayCopy[index] + c,
                    ...state.introArray.slice(index + 1)
                  ]
                }));
                res(null);
              }, 100 * i);
            })
        )
      );
    };

    const cycle = async () => {
      let i = 0;
      for (const starterText of starterArray) {
        // Setting empty string for each line in starterArray so we dont get undefined as first character
        this.setState((state) => ({
          introArray: [...state.introArray, ""]
        }));

        await createTypingEffect(starterText, i);
        i++;
      }
    };

    cycle();
  }
  render() {
    console.log(this.state.introArray);
    return (
      <div className="w-1/2 h-1/2 p-5 flex items-start justify-start bg-clip-padding bg-slate-900 backdrop-filter backdrop-blur-xl bg-opacity-60 border border-gray-900 rounded">
        <div className="flex flex-col">
          {this.state.introArray.map((introLine, index) => (
            <div className="flex items-center" key={index}>
              <p className="text-white">{introLine}</p>
            </div>
          ))}
        </div>
      </div>
    );
  }
}

export default Terminal;

คุณสามารถใช้หลายวิธีเพื่อจัดการกับตรรกะแบบอะซิงค์ได้ ผมใช้วิธี Promise.all ที่นี่ โดยให้เวลาเพิ่มขึ้นกับ timeout เพียงเพื่อทำตามสิ่งที่คุณกำลังทำ วิธีแก้ปัญหาอีกวิธีหนึ่งคือการใช้ลูป for...of แบบง่าย ๆ และรอ promisified timeout ภายใน แบบนั้นคุณไม่จำเป็นต้องให้เวลาเพิ่มขึ้นกับ timeout เพราะพวกมันจะถูกดำเนินการ ค่อนข้างพร้อมกัน

ตัวอย่างการทำงานอยู่ที่นี่ (ไม่สามารถเพิ่มโค้ดตัวอย่างที่ใช้งานได้ที่นี่ เนื่องจาก SO ไม่มีการรองรับ TS)