与while连续反应变化状态

问题描述

我正在尝试使用React来实现 The Life 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,但是添加这样的子句时,屏幕永远不会更新。

解决方法

虽然循环阻塞了主线程,所以它们没有给您的应用程序更新UI的机会。建议您使用requestAnimationFramesetTimeout来执行更新。

可能看起来像这样:

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。

您可以尝试使用setTimeoutsetInterval将您的逻辑发布到下一事件循环。

,

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>