具有间隔的类组件与函数组件

时间:2021-08-01 21:58:36

标签: node.js reactjs react-hooks

如果这是一个多余的问题,请原谅我,但我在任何地方都找不到适合我的用例的确切答案。

我正在 React 中制作一个打字机效果组件,以纯粹在 React 中进行一些文本冒险,只是为了好玩。我已经设法让它以我喜欢的方式与类组件一起工作,但我更喜欢将它调整为函数组件,以此作为更好地熟悉函数组件的机会,但它似乎以奇怪的方式运行一个区间。我以前以多种不同的方式使用过函数组件,但出于某种原因,这个方法让我很难受。

这是一个jsfiddle with the class component.

class Typewriter extends React.Component {

    state = {
        showCursor: false,
        displayMsgs: []
    }

    constructor(props) {
        super(props)
        this.typeSpeed = this.props.typeSpeed ? this.props.typeSpeed : 250
        this.curChar = 0
        this.msgLength = this.props.msg.length
        this.chunks = [{msg: '', flags: {color: "black"}}]
        this.curChunk = 0
        this.inChunk = false

        for(let i = 0; i < this.msgLength; i++) {
            
            if(this.props.msg.charAt(i) === '\\') {
                this.curChunk += 1
                this.inChunk = !this.inChunk
                let flags = { color: "black" }

                if(this.inChunk) {
                    switch(this.props.msg.charAt(i + 1)) {
                        case "R":
                            flags = { color: "red" }
                        break
                        default:
                        break
                    }
                }

                this.chunks.push({msg: '', flags: flags})
                i += 2
            }
            
            this.chunks[this.curChunk].msg += this.props.msg.charAt(i)
        }
        
        this.curChunk = 0
        console.log(this.chunks)
    }

    componentDidMount() {

        const displayMsgs = (new Array(this.chunks.length)).fill("")
        this.setState({ displayMsgs: displayMsgs })

        const cursorInterval = setInterval(() => {
            this.setState({showCursor: !this.state.showCursor})
        }, 250)

        const interval = setInterval(() => {

            const newDisplay = this.state.displayMsgs.map((el, i) => (
                i === this.curChunk ? this.state.displayMsgs[this.curChunk] + this.chunks[this.curChunk].msg.charAt(this.curChar)
                               : el
            ))

            this.setState({displayMsgs: newDisplay})
            this.curChar += 1

            if(this.curChar >= this.chunks[this.curChunk].msg.length) {
                this.curChar = 0
                this.curChunk += 1
                console.log(this.curChunk)
            }

            if(this.curChunk >= this.chunks.length)
                end()

        }, this.typeSpeed)

        const skipTyping = () => {
            clearInterval(interval)
            let newDisplay = []
            for(let i = 0; i < this.state.displayMsgs.length; i++){
                newDisplay.push(this.chunks[i].msg)
            }
            this.setState({ displayMsgs: newDisplay })
            
            end()
        }
        

        const end = () => {
            clearInterval(interval)
            clearInterval(cursorInterval)
            document.removeEventListener("keypress", skipTyping)
            document.removeEventListener("click", skipTyping)
            this.setState({showCursor: false})

            if(this.props.callback)
                this.props.callback()
        }

        document.addEventListener("keypress", skipTyping)
        document.addEventListener("click", skipTyping)
        
    }

    render() {
        return (
            <div>
                { this.state.displayMsgs.map((msg, i) => 
                    (
                        <span key={i} style={{ color: this.chunks[i].flags.color }}>{msg}</span>
                    )
                )}
                { this.state.showCursor && <span>|</span>}
            </div>
        )
    }
}


ReactDOM.render(<Typewriter msg="Test message. As you can see it \Rworks\R smoothly." typeSpeed={50} />, document.querySelector("#app"))

它运行顺利,正如我所期望的那样,我可以轻松地在 componentDidMount 函数。此组件也适用于传递的回调,允许我在更大的应用中链接多个 Typewriter 对象。

相反,我似乎无法让钩子在这种情况下工作。我使用了 Dan Abramov 的自定义钩子 useInterval,但它的行为不稳定,而且似乎比类组件更复杂。这里我们有一个 jsfiddle with the functional component. 我只是让它以一种丑陋的方式工作,没有按键跳过输入等。

如您所见,功能组件在更新时非常不稳定;完成输入后,光标以预期的间隔/平滑度闪烁。出于这个原因,我怀疑渲染中发生了一些有趣的事情,这导致了我无法弄清楚的重大性能损失。非常感谢任何见解。

1 个答案:

答案 0 :(得分:1)

问题

看起来您的问题是间隔回调中的状态突变。您保存对 displayMsgs 状态的引用,对其进行变异,然后将其保存回状态。 React 没有看到新的引用,因此会在中间重新渲染上放弃,结果是“断断续续的动画”。

useInterval(() => {
  if (displayMsgs && curChunk.current < chunks.current.length) {
    let newDisplay = displayMsgs;  // <-- reference to current state

    newDisplay[curChunk.current] += chunks.current[
      curChunk.current
    ].msg.charAt(curChar.current); // <-- Mutation!!!

    console.log('newDisplay', newDisplay);
    setDisplayMsgs(newDisplay);    // <-- save reference back into state
    curChar.current += 1;

    if (curChar.current >= chunks.current[curChunk.current].msg.length) {
      curChar.current = 0;
      curChunk.current += 1;
      console.log(curChunk.current);
    }
  }
}, typeSpeed);

解决方案

先对状态进行浅拷贝,然后再更新。

useInterval(() => {
  if (displayMsgs && curChunk.current < chunks.current.length) {
    let newDisplay = displayMsgs.slice(); // <-- shallow copy array

    newDisplay[curChunk.current] += chunks.current[
      curChunk.current
    ].msg.charAt(curChar.current);

    console.log('newDisplay', newDisplay);
    setDisplayMsgs(newDisplay);
    curChar.current += 1;

    if (curChar.current >= chunks.current[curChunk.current].msg.length) {
      curChar.current = 0;
      curChunk.current += 1;
      console.log(curChunk.current);
    }
  }
}, typeSpeed);

Edit class-component-vs-function-component-with-intervals