React Hook 用于重新渲染由异步递归函数更新的状态
React Hook for re-rendering state updated by asynchronous recursive function
我正在构建一个 React 应用程序来解决数独游戏并可视化解决算法。
我正在尝试将 class 组件转换为功能组件,但不确定如何使状态更改生效。
有问题的代码是:
async solve() {
await this.sleep(0);
const board = this.state.board;
for (let row = 0; row < 9; row++) {
for (let col = 0; col < 9; col++) {
if (board[row][col] !== 0) {
// can't change filled cells
continue;
}
// try 1 to 9 for the empty cell
for (let num = 1; num <= 9; num++) {
if (App.isValidMove(board, row, col, num)) {
// valid move, try it
board[row][col] = num;
this.setState({ board: board });
if (await this.solve()) {
return true;
} else {
// didn't solve with this move, backtrack
board[row][col] = 0;
this.setState({ board: board });
}
}
}
// nothing worked for empty cell, must have deprived boards solution with a previous move
return false;
}
}
// all cells filled
return true;
}
(Full code here 和
app is hosted here)
这里我需要async
所以我可以使用sleep()
来可视化算法。我使用 this.setState({ board: board });
来在每次棋盘发生变化时触发重新渲染。
尝试转换为功能组件时,我尝试了:
useState
挂钩,我使用 const [board, setBoard] = useState(initialBoard);
并将 this.setState
调用替换为 setBoard(board)
。这没有用,因为钩子正在 called in a loop
- 用
useEffect
包装 useState
(即 useEffect(() => setBoard(board), [board])
)。这没有编译,得到错误 React Hook "useEffect" may be executed more than once...
- 也调查了
useReducer
但 read it doesn't work well with async
我的问题是:
- 这甚至可以转换为功能组件吗?
- 如果是,我应该使用什么钩子,是否需要重新设计我的
solve()
函数?
- 最好将它从 class 组件转换为功能组件吗?
你可以在每次棋盘改变时使用一个效果,这个效果会运行。当没有更多的动作时,棋盘不会改变,效果也不会再运行。
以下是如何执行此操作的示例:
const { useState, useEffect } = React;
const getCell = (board) => {
const row = board.findIndex((row) =>
row.some((cell) => !cell.checked)
);
if (row === -1) {
return [-1, -1, false];
}
const col = board[row].findIndex((cell) => !cell.checked);
return [row, col, true];
};
const initialState = [
[{ checked: false }, { checked: false }],
[{ checked: false }, { checked: false }],
];
const App = () => {
const [board, setBoard] = useState(initialState);
useEffect(() => {
const timer = setTimeout(
() =>
setBoard((board) => {
const [row, col, set] = getCell(board);
if (set) {//do we need to set board (there are moves)
const boardCopy = [...board];
boardCopy[row] = [...board[row]];
boardCopy[row][col] = {
...boardCopy[row][col],
checked: true,
};
return boardCopy;
}
//this will return the same as passed in
// so App will not re render because board
// will not have changed
return board;//no moves left because set is false
}),
1000
);
return () => clearTimeout(timer);
}, [board]);
return (
<div>
<button onClick={() => setBoard(initialState)}>
reset
</button>
<table>
<tbody>
{board.map((row, i) => (
<tr key={i}>
{row.map(({ checked }, i) => (
<td key={i}>{checked ? 'X' : 'O'}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
已使用 useState
挂钩。
问题是我没有正确抄板;需要使用 const boardCopy = [...board];
(而不是 const boardCopy = board
)。
我正在构建一个 React 应用程序来解决数独游戏并可视化解决算法。
我正在尝试将 class 组件转换为功能组件,但不确定如何使状态更改生效。
有问题的代码是:
async solve() {
await this.sleep(0);
const board = this.state.board;
for (let row = 0; row < 9; row++) {
for (let col = 0; col < 9; col++) {
if (board[row][col] !== 0) {
// can't change filled cells
continue;
}
// try 1 to 9 for the empty cell
for (let num = 1; num <= 9; num++) {
if (App.isValidMove(board, row, col, num)) {
// valid move, try it
board[row][col] = num;
this.setState({ board: board });
if (await this.solve()) {
return true;
} else {
// didn't solve with this move, backtrack
board[row][col] = 0;
this.setState({ board: board });
}
}
}
// nothing worked for empty cell, must have deprived boards solution with a previous move
return false;
}
}
// all cells filled
return true;
}
(Full code here 和 app is hosted here)
这里我需要async
所以我可以使用sleep()
来可视化算法。我使用 this.setState({ board: board });
来在每次棋盘发生变化时触发重新渲染。
尝试转换为功能组件时,我尝试了:
useState
挂钩,我使用const [board, setBoard] = useState(initialBoard);
并将this.setState
调用替换为setBoard(board)
。这没有用,因为钩子正在 called in a loop- 用
useEffect
包装useState
(即useEffect(() => setBoard(board), [board])
)。这没有编译,得到错误React Hook "useEffect" may be executed more than once...
- 也调查了
useReducer
但 read it doesn't work well withasync
我的问题是:
- 这甚至可以转换为功能组件吗?
- 如果是,我应该使用什么钩子,是否需要重新设计我的
solve()
函数? - 最好将它从 class 组件转换为功能组件吗?
你可以在每次棋盘改变时使用一个效果,这个效果会运行。当没有更多的动作时,棋盘不会改变,效果也不会再运行。
以下是如何执行此操作的示例:
const { useState, useEffect } = React;
const getCell = (board) => {
const row = board.findIndex((row) =>
row.some((cell) => !cell.checked)
);
if (row === -1) {
return [-1, -1, false];
}
const col = board[row].findIndex((cell) => !cell.checked);
return [row, col, true];
};
const initialState = [
[{ checked: false }, { checked: false }],
[{ checked: false }, { checked: false }],
];
const App = () => {
const [board, setBoard] = useState(initialState);
useEffect(() => {
const timer = setTimeout(
() =>
setBoard((board) => {
const [row, col, set] = getCell(board);
if (set) {//do we need to set board (there are moves)
const boardCopy = [...board];
boardCopy[row] = [...board[row]];
boardCopy[row][col] = {
...boardCopy[row][col],
checked: true,
};
return boardCopy;
}
//this will return the same as passed in
// so App will not re render because board
// will not have changed
return board;//no moves left because set is false
}),
1000
);
return () => clearTimeout(timer);
}, [board]);
return (
<div>
<button onClick={() => setBoard(initialState)}>
reset
</button>
<table>
<tbody>
{board.map((row, i) => (
<tr key={i}>
{row.map(({ checked }, i) => (
<td key={i}>{checked ? 'X' : 'O'}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
已使用 useState
挂钩。
问题是我没有正确抄板;需要使用 const boardCopy = [...board];
(而不是 const boardCopy = board
)。