ReactJS中如何实现基于循环数据的UI逐帧更新?
实现React元素的分步移动动画
问题描述
我在UI中创建了网格,希望展示数组给定所有点位的完整移动流程,但UI直接更新为循环的最后一个值。例如
position = [1, 2, 3],期望圆圈从1移动到2再到3,而非直接从1跳到3。以下是我的Game组件代码:
import React, { useEffect, useState } from 'react'; import { Button } from 'reactstrap'; import { motion } from "framer-motion"; import './style.css'; import Grid from './grid' class Game extends React.Component { constructor(props) { super(props); this.state = { idOfBegin: "1", position: 0, xAxis: 0, yAxis: 0, position: [], arrOfObj: [100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] }; } start = () => { var a = document.getElementById("3"); var codinates = a.getBoundingClientRect(); var x = codinates.left + 35; var y = codinates.top + 5; console.log(x, y); this.setState({ xAxis: x }); this.setState({ yAxis: y }); } loadPosition = async (event) => { fetch('api/groups') .then(response => response.json()) .then(data => { //console.log('======== ' +data); //groups.map(group => setPosition(group)); this.setState({ position: data }); }); } getCellValue = async (event) => { this.loadPosition(); this.state.position.forEach(p => { console.log('====== ' + p); if (p != null) { var a = document.getElementById(p); var codinates = a.getBoundingClientRect(); var x = codinates.left + 35; var y = codinates.top + 5; //console.log(x,y); this.setState({ xAxis: x }); this.setState({ yAxis: y }); } }); } componentDidMount() { this.setState({ xAxis: 45 }); this.setState({ yAxis: 905 }); } render() { return ( <div> <div className="grid"><Grid arrOfObj={this.state.arrOfObj} /> <motion.div animate={{ x: this.state.xAxis, y: this.state.yAxis }} transition={{ type: "tween", duration: 1 }} ></motion.div> </div> <div className="control"> <Button color="primary" onClick={() => this.start()}>Start</Button> <Button color="primary" onClick={() => this.getCellValue()}>Roll Dice</Button> </div> </div> ); } } export default Game;
问题原因
setState是异步批量更新机制,循环中连续调用会直接覆盖之前的状态,最终只应用最后一次更新结果。- 未等待前一个动画完成就触发下一次状态更新,导致动画被中断,直接跳到最终位置。
解决方案
修改代码,确保逐个处理位置并等待每个动画完成:
import React from 'react'; import { Button } from 'reactstrap'; import { motion } from "framer-motion"; import './style.css'; import Grid from './grid' class Game extends React.Component { constructor(props) { super(props); this.state = { idOfBegin: "1", xAxis: 45, yAxis: 905, position: [], arrOfObj: [100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] }; this.motionRef = React.createRef(); } start = () => { const a = document.getElementById("3"); const codinates = a.getBoundingClientRect(); const x = codinates.left + 35; const y = codinates.top + 5; this.setState({ xAxis: x, yAxis: y }); } loadPosition = () => { return fetch('api/groups') .then(response => response.json()) .then(data => { this.setState({ position: data }); return data; }); } getCellValue = async () => { const positions = await this.loadPosition(); if (!positions || positions.length === 0) return; for (const p of positions) { if (!p) continue; const a = document.getElementById(p); if (!a) continue; const codinates = a.getBoundingClientRect(); const x = codinates.left + 35; const y = codinates.top + 5; this.setState({ xAxis: x, yAxis: y }); await new Promise(resolve => setTimeout(resolve, 1100)); } } render() { return ( <div> <div className="grid"> <Grid arrOfObj={this.state.arrOfObj} /> <motion.div ref={this.motionRef} animate={{ x: this.state.xAxis, y: this.state.yAxis }} transition={{ type: "tween", duration: 1 }} ></motion.div> </div> <div className="control"> <Button color="primary" onClick={this.start}>Start</Button> <Button color="primary" onClick={this.getCellValue}>Roll Dice</Button> </div> </div> ); } } export default Game;
关键修改点
- 移除state中重复的
position定义,初始化时直接设置xAxis和yAxis,减少不必要的setState调用。 - 将
loadPosition改为返回Promise,确保数据加载完成后再处理移动逻辑。 getCellValue改为异步函数,使用for...of循环逐个遍历位置,每次更新状态后等待1.1秒(确保1秒的动画完全结束)。- 合并
start方法中的两次setState为一次,优化性能。
内容的提问来源于stack exchange,提问作者chk.buddi
相关产品推荐
相关产品推荐

