用 while 连续反应改变状态
React change state continuously with while
我正在尝试使用 React 实现 The Game Of Life,并且它的工作原理......大部分。我的实现包括一个触发游戏的 start
按钮,并且必须一直循环直到用户按下按钮 stop
。
我说它有效...主要是因为我可以看到矩阵正在按预期发展,但我无法使用 while
子句循环该过程。当我删除这样的条款时,游戏会在用户每次按下时执行一个循环 start
,但我希望游戏连续执行循环。
这是代码:
import React from 'react';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Grid from './Grid';
class Matrix extends React.Component {
constructor (props) {
super(props);
this.state = {
cycle: 0,
rows: 120,
columns: 260,
livingBase: 0.01,
matrix: null,
killWith: 2,
setAliveWith: 4,
continue: true
};
}
getRandomMatrix = () => {
let randomMatrix=[]
let randomRow = [];
let randomNumber = null;
for (let i=0; i<this.state.columns; i++) {
randomRow = []
for (let j=0; j<this.state.rows; j++) {
randomNumber = Math.random();
randomRow.push(randomNumber < this.state.livingBase? true: false);
}
randomMatrix.push(randomRow);
}
this.setState({matrix: randomMatrix})
}
getNextMatrix = () => {
let newMatrix = this.state.matrix.slice();
const getCurrentCellLivingNeighbours = (n, m) => {
let currenCellLivingNeighburs = 0;
const getNeighbour = (l, k) => {
let tryCell = false;
try {
tryCell = this.state.matrix[l][k];
return tryCell
} catch {
return false
}
}
if (getNeighbour(n-1, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n-1, m)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n-1, m+1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n, m+1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m+1)) {
currenCellLivingNeighburs++;
}
return currenCellLivingNeighburs;
}
for (let j=0; j<this.state.matrix.length; j++) {
for (let i=0; i<this.state.matrix[0].length; i++) {
let currentCell = this.state.matrix[j][i];
let livingNeigbours = getCurrentCellLivingNeighbours(j, i)
if (currentCell) {
if (livingNeigbours<2 || livingNeigbours>3) {
newMatrix[j][i] = false;
}
} else {
if (livingNeigbours === 3) {
newMatrix[j][i] = true;
}
}
}
}
this.setState({matrix: newMatrix})
}
handleResetClick = () => {
this.getRandomMatrix();
}
catchLivingBaseChange = (e) => {
this.setState({livingBase: parseInt(e.target.value)/100})
}
handleStartClick = () => {
while (this.state.continue) {
this.getNextMatrix();
}
}
render () {
return (
<div>
<AppBar position="static">
<Toolbar>
<div style={{display: 'flex'}}>
<div>
<Typography variant="h6" >
The Game Of Life
</Typography>
</div>
<div style={{paddingLeft: 15}}>
<TextField id="livingBase" label="Porcentaje de Inicio" defaultValue="1" onChange={this.catchLivingBaseChange.bind(this)} />
<Button color="black" variant='contained' onClick={this.handleResetClick.bind(this)} >Reset</Button>
<Button color="black" variant='contained' onClick={this.handleStartClick.bind(this)} >Start</Button>
</div>
</div>
</Toolbar>
</AppBar>
{this.state.matrix? <Grid matrix={this.state.matrix} />: <h1>Push reset to init matrix...</h1>}
</div>
)
}
}
export default Matrix;
方法 handleStartClick()
是我想用 while
子句循环的方法,但是添加这样的子句后屏幕永远不会更新。
While 循环会阻塞主线程,因此它们不会让您的应用有机会更新 UI。我建议您改用 requestAnimationFrame
or setTimeout
来执行更新。
可能看起来像这样:
const nextMatrix = () => {
// compute and return next generation
}
const Life = () => {
const [matrix, setMatrix] = useState();
const [running, setRunning] = useState(false);
const update = useCallback(() => {
setMatrix(nextMatrix()); // update state with the next generation
if (running) {
requestAnimationFrame(update); // run again asap (usually 60Hz)
}
}, [running])
return (
{/* render current matrix here */}
<button onClick={() => setRunning(!running)}>{running ? 'stop' : 'start'}</button>
)
}
请注意,此方法可能会对浏览器造成负担。如果您担心给其他任务一些喘息的空间,您可以使用 setTimeout 并降低更新矩阵的频率。
您是否在使用 while
循环时卡在屏幕上?我不认为你可以在 Javascript 中使用长 运行 过程来做到这一点,它很容易冻结你的 UI。
您可以尝试使用setTimeout
或setInterval
来post您的逻辑进入下一个事件循环周期。
这一行有异常let newMatrix = this.state.matrix.slice();
。 this.state.matrix
最初是 = null
而你正试图将它切片!
Cannot read property 'slice' of null
这就是 getNextMatrix()
不起作用的原因!
此外,您应该改用 setInterval
,因为一段时间后会阻止您的 UI 更新。
你可以这样维护循环。
setinterval( () => {
this.getNextMatrix();
}, 100); /// 100 or whatever value works for you. the value is in miliseconds
您可能想阅读有关 the event loop 的内容。
如果您想使用 while
循环,您需要一些方法将控制权交还给浏览器。您可以使用 async
函数和异步等待
const waitFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
handleStartClick = async() => { // async allows this function to use await
while (this.state.continue) {
this.getNextMatrix();
await waitFrame(); // await lets this function give up control to the browser
}
}
一个工作示例
<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const waitFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
class Grid extends React.Component {
render() {
const rows = this.props.matrix;
return (
<table>
{
rows.map(row => {
return (
<tr>
{
row.map(cell => {
return (
<td>
{cell ? '●' : '○'}
</td>
);
})
}
</tr>
);
})
}
</table>
);
}
};
class Matrix extends React.Component {
constructor (props) {
super(props);
this.state = {
cycle: 0,
rows: 120,
columns: 260,
livingBase: 0.33,
matrix: null,
killWith: 2,
setAliveWith: 4,
continue: true
};
}
getRandomMatrix = () => {
let randomMatrix=[]
let randomRow = [];
let randomNumber = null;
for (let i=0; i<this.state.columns; i++) {
randomRow = []
for (let j=0; j<this.state.rows; j++) {
randomNumber = Math.random();
randomRow.push(randomNumber < this.state.livingBase? true: false);
}
randomMatrix.push(randomRow);
}
this.setState({matrix: randomMatrix})
}
getNextMatrix = () => {
let newMatrix = this.state.matrix.slice();
const getCurrentCellLivingNeighbours = (n, m) => {
let currenCellLivingNeighburs = 0;
const getNeighbour = (l, k) => {
let tryCell = false;
try {
tryCell = this.state.matrix[l][k];
return tryCell
} catch(e) {
return false
}
}
if (getNeighbour(n-1, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n-1, m)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n-1, m+1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n, m+1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m+1)) {
currenCellLivingNeighburs++;
}
return currenCellLivingNeighburs;
}
for (let j=0; j<this.state.matrix.length; j++) {
for (let i=0; i<this.state.matrix[0].length; i++) {
let currentCell = this.state.matrix[j][i];
let livingNeigbours = getCurrentCellLivingNeighbours(j, i)
if (currentCell) {
if (livingNeigbours<2 || livingNeigbours>3) {
newMatrix[j][i] = false;
}
} else {
if (livingNeigbours === 3) {
newMatrix[j][i] = true;
}
}
}
}
this.setState({matrix: newMatrix})
}
handleResetClick = () => {
this.getRandomMatrix();
}
catchLivingBaseChange = (e) => {
this.setState({livingBase: parseInt(e.target.value)/100})
}
handleStartClick = async () => {
while (this.state.continue) {
this.getNextMatrix();
await waitFrame();
}
}
render () {
return (
<div>
<div style={{display: 'flex'}}>
<div style={{paddingLeft: 15}}>
<button color="black" variant='contained' onClick={this.handleResetClick.bind(this)} >Reset</button>
<button color="black" variant='contained' onClick={this.handleStartClick.bind(this)} >Start</button>
</div>
</div>
{this.state.matrix? <Grid matrix={this.state.matrix} />: <h1>Push reset to init matrix...</h1>}
</div>
)
}
}
ReactDOM.render(React.createElement(Matrix), document.body);
</script>
我正在尝试使用 React 实现 The Game Of Life,并且它的工作原理......大部分。我的实现包括一个触发游戏的 start
按钮,并且必须一直循环直到用户按下按钮 stop
。
我说它有效...主要是因为我可以看到矩阵正在按预期发展,但我无法使用 while
子句循环该过程。当我删除这样的条款时,游戏会在用户每次按下时执行一个循环 start
,但我希望游戏连续执行循环。
这是代码:
import React from 'react';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Grid from './Grid';
class Matrix extends React.Component {
constructor (props) {
super(props);
this.state = {
cycle: 0,
rows: 120,
columns: 260,
livingBase: 0.01,
matrix: null,
killWith: 2,
setAliveWith: 4,
continue: true
};
}
getRandomMatrix = () => {
let randomMatrix=[]
let randomRow = [];
let randomNumber = null;
for (let i=0; i<this.state.columns; i++) {
randomRow = []
for (let j=0; j<this.state.rows; j++) {
randomNumber = Math.random();
randomRow.push(randomNumber < this.state.livingBase? true: false);
}
randomMatrix.push(randomRow);
}
this.setState({matrix: randomMatrix})
}
getNextMatrix = () => {
let newMatrix = this.state.matrix.slice();
const getCurrentCellLivingNeighbours = (n, m) => {
let currenCellLivingNeighburs = 0;
const getNeighbour = (l, k) => {
let tryCell = false;
try {
tryCell = this.state.matrix[l][k];
return tryCell
} catch {
return false
}
}
if (getNeighbour(n-1, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n-1, m)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n-1, m+1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n, m+1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m+1)) {
currenCellLivingNeighburs++;
}
return currenCellLivingNeighburs;
}
for (let j=0; j<this.state.matrix.length; j++) {
for (let i=0; i<this.state.matrix[0].length; i++) {
let currentCell = this.state.matrix[j][i];
let livingNeigbours = getCurrentCellLivingNeighbours(j, i)
if (currentCell) {
if (livingNeigbours<2 || livingNeigbours>3) {
newMatrix[j][i] = false;
}
} else {
if (livingNeigbours === 3) {
newMatrix[j][i] = true;
}
}
}
}
this.setState({matrix: newMatrix})
}
handleResetClick = () => {
this.getRandomMatrix();
}
catchLivingBaseChange = (e) => {
this.setState({livingBase: parseInt(e.target.value)/100})
}
handleStartClick = () => {
while (this.state.continue) {
this.getNextMatrix();
}
}
render () {
return (
<div>
<AppBar position="static">
<Toolbar>
<div style={{display: 'flex'}}>
<div>
<Typography variant="h6" >
The Game Of Life
</Typography>
</div>
<div style={{paddingLeft: 15}}>
<TextField id="livingBase" label="Porcentaje de Inicio" defaultValue="1" onChange={this.catchLivingBaseChange.bind(this)} />
<Button color="black" variant='contained' onClick={this.handleResetClick.bind(this)} >Reset</Button>
<Button color="black" variant='contained' onClick={this.handleStartClick.bind(this)} >Start</Button>
</div>
</div>
</Toolbar>
</AppBar>
{this.state.matrix? <Grid matrix={this.state.matrix} />: <h1>Push reset to init matrix...</h1>}
</div>
)
}
}
export default Matrix;
方法 handleStartClick()
是我想用 while
子句循环的方法,但是添加这样的子句后屏幕永远不会更新。
While 循环会阻塞主线程,因此它们不会让您的应用有机会更新 UI。我建议您改用 requestAnimationFrame
or setTimeout
来执行更新。
可能看起来像这样:
const nextMatrix = () => {
// compute and return next generation
}
const Life = () => {
const [matrix, setMatrix] = useState();
const [running, setRunning] = useState(false);
const update = useCallback(() => {
setMatrix(nextMatrix()); // update state with the next generation
if (running) {
requestAnimationFrame(update); // run again asap (usually 60Hz)
}
}, [running])
return (
{/* render current matrix here */}
<button onClick={() => setRunning(!running)}>{running ? 'stop' : 'start'}</button>
)
}
请注意,此方法可能会对浏览器造成负担。如果您担心给其他任务一些喘息的空间,您可以使用 setTimeout 并降低更新矩阵的频率。
您是否在使用 while
循环时卡在屏幕上?我不认为你可以在 Javascript 中使用长 运行 过程来做到这一点,它很容易冻结你的 UI。
您可以尝试使用setTimeout
或setInterval
来post您的逻辑进入下一个事件循环周期。
这一行有异常let newMatrix = this.state.matrix.slice();
。 this.state.matrix
最初是 = null
而你正试图将它切片!
Cannot read property 'slice' of null
这就是 getNextMatrix()
不起作用的原因!
此外,您应该改用 setInterval
,因为一段时间后会阻止您的 UI 更新。
你可以这样维护循环。
setinterval( () => {
this.getNextMatrix();
}, 100); /// 100 or whatever value works for you. the value is in miliseconds
您可能想阅读有关 the event loop 的内容。
如果您想使用 while
循环,您需要一些方法将控制权交还给浏览器。您可以使用 async
函数和异步等待
const waitFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
handleStartClick = async() => { // async allows this function to use await
while (this.state.continue) {
this.getNextMatrix();
await waitFrame(); // await lets this function give up control to the browser
}
}
一个工作示例
<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const waitFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
class Grid extends React.Component {
render() {
const rows = this.props.matrix;
return (
<table>
{
rows.map(row => {
return (
<tr>
{
row.map(cell => {
return (
<td>
{cell ? '●' : '○'}
</td>
);
})
}
</tr>
);
})
}
</table>
);
}
};
class Matrix extends React.Component {
constructor (props) {
super(props);
this.state = {
cycle: 0,
rows: 120,
columns: 260,
livingBase: 0.33,
matrix: null,
killWith: 2,
setAliveWith: 4,
continue: true
};
}
getRandomMatrix = () => {
let randomMatrix=[]
let randomRow = [];
let randomNumber = null;
for (let i=0; i<this.state.columns; i++) {
randomRow = []
for (let j=0; j<this.state.rows; j++) {
randomNumber = Math.random();
randomRow.push(randomNumber < this.state.livingBase? true: false);
}
randomMatrix.push(randomRow);
}
this.setState({matrix: randomMatrix})
}
getNextMatrix = () => {
let newMatrix = this.state.matrix.slice();
const getCurrentCellLivingNeighbours = (n, m) => {
let currenCellLivingNeighburs = 0;
const getNeighbour = (l, k) => {
let tryCell = false;
try {
tryCell = this.state.matrix[l][k];
return tryCell
} catch(e) {
return false
}
}
if (getNeighbour(n-1, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n-1, m)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n-1, m+1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n, m+1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m-1)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m)) {
currenCellLivingNeighburs++;
}
if (getNeighbour(n+1, m+1)) {
currenCellLivingNeighburs++;
}
return currenCellLivingNeighburs;
}
for (let j=0; j<this.state.matrix.length; j++) {
for (let i=0; i<this.state.matrix[0].length; i++) {
let currentCell = this.state.matrix[j][i];
let livingNeigbours = getCurrentCellLivingNeighbours(j, i)
if (currentCell) {
if (livingNeigbours<2 || livingNeigbours>3) {
newMatrix[j][i] = false;
}
} else {
if (livingNeigbours === 3) {
newMatrix[j][i] = true;
}
}
}
}
this.setState({matrix: newMatrix})
}
handleResetClick = () => {
this.getRandomMatrix();
}
catchLivingBaseChange = (e) => {
this.setState({livingBase: parseInt(e.target.value)/100})
}
handleStartClick = async () => {
while (this.state.continue) {
this.getNextMatrix();
await waitFrame();
}
}
render () {
return (
<div>
<div style={{display: 'flex'}}>
<div style={{paddingLeft: 15}}>
<button color="black" variant='contained' onClick={this.handleResetClick.bind(this)} >Reset</button>
<button color="black" variant='contained' onClick={this.handleStartClick.bind(this)} >Start</button>
</div>
</div>
{this.state.matrix? <Grid matrix={this.state.matrix} />: <h1>Push reset to init matrix...</h1>}
</div>
)
}
}
ReactDOM.render(React.createElement(Matrix), document.body);
</script>