javascript - 如何仅重新渲染功能组件中的 JSX?
问题描述
我正在通过制作战舰游戏来学习反应。我的网格如下所示:
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> )
})
它位于功能组件内部。我不能重新渲染整个组件,因为船的位置会改变(它们随机放置在板上)
解决方案
我假设您直接在状态中设置字段。这样,板对象保持不变,并且不会触发重新渲染。您必须重新分配状态,从而创建状态更改以做出反应。问题是:您没有重新渲染整个组件。您的函数正在返回新状态,是的。但是 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')
);
推荐阅读
- docker - 运行时访问docker容器的IP
- flutter - Flutter中的DropdownButtonFormField在使用图标时不居中,在其InputDecoration中使用suffixIcon时,将无法按下
- symfony - crudcontroller 中基于当前登录用户或用户 ID 的实体数据
- typescript - 如何在打字稿中访问嵌套条件类型
- ansible - 如何遍历主机名不同的变量列表
- hangouts-chat - hangout-chat 可以重新调整它的 PC 网络或移动设备吗?我想让它自己自动调整大小
- javascript - 使用 GET METHOD 选择表单
- amazon-web-services - AWS alb/elb 日志或 vpc 流日志是否包含用户 POST 请求的正文?
- reactjs - VS Code 不会警告可能的未定义对象
- unity3d - VSCode 中的 Unity API 离线搜索