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