如何在多个React Redux组件之间使用requestAnimationFrame实现gameloop?

时间:2019-01-06 23:20:30

标签: javascript reactjs typescript redux react-redux

努力思考最好的解决方法。我可以对requestAnimationFrame使用递归调用来进行游戏循环:

export interface Props {
    name: string;
    points: number;
    onIncrement?: () => void;
    onDecrement?: () => void;
}

class Hello extends React.Component<Props, object> {

    constructor(props: Props) {
        super(props);
    }

    render() {
        const { name, points, onIncrement, onDecrement } = this.props;

        return (
            <div className="hello">
                <div className="greeting">
                    Hello {name + points}
                </div>
                <button onClick={onDecrement}>-</button>
                <button onClick={onIncrement}>+</button>
            </div>
        );
    }

    componentDidMount() {
        this.tick();
    }

    tick = () => {
        this.props.onIncrement();
        requestAnimationFrame(this.tick)
    }

}

但是如果我想在每帧上怎么办:

  • Component1做X
  • Component2做Y
  • Component3做Z

每个组件中只能有另一个循环,但是我的理解是,要进行多个requestAnimationFrame循环是不好的做法,并且对性能的影响很大。

所以我在这里迷路了。如何让另一个组件使用相同的循环? (如果那是最好的解决方法!)

3 个答案:

答案 0 :(得分:2)

您需要一个父组件,该父组件调用requestAnimationFrame并遍历每个周期需要更新的update子组件的refs数组,并调用其class ProgressBar extends React.Component { constructor(props) { super(props); this.state = { progress: 0, }; } update() { this.setState((state) => ({ progress: (state.progress + 0.5) % 100, })); } render() { const { color } = this.props; const { progress } = this.state; const style = { background: color, width: `${ progress }%`, }; return( <div className="progressBarWrapper"> <div className="progressBarProgress" style={ style }></div> </div> ); } } class Main extends React.Component { constructor(props) { super(props); const progress1 = this.progress1 = React.createRef(); const progress2 = this.progress2 = React.createRef(); const progress3 = this.progress3 = React.createRef(); this.componentsToUpdate = [progress1, progress2, progress3]; this.animationID = null; } componentDidMount() { this.animationID = window.requestAnimationFrame(() => this.update()); } componentWillUnmount() { window.cancelAnimationFrame(this.animationID); } update() { this.componentsToUpdate.map(component => component.current.update()); this.animationID = window.requestAnimationFrame(() => this.update()); } render() { return( <div> <ProgressBar ref={ this.progress1 } color="magenta" /> <ProgressBar ref={ this.progress2 } color="blue" /> <ProgressBar ref={ this.progress3 } color="yellow" /> </div> ); } } ReactDOM.render(<Main />, document.getElementById('app'));(或者您可以要调用它)方法:

body {
  margin: 0;
  padding: 16px;
}

.progressBarWrapper {
  position: relative;
  width: 100%;
  border: 3px solid black;
  height: 32px;
  box-sizing: border-box;
  margin-bottom: 16px;
}

.progressBarProgress {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="app"></div>
60 fps

但是请记住,如果您尝试做太复杂的事情并想打enqueueSetState,React可能不是使用的正确工具。

此外,setState is asynchronous,所以在调用它时,您只是将更新推送到React会在某个时候处理的队列,这实际上可能发生在下一帧或更高版本中。

我分析了这个简单的示例,以查看是否确实如此,实际上并非如此。更新已添加到队列(regexp = re.compile('MACHINE:\s={0,1}\s{0,1}((\S+=\^M\s\S+|\S+))') value = regexp.search(data)[1] value.replace('=^M\n', '')) )中,但工作立即完成:

Example app profiling result

但是,我怀疑在一个实际的应用程序中,React可以处理更多更新,或者在将来的React版本中具有时间分片,具有优先级的异步更新...渲染实际上可能发生在不同的框架中。

>

答案 1 :(得分:0)

您应该创建一个运行循环的父组件,然后将其传递给看起来像这样的其他组件:

<Loop>
    <ComponentX loop={loop} />
    <ComponentY loop={loop} />
    <ComponentZ loop={loop} />
</Loop>

答案 2 :(得分:0)

One solution would be to define an array such as callbacks as part of your state. At the beginning of each component's lifecycle, add a function to this array which does what you want each loop. Then call each function in your rAF loop like this:

update( performance.now())

// Update loop
function update( timestamp ) {
    // Execute callback
    state.callbacks.forEach( cb => cb( ...args )) // Pass frame delta, etc.

    requestAnimationFrame( update )
  }

With some work you can adjust this simple example to provide a way to remove functions from callbacks to allow dynamic adding/subtracting routines from the game loop by name or signature.

You could also pass an object wrapping the function which also contains an integer that you could use to sort the callbacks by priority.