代码之家  ›  专栏  ›  技术社区  ›  AlwaysNeedingHelp

如何在多个react redux组件之间实现带有requestAnimationFrame的gameloop?

  •  1
  • AlwaysNeedingHelp  · 技术社区  · 7 年前

    努力想一想最好的方法。我可以使用递归调用 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)
        }
    
    }
    

    但是如果我想在每一帧上:

    • 组件1到do x
    • 要执行的组件2
    • 组件3到do z

    我可以在每个组件中有另一个循环,但是我的理解是多个循环是不好的实践 请求动画框架 循环进行,这是一个重大的性能冲击。

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

    3 回复  |  直到 7 年前
        1
  •  2
  •   Danziger    7 年前

    您需要一个调用 requestAnimationFrame 并迭代一个数组 refs 在每个循环中需要更新的子组件,调用其 update (或您想如何调用它)方法:

    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 ,react可能不是正确的工具。

    也, setState is asynchronous 因此,当调用它时,您只是将更新推送到某个响应的队列中,该队列将在某个时间点进行处理,这可能在下一帧或更高的时间发生。

    我分析了这个简单的例子,看看是不是真的,事实上不是。更新被添加到队列中( enqueueSetState ,但这项工作是直接完成的:

    Example app profiling result

    但是,我怀疑,在React需要处理更多更新的实际应用程序中,或者在React的未来版本中,使用诸如时间切片、具有优先级的异步更新等功能…渲染实际上可能发生在不同的帧中。

        2
  •  0
  •   Frank    7 年前

    您应该创建一个运行循环的父组件,然后将其传递给其他组件,它应该如下所示:

    <Loop>
        <ComponentX loop={loop} />
        <ComponentY loop={loop} />
        <ComponentZ loop={loop} />
    </Loop>
    
        3
  •  0
  •   soulofmischief    7 年前

    一种解决方案是定义一个数组,例如 callbacks 作为你所在州的一部分。在每个组件的生命周期的开始,向这个数组添加一个函数,它执行每个循环所需的操作。然后像这样调用raf循环中的每个函数:

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

    通过一些工作,您可以调整这个简单的示例,以提供从中删除函数的方法。 回调 允许从游戏循环中按名称或签名动态添加/减去例程。

    您还可以传递一个包装函数的对象,该函数还包含一个整数,您可以使用该整数按优先级对回调进行排序。

    推荐文章