【问题标题】:Class component vs. function component with intervals类组件与带间隔的函数组件
【发布时间】:2021-08-01 21:58:36
【问题描述】:

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

我正在 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.,我只是让它以一种丑陋的方式工作,没有按键跳过输入等。

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

【问题讨论】:

    标签: node.js reactjs react-hooks


    【解决方案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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-01-10
      • 2021-04-15
      • 2020-09-03
      • 2018-11-16
      • 2019-12-05
      • 1970-01-01
      • 2019-03-08
      • 1970-01-01
      相关资源
      最近更新 更多