J'essaie de comprendre les nuances de l'utilisation de quelque chose comme une boucle de jeu à l'intérieur de React (version 16+). Je suis confus quant à la façon dont la stratégie de rendu de React entre en conflit (ou n'entre pas en conflit) ) avec un autre ordonnanceur de rendu - dans ce cas: demander un cadre d'animation.
Reportez-vous à l'exemple suivant où une boucle de jeu est utilisée pour définir l'état:
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>;
}
}
Est-ce que cela fonctionnera de la même manière que si l'on avait manipulé directement le DOM? Ou, réagira-t-il pour effectuer un rendu comme les mises à jour de l'état des lots, ce qui ira à l'encontre de l'utilisation d'un cadre d'animation de demande?
C'est ce que j'ai pensé en répondant à cette question: Comment implémenter un gameloop avec requestAnimationFrame sur plusieurs React composants Redux?
Comme setState
est asynchrone , il n'y a en fait aucune garantie qu'en l'appelant React va effectuer la mise à jour et restituer le composant, sur le au contraire, React pourrait simplement pousser cette mise à jour vers une file d'attente qui sera traitée à un moment donné, qui pourrait en fait se trouver dans la trame suivante ou plus tard.
J'ai profilé une application vraiment simple en utilisant requestAnimationFrame
pour voir si c'était le cas et en fait ce n'est pas le cas:
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>
Ici, vous pouvez voir comment les mises à jour sont ajoutées à la file d'attente (enqueueSetState
), mais le travail est effectué immédiatement et tous les appels à update
, setState
, render
, commitWork
... se produisent dans le même cadre:
Cependant, je soupçonne que dans une application réelle où React a plus de mises à jour à gérer ou dans les futures versions de React avec des fonctionnalités comme le découpage temporel, les mises à jour asynchrones avec des priorités) ... ce n'est peut-être pas le cas.