【问题标题】:How to re-render only the JSX in function component?如何仅重新渲染功能组件中的 JSX?
【发布时间】:2021-08-08 18:20:48
【问题描述】:

我正在通过制作战舰游戏来学习反应。我的网格如下所示:

const [board, setBoard] = 
   useState([{ship: false, beenHit: false}, {ship: true, beenHit: false},
               {ship: true, beenHit: false},{ship: false, beenHit: false},
               {ship: false, beenHit: false},{ship: true, beenHit: false}
              ])

每个字段都是一个对象,有 2 个属性,表示字段上是否有船,以及玩家是否已经点击过它。我为每个字段分配了 receiveHit() 函数,所以当玩家点击该字段时,它将数组中的对象标记为 {ship: true, benHit: true}。它工作正常,但问题是,网格不会重新渲染自己,所以字段的颜色不会改变。

决定类名(以及字段颜色)的函数:

function decideColor(field){
     if(field.beenHit && field.ship){
        return 'red'
     } else{
         return 'blue'
       }
}

返回的 JSX:

return ({
 board.map((field, i)=> <div onClick = {() => board.receiveHit(i)} className = {decideColor(field)}> 
 </div> )
})

它位于功能组件内部。我无法重新渲染整个组件,因为船的位置会改变(它们随机放置在板上)

【问题讨论】:

    标签: javascript reactjs react-hooks components


    【解决方案1】:

    我假设您直接在状态中设置字段。这样,板对象保持不变,并且不会触发重新渲染。您必须重新分配状态,从而创建状态更改以做出反应。问题是:您没有重新渲染整个组件。您的函数正在返回新状态,是的。但是 React 的内部机制决定了哪些需要重新渲染到 DOM 哪些不需要(props 和 keys 在这里发挥作用,请确保使用它们)。以我为例,打开 DevTools 并观察单击框时所做的更改。您将看到只有被点击的 div 被渲染到 DOM。因此,明智的做法是制作大量更小的组件以提高重新渲染的效率。

    .board {
      display:flex; 
      width: calc(20em + 20px); /* 10 x 10 field with borders */
      flex-wrap:wrap;
    }
    
    .field { 
      display:inline-block;
      width: 2em;
      height: 2em;
      background-color: lightgray;
      border: thin solid black;
    }
    
    .field.ship {
      background-color: blue;
    }
    
    .field.hit {
      background-color: grey;
    }
    
    .field.ship.hit {
      background-color: red;
    }
    
    
    const Field = ({field, ...props}) => {
      return <div {...props}/>
    }
    
    const Board = () => {
      // init 10 x 10 board
      const [board, setBoard] = React.useState([...new Array(100)].map((elem, index) => {return {}}));
      const onClick = (event, i) => {
        // This takes the existing board, replaces the element with the index i 
        // with hit set to true. And sets the newly created *copy* as the new 
        // version of the board. It will trigger a re-render but as mentioned
        // before, only the truly changed (in this case className changes) will 
        // rendered to the DOM.
        setBoard(Object.assign([...board], {
            [i]: {
                ...board[i],
                hit: true
            }
        }));
      }  
      
      const decideClassName = (i) => {
        // color is based on the keys and the resulting class combination,
        // field, field ship, field ship hit, and field hit (see CSS)
        return `field ${Object.keys(board[i]).join(" ")}`;
      }
      
      const setShip = (board, x, y, length, type = "horizontal") => {
        const index = y * 10 + x;
        
        for (let i = 0; i < length; i++) {
          board[index + i * (type == "horizontal" ? 1 : 10)].ship = true;  
        }
      }
      
      // init ships
      React.useEffect(() => {
        const copy = [...board];
        setShip(copy, 5,5,4);
        setShip(copy, 2,2,3, "vertical");
        setBoard(copy);
      }, [])
      
      return (
        <div className="board">
          {
            board.map((field, i) => <Field key={`field-${i}`} field={field} onClick={(event) => onClick(event, i)} className={decideClassName(i)}/>)
          }
        </div>
    
      )
    }
        
    ReactDOM.render(
        <Board/>,
      document.getElementById('root')
    );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-29
      • 2020-10-31
      • 1970-01-01
      • 2020-01-06
      • 2021-12-08
      • 2022-11-29
      • 2019-01-06
      • 2020-11-30
      相关资源
      最近更新 更多