javascript - React 中的 setTimeout 隐式地​​将数字输出到 DOM

标签 javascript reactjs rendering settimeout

我正在尝试在 React 中实现延迟打字动画,该动画启动时会删除占位符文本。我的尝试是在超时后设置状态,然后渲染动画并在状态为 true 时删除占位符。

但是,使用 setTimeout 在其容器中输出一些“随机”数字,我一直无法弄清楚为什么 - 我假设呈现的数字是超时时间(以毫秒为单位),它们之前只改变了几次停止。

输出可以在这里看到:

enter image description here

整个组件的示例可以在这里看到:

enter image description here

本质上,我正在尝试为聊天通信设置动画,并且需要渲染一个看起来像输入字段的 div。 div 有一个默认的占位符文本,需要在 xxxx 毫秒后删除,之后渲染打字员文本以显示打字动画。

下面描述的聊天组件使用号码状态以及增加号码的函数。数字状态用于识别哪些聊天气泡已被渲染,因为气泡有一个动画回调,这是状态被更改的地方 - 以确保下一个聊天气泡不会开始动画,直到前一个聊天气泡完全完成完毕。

问题是,在渲染“输入字段”时,我需要超时,因为用户必须在触发打字员的打字动画之前看到占位符几秒钟。

Chat.jsx

import React, { useEffect, useRef, useState } from 'react';
import ChatBubble from './ChatBubble/ChatBubble';
import classes from './Chat.module.css';
import ScrollAnimation from 'react-animate-on-scroll';
import Typist from 'react-typist';

const Chat = () => {
  const [state, setState] = useState(0);

  const [showInputText, setShowInputText] = useState(false);

  const choices = [{ text: 'Under 2 år siden' }, { text: 'Over 2 år siden' }];

  const choices2 = [{ text: 'Ja' }, { text: 'Nej' }];

  const typistCursor = {
    hideWhenDone: true,
    hideWhenDoneDelay: 200
  };

  let inputText = <Typist cursor={typistCursor}>test@mail.com</Typist>;
  if(state >= 6) {
    setTimeout(() => {
      inputText = <div className={classes.InputText}>Indtast din email her...</div>
    }, 1000)
  }

  const inputText = <Typist cursor={typistCursor}>test@mail.com</Typist>;

  const renderNextBubble = () => {
    const newState = state + 1;
    setState(newState);
    console.log('test state', state);
  };

  return (
    <div className={classes.chatWrapper}>

      <ChatBubble
        isReply={false}
        animationDelay={0}
        animationCallback={renderNextBubble}
        chatChoices={choices}
      >
        <p>Hvornår købte du din vare?</p>
      </ChatBubble>

      {state >= 1 ? (
        <ChatBubble
          isReply={true}
          animationDelay={0}
          animationCallback={renderNextBubble}
        >
          Under 2 år siden
        </ChatBubble>
      ) : null}

      {state >= 2 ? (
        <ChatBubble
          isReply={false}
          animationDelay={0}
          animationCallback={renderNextBubble}
          chatChoices={choices2}
        >
          <p>Er det under 6 måneder siden at du bestilte/modtog dit køb?</p>
        </ChatBubble>
      ) : null}

      {state >= 3 ? (
        <ScrollAnimation
          animateIn="fadeIn"
          duration={0.5}
          delay={-0.25}
          animateOnce={true}
          afterAnimatedIn={renderNextBubble}
        >
          <div className={classes.DotContainer}>
            <div className={classes.Dot}></div>
          </div>
        </ScrollAnimation>
      ) : null}
      {state >= 4 ? (
        <ScrollAnimation
          animateIn="fadeIn"
          duration={0.5}
          delay={-0.25}
          animateOnce={true}
          afterAnimatedIn={renderNextBubble}
        >
          <div className={classes.DotContainer}>
            <div className={classes.Dot}></div>
          </div>
        </ScrollAnimation>
      ) : null}
      {state >= 5 ? (
        <ScrollAnimation
          animateIn="fadeIn"
          duration={0.5}
          delay={-0.25}
          animateOnce={true}
          afterAnimatedIn={renderNextBubble}
        >
          <div className={classes.DotContainer}>
            <div className={classes.Dot}></div>
          </div>
        </ScrollAnimation>
      ) : null}

      {state >= 6 ? (
        <>
          <ChatBubble
            isReply={false}
            animationDelay={0}
            animationCallback={renderNextBubble}
          >
            <p style={{ fontWeight: 'bold' }}>Du er næsten færdig</p>
            <p>
              Skriv din email nedenunder, så har vi en mulighed for at sende
              klagen til dig
            </p>
            <p style={{ fontWeight: 'bold' }}>
              Dobbelttjek at du har skrevet den rigtige mail!
            </p>
          </ChatBubble>
          <div className={classes.EmailInput}>
            {setTimeout(() => {
              console.log('executing timeout');
              setShowInputText(true);
            }, 1000)}
            {showInputText ? (
              inputText
            ) : (
              <div className={classes.InputText}>Indtast din email her...</div>
            )}
          </div>
        </>
      ) : null}
    </div>
  );
};

export default Chat;

ChatBubble.jsx

import React from 'react';
import classes from './ChatBubble.module.css';
import Typist from 'react-typist';
import ChatChoices from '../ChatChoices/ChatChoices';
import ScrollAnimation from 'react-animate-on-scroll';

const chatBubble = (props) => {
  const { isReply, animationDelay, animationCallback, chatChoices } = props;
  let text = props.children;

  const typistCursor = {
    hideWhenDone: true,
    hideWhenDoneDelay: 200
  };

  if (props.typist) {
    text = (
      <Typist cursor={typistCursor}>
        <Typist.Delay ms={600} />
        {props.children}
      </Typist>
    );
  }

  return (
    <ScrollAnimation
      animateIn="fadeIn"
      duration={1}
      delay={animationDelay}
      animateOnce={true}
      afterAnimatedIn={animationCallback}
    >
      <div
        className={`${classes.chatLine} ${
          isReply ? classes.chatLineWhite : classes.chatLineBlue
        }`}
      >
        <div
          className={`${
            isReply ? classes.chatBubbleBlue : classes.chatBubbleWhite
          } ${classes.chatBubble}`}
        >
          <div>{text}</div>
        </div>
      </div>
      {chatChoices ? <ChatChoices choices={chatChoices} /> : null}
    </ScrollAnimation>
  );
};

export default chatBubble;

ChatChoices.jsx

import React from 'react';
import classes from './ChatChoices.module.css';

const chatChoices = ({ choices }) => {
  return (
    <div className={classes.chatLine}>
      <div className={classes.wrapper}>
        <p>VÆLG EN MULIGHED</p>
        <div className={classes.choicesWrapper}>
          {choices
            ? choices.map((choice) => (
                <div key={choice.text} className={classes.choice}>
                  {choice.text}
                </div>
              ))
            : null}
        </div>
      </div>
    </div>
  );
};

export default chatChoices;

最佳答案

在 JSX 中,{...}输出其中表达式的结果。 (您在其他地方依赖于此,例如 className={classes.InputText} 。)您正在评估 setTimeout 。在{} ,它返回一个计时器句柄,它是一个数字。

您不应该使用setTimeout完全在你的 JSX 中。相反,只需在组件主体中运行它,如果您确实希望它在每次渲染组件时运行:

const Chat = () => {

  const [showInputText, setShowInputText] = useState(false)

  const typistCursor = {
    hideWhenDone: true,
    hideWhenDoneDelay: 200,
  }

  const inputText = (<Typist cursor={typistCursor}>test@mail.com</Typist>)

  // *** Moved
  setTimeout(() => {
    console.log('executing timeout');
    setShowInputText(true);
  }, 1000)
  // ***

  return (
    <div className={classes.EmailInput}>
      {showInputText ? (inputText) : (<div className={classes.InputText}>Indtast din email her...</div>)}
    </div>
  )
}

实例:

const { useState } = React;

const classes = {
    InputText: {
        color: "green"
    }
};

const Chat = () => {

  const [showInputText, setShowInputText] = useState(false)

  const typistCursor = {
    hideWhenDone: true,
    hideWhenDoneDelay: 200,
  }

  // *** Replaced Typist here just for demo purposes
  const inputText = (<div>test@mail.com</div>)

  // *** Moved
  setTimeout(() => {
    console.log('executing timeout');
    setShowInputText(true);
  }, 1000)
  // ***

  return (
    <div className={classes.EmailInput}>
      {showInputText ? (inputText) : (<div className={classes.InputText}>Indtast din email her...</div>)}
    </div>
  )
}

ReactDOM.render(<Chat />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>

但是,请注意,通过创建 setTimeout无条件地,即使 showInputText,你也会一次又一次地这样做。已经是true 。如果您只想在 false 时执行此操作,添加一个分支:

const Chat = () => {

  const [showInputText, setShowInputText] = useState(false)

  const typistCursor = {
    hideWhenDone: true,
    hideWhenDoneDelay: 200,
  }

  const inputText = (<Typist cursor={typistCursor}>test@mail.com</Typist>)

  // *** Added `if`
  if (!showInputText) {
    // *** Moved
    setTimeout(() => {
      console.log('executing timeout');
      setShowInputText(true);
    }, 1000)
    // ***
  }

  return (
    <div className={classes.EmailInput}>
      {showInputText ? (inputText) : (<div className={classes.InputText}>Indtast din email her...</div>)}
    </div>
  )
}

实例:

const { useState } = React;

const classes = {
    InputText: {
        color: "green"
    }
};

const Chat = () => {

  const [showInputText, setShowInputText] = useState(false)

  const typistCursor = {
    hideWhenDone: true,
    hideWhenDoneDelay: 200,
  }

  // *** Replaced Typist here just for demo purposes
  const inputText = (<div>test@mail.com</div>)

  // *** Added `if`
  if (!showInputText) {
    // *** Moved
    setTimeout(() => {
      console.log('executing timeout');
      setShowInputText(true);
    }, 1000)
    // ***
  }
  
  return (
    <div className={classes.EmailInput}>
      {showInputText ? (inputText) : (<div className={classes.InputText}>Indtast din email her...</div>)}
    </div>
  )
}

ReactDOM.render(<Chat />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>

<小时/>

在评论中,您说过您担心在显示组件之前开始超时,并且超时应该仅在 state >= 6 时开始。 。为此,请使用 useEffect 回调 state (和 showInputText )作为依赖项,并设置计时器 if !showInputText && state >= 6 :

// *** `useEffect` depending on `state` and `showInputText`
useEffect(() => {
  // You'll see this console log every time the component is rendered
  // with an updated `showInputText` or `state`
  console.log("useEffect callback called");
  // *** Added `if`
  if (!showInputText && state >= 6) {
    console.log("Setting timer");
    // *** Moved
    setTimeout(() => {
      // You'll only see this one when `showInputText` was falsy when
      // the `useEffect` callback was called just after rendering
      console.log('executing timeout');
      setShowInputText(true);
    }, 1000)
    // ***
  }
}, [showInputText, state]);

实例:

const { useState, useEffect } = React;

const classes = {
    InputText: {
        color: "green"
    }
};

const Chat = () => {

  const [state, setState] = useState(0);
  const [showInputText, setShowInputText] = useState(false)

  const typistCursor = {
    hideWhenDone: true,
    hideWhenDoneDelay: 200,
  }

  // *** Replaced Typist here just for demo purposes
  const inputText = (<div>test@mail.com</div>)

  // *** `useEffect` depending on `state` and `showInputText`
  useEffect(() => {
    // You'll see this console log every time the component is rendered
    // with an updated `showInputText` or `state`
    console.log("useEffect callback called");
    // *** Added `if`
    if (!showInputText && state >= 6) {
      console.log("Setting timer");
      // *** Moved
      setTimeout(() => {
        // You'll only see this one when `showInputText` was falsy when
        // the `useEffect` callback was called just after rendering
        console.log('executing timeout');
        setShowInputText(true);
      }, 1000)
      // ***
    }
  }, [showInputText, state]);
  
  return (
    <div className={classes.EmailInput}>
      {showInputText ? (inputText) : (<div className={classes.InputText}>Indtast din email her...</div>)}
      <input type="button" onClick={
          /* Just a really quick and dirty button to let us increment `state` */
          () => setState(s => s + 1)
          } value={`State: ${state} - Increment`} />
    </div>
  )
}

ReactDOM.render(<Chat />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>

最后,如果您的组件可能由于除 setShowInputText(true) 之外的某些其他原因而被重新渲染,调用上面的方法,您可能想通过 useEffect 中的清理函数取消计时器以避免过时的调用。钩子(Hook):

// *** `useEffect` depending on `state` and `showInputText`
useEffect(() => {
  // You'll see this console log every time the component is rendered
  // with an updated `showInputText` or `state`
  console.log("useEffect callback called");
  // *** Added `if`
  if (!showInputText && state >= 6) {
    console.log("Setting timer");
    // *** Moved
    const timer = setTimeout(() => {
      // You'll only see this one when `showInputText` was falsy when
      // the `useEffect` callback was called just after rendering
      console.log('executing timeout');
      setShowInputText(true);
    }, 1000)
    // ***
    // *** This is the cleanup function. It's a no-op if the timer has
    // already fired; if the timer hasn't fired, it prevents it firing
    // twice.
    return () => clearTimeout(timer);
  }
}, [showInputText, state]);

实例:

const { useState, useEffect } = React;

const classes = {
    InputText: {
        color: "green"
    }
};

const Chat = () => {

  const [state, setState] = useState(0);
  const [showInputText, setShowInputText] = useState(false)

  const typistCursor = {
    hideWhenDone: true,
    hideWhenDoneDelay: 200,
  }

  // *** Replaced Typist here just for demo purposes
  const inputText = (<div>test@mail.com</div>)

  // *** `useEffect` depending on `state` and `showInputText`
  useEffect(() => {
    // You'll see this console log every time the component is rendered
    // with an updated `showInputText` or `state`
    console.log("useEffect callback called");
    // *** Added `if`
    if (!showInputText && state >= 6) {
      // *** Moved
      console.log("Setting timer");
      const timer = setTimeout(() => {
        // You'll only see this one when `showInputText` was falsy when
        // the `useEffect` callback was called just after rendering
        console.log('executing timeout');
        setShowInputText(true);
      }, 1000)
      // ***
      // *** This is the cleanup function. It's a no-op if the timer has
      // already fired; if the timer hasn't fired, it prevents it firing
      // twice.
      return () => {
        console.log("Clearing timer");
        clearTimeout(timer);
      };
    }
  }, [showInputText, state]);
  
  return (
    <div className={classes.EmailInput}>
      {showInputText ? (inputText) : (<div className={classes.InputText}>Indtast din email her...</div>)}
      <input type="button" onClick={
          /* Just a really quick and dirty button to let us increment `state` */
          () => setState(s => s + 1)
          } value={`State: ${state} - Increment`} />
    </div>
  )
}

ReactDOM.render(<Chat />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>

关于javascript - React 中的 setTimeout 隐式地​​将数字输出到 DOM,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60398224/

相关文章:

javascript - XMLHttpRequest 同源策略

reactjs - 如何将 gltf 编码/压缩为 draco

node.js - 多部分表单组件 "cannot POST/divespotform"无法正常工作 parseInt 问题?

3d - 在工程应用程序中进行实时 3D 渲染的最佳方法是什么?

Java 应用程序 - AWT 渲染导致抗锯齿打开时文本损坏

javascript - 我如何减少图表 js 圆环图的边框宽度

javascript - 有人可以解释这个电子邮件正则表达式的含义吗

html - 通过 'View Source' 显示的 HTML 是否与(Firebug)开发人员工具中显示的 HTML 不同?

javascript - app.get 不适用于基本路线

reactjs - react-i18next 并用组件替换占位符键