使用 React-Hooks,如何防止从 Array.map 创建的组件在其兄弟姐妹之一更改状态时重新呈现?
Using React-Hooks, how do I prevent a component created from Array.map from re-rendering if one of it's siblings changes state?
我使用 array.map 创建了一个组件网格。使用 console.log
我能够看到只要其中一个组件改变状态,每个组件都会重新渲染。当我有一个 50x50 的网格时,这会变得非常慢。
import React, { useState } from 'react';
function Cell({ cell, cellState, updateBoard }) {
console.log('cell rendered')
const CellStyle = {
display: 'inline-block',
width: '10px',
height: '10px',
border: '1px green solid',
background: cellState ? 'green' : 'purple'
};
function handleClick(e) {
updateBoard(cell, !cellState)
}
return (
<span
style={CellStyle}
onClick={handleClick}
/>
)
}
function App() {
console.log('board rendered')
const initialState = new Array(10).fill().map(() => new Array(10).fill(false));
let [board, setBoard] = useState(initialState);
function updateBoard(cell, nextState) {
let tempBoard = [...board];
tempBoard[cell[0]][cell[1]] = nextState;
setBoard(tempBoard)
}
return (
<div style={{ display: 'inline-block' }}>
{board.map((v, i, a) => {
return (
<div
key={`Row${i}`}
style={{ height: '12px' }}
>
{v.map((w, j) =>
<Cell
key={`${i}-${j}`}
cell={[i, j]}
cellState={board[i][j]}
updateBoard={updateBoard}
/>
)}
</div>
)
}
)}
</div>
)
}
export default App;
当我点击其中一个组件时,我希望父状态更新,点击的组件更新并重新渲染。由于其余组件未更改,我不希望其他组件重新呈现。我如何使用 React-Hooks 完成此操作?
很少有东西可以大大提高性能:
- 使用
memo()
const MemoizedCell = memo(Cell);
/*...*/
<MemoizedCell
/*...*/
/>
- 不是每次都传递对
<Cell />
的新引用
您正在传递 cell={[i, j]}
- 它会在您每次调用它时创建 新数组 (!) 这意味着 Cells 的属性已更改 - 为什么不然后再渲染?
与传递 updateBoard={updateBoard}
相同 - 每次 <App />
渲染时您都在创建新函数。你需要记住它并在功能中使用旧状态。
const updateBoard = useCallback(
(cell, nextState) => {
setBoard(oldBoard => {
let tempBoard = [...oldBoard];
tempBoard[cell[0]][cell[1]] = nextState;
return tempBoard;
});
},
[setBoard]
);
- 您正在为每个渲染创建
initialState
- 将其移动到上方(外部)<App />
或在 useState
内创建它作为函数(并使用 const
而不是 let
这里).
const [board, setBoard] = useState(() =>
new Array(10).fill().map(() => new Array(10).fill(false))
);
最终解决方案:
import React, { useState, memo, useCallback } from "react";
import ReactDOM from "react-dom";
function Cell({ i, j, cellState, updateBoard }) {
console.log(`cell ${i}, ${j} rendered`);
const CellStyle = {
display: "inline-block",
width: "10px",
height: "10px",
border: "1px green solid",
background: cellState ? "green" : "purple"
};
function handleClick(e) {
updateBoard([i, j], !cellState);
}
return <span style={CellStyle} onClick={handleClick} />;
}
const MemoizedCell = memo(Cell);
function App() {
console.log("board rendered");
const [board, setBoard] = useState(() =>
new Array(10).fill().map(() => new Array(10).fill(false))
);
const updateBoard = useCallback(
(cell, nextState) => {
setBoard(oldBoard => {
let tempBoard = [...oldBoard];
tempBoard[cell[0]][cell[1]] = nextState;
return tempBoard;
});
},
[setBoard]
);
return (
<div style={{ display: "inline-block" }}>
{board.map((v, i, a) => {
return (
<div key={`Row${i}`} style={{ height: "12px" }}>
{v.map((w, j) => (
<MemoizedCell
key={`${i}-${j}`}
i={i}
j={j}
cellState={board[i][j]}
updateBoard={updateBoard}
/>
))}
</div>
);
})}
</div>
);
}
export default App;
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
我使用 array.map 创建了一个组件网格。使用 console.log
我能够看到只要其中一个组件改变状态,每个组件都会重新渲染。当我有一个 50x50 的网格时,这会变得非常慢。
import React, { useState } from 'react';
function Cell({ cell, cellState, updateBoard }) {
console.log('cell rendered')
const CellStyle = {
display: 'inline-block',
width: '10px',
height: '10px',
border: '1px green solid',
background: cellState ? 'green' : 'purple'
};
function handleClick(e) {
updateBoard(cell, !cellState)
}
return (
<span
style={CellStyle}
onClick={handleClick}
/>
)
}
function App() {
console.log('board rendered')
const initialState = new Array(10).fill().map(() => new Array(10).fill(false));
let [board, setBoard] = useState(initialState);
function updateBoard(cell, nextState) {
let tempBoard = [...board];
tempBoard[cell[0]][cell[1]] = nextState;
setBoard(tempBoard)
}
return (
<div style={{ display: 'inline-block' }}>
{board.map((v, i, a) => {
return (
<div
key={`Row${i}`}
style={{ height: '12px' }}
>
{v.map((w, j) =>
<Cell
key={`${i}-${j}`}
cell={[i, j]}
cellState={board[i][j]}
updateBoard={updateBoard}
/>
)}
</div>
)
}
)}
</div>
)
}
export default App;
当我点击其中一个组件时,我希望父状态更新,点击的组件更新并重新渲染。由于其余组件未更改,我不希望其他组件重新呈现。我如何使用 React-Hooks 完成此操作?
很少有东西可以大大提高性能:
- 使用
memo()
const MemoizedCell = memo(Cell);
/*...*/
<MemoizedCell
/*...*/
/>
- 不是每次都传递对
<Cell />
的新引用
您正在传递 cell={[i, j]}
- 它会在您每次调用它时创建 新数组 (!) 这意味着 Cells 的属性已更改 - 为什么不然后再渲染?
与传递 updateBoard={updateBoard}
相同 - 每次 <App />
渲染时您都在创建新函数。你需要记住它并在功能中使用旧状态。
const updateBoard = useCallback(
(cell, nextState) => {
setBoard(oldBoard => {
let tempBoard = [...oldBoard];
tempBoard[cell[0]][cell[1]] = nextState;
return tempBoard;
});
},
[setBoard]
);
- 您正在为每个渲染创建
initialState
- 将其移动到上方(外部)<App />
或在useState
内创建它作为函数(并使用const
而不是let
这里).
const [board, setBoard] = useState(() =>
new Array(10).fill().map(() => new Array(10).fill(false))
);
最终解决方案:
import React, { useState, memo, useCallback } from "react";
import ReactDOM from "react-dom";
function Cell({ i, j, cellState, updateBoard }) {
console.log(`cell ${i}, ${j} rendered`);
const CellStyle = {
display: "inline-block",
width: "10px",
height: "10px",
border: "1px green solid",
background: cellState ? "green" : "purple"
};
function handleClick(e) {
updateBoard([i, j], !cellState);
}
return <span style={CellStyle} onClick={handleClick} />;
}
const MemoizedCell = memo(Cell);
function App() {
console.log("board rendered");
const [board, setBoard] = useState(() =>
new Array(10).fill().map(() => new Array(10).fill(false))
);
const updateBoard = useCallback(
(cell, nextState) => {
setBoard(oldBoard => {
let tempBoard = [...oldBoard];
tempBoard[cell[0]][cell[1]] = nextState;
return tempBoard;
});
},
[setBoard]
);
return (
<div style={{ display: "inline-block" }}>
{board.map((v, i, a) => {
return (
<div key={`Row${i}`} style={{ height: "12px" }}>
{v.map((w, j) => (
<MemoizedCell
key={`${i}-${j}`}
i={i}
j={j}
cellState={board[i][j]}
updateBoard={updateBoard}
/>
))}
</div>
);
})}
</div>
);
}
export default App;
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);