Реагировать: Если вы установите состояние в кадре анимации запроса будет реагировать расписание рендеринга на кадре анимации? - PullRequest
0 голосов
/ 30 декабря 2018

Я пытаюсь понять нюансы использования чего-то вроде игрового цикла внутри React (версия 16+).Меня смущает, как стратегия рендеринга React конфликтует (или не конфликтует) с другим планировщиком рендеринга - в данном случае: запрос кадра анимации.

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

class Loop extends Component {
  constructor(props) {
    super(props);
    this.state = { x: 0 };
  }

  componentDidMount() {
    let then = performance.now();
    const loop = now => {
      if (this.state.x < 400)
        window.requestAnimationFrame(loop);
      const dt = now - then;
      then = now;
      this.setState(prevState => ({ x: prevState.x + (dt * 0.1) }));
    };
    loop(then);
  }

  render() {
    const { x } = this.state;
    return <div style={{
      backgroundColor: "green",
      height: "50px",
      width: `${x}px`
    }}></div>;
  }
}

Будет ли это работать так же, как если бы вы манипулировали DOM напрямую?Или, будет ли реагировать на рендеринг, чтобы сделать что-то наподобие обновления состояния пакета, отказавшись от цели использования кадра анимации запроса?

1 Ответ

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

Вот что я подумал, отвечая на этот вопрос: Как реализовать игровой цикл с requestAnimationFrame для нескольких компонентов React Redux?

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

Я профилировал очень простое приложение, используя requestAnimationFrame, чтобы увидеть, так ли это на самом деле, а на самом деле это не так:

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>

Здесь вы можете увидеть, как обновления добавляются в очередь (enqueueSetState), но работа выполняется сразу и все вызовы* update, setState, render, commitWork ... происходят в одном кадре:

Example app profiling result

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

...