Как реализовать gameloop с requestAnimationFrame для нескольких компонентов React Redux? - PullRequest
0 голосов
/ 07 января 2019

Изо всех сил пытаюсь придумать лучший способ сделать это. Я могу использовать рекурсивный вызов с 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, чтобы сделать X
  • Компонент2, чтобы сделать Y
  • Компонент3, чтобы сделать Z

Я мог бы просто иметь еще один цикл в каждом компоненте, однако, насколько я понимаю, плохая практика - иметь несколько циклов requestAnimationFrame, и это значительный удар по производительности.

Так что я в растерянности здесь. Как я могу иметь другой компонент использовать тот же цикл? (Если это даже лучший способ сделать это!)

Ответы [ 3 ]

0 голосов
/ 10 января 2019

Одним из решений будет определение массива, такого как callbacks, как части вашего состояния. В начале жизненного цикла каждого компонента добавьте в этот массив функцию, которая делает то, что вы хотите, для каждого цикла. Затем вызовите каждую функцию в цикле rAF следующим образом:

update( performance.now())

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

    requestAnimationFrame( update )
  }

Проделав небольшую работу, вы можете настроить этот простой пример, чтобы обеспечить способ удаления функций из callbacks, чтобы разрешить динамическое добавление / вычитание подпрограмм из игрового цикла по имени или подписи.

Вы также можете передать объект, обертывающий функцию, который также содержит целое число, которое можно использовать для сортировки обратных вызовов по приоритету.

0 голосов
/ 11 января 2019

Вам нужен родительский компонент, который вызывает 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 является асинхронным , поэтому, вызывая его, вы просто отправляете обновление в очередь, которую React будет обрабатывать в определенный момент, что может фактически произойти в следующем кадре или позже.

Я описал этот простой пример, чтобы увидеть, так ли это на самом деле. Обновления добавляются в очередь (enqueueSetState), но работа выполняется сразу:

Example app profiling result

Однако я подозреваю, что в реальном приложении, в котором React есть больше обновлений для обработки, или в будущих версиях React с такими функциями, как срезы времени, асинхронные обновления с приоритетами ... рендеринг может фактически происходить в другом кадре.

0 голосов
/ 10 января 2019

Вы должны создать родительский компонент, который выполняет цикл, а затем передает его другим компонентам, он должен выглядеть следующим образом:

<Loop>
    <ComponentX loop={loop} />
    <ComponentY loop={loop} />
    <ComponentZ loop={loop} />
</Loop>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...