使用useContext时重置子组件状态:点击按钮后计算结果未重置的修复
问题:再次点击按钮后Analysis组件的Table保留旧计算结果并累加
首次点击DealWithHands组件中的按钮时功能正常,但再次点击后,Analysis组件内的table会保留之前的计算结果并基于此继续累加计算,这不符合预期。
我考虑过两种解决思路,但存在疑问:
- 点击按钮时先卸载再重新挂载子组件,但不清楚实现方法,且担心性能开销
- 由父组件控制子组件状态,但认为这并非合理方案,因为子组件状态与父组件无关
相关代码
DealWithHands组件
import { useState, ChangeEvent, useMemo } from "react"; import "./index.css"; import ShowResults from "../Show/ShowResults"; function deal() { /* get boards */ } export default function DealWithHands() { const [board_size, setBoard_size] = useState<number>(1); const [boards, setBoards] = useState<any[][]>([]); function handleSize(e: ChangeEvent) { setBoard_size(Number((e.target as HTMLInputElement).value)); } function handleClick() { setBoards(deal(Number(board_size))); } return ( <> <div className="deal-setting"> <input value={board_size} onChange={handleSize} placeholder="input your number here"></input> <button onClick={handleClick}>Get new boards</button> </div> {useMemo(() => <ShowResults all_boards={boards.map((x) => { return { board: x } })} /> , [boards])} </> ) }
ShowResults组件
import React, { createContext } from "react"; import ShowAllBoards from "../../Components/ShowAllBoards/ShowAllBoards"; import Hand from "../Deal/Hand"; import Analysis from "../Analysis/Analysis"; export interface CompleteBoard { board: Hand[]; ddtricks?: (string | number)[][] | string; } interface ShowResultsProps { all_boards: CompleteBoard[]; } interface ShowResultsContextProps { all_boards: CompleteBoard[]; } export const ShowResultsContext = createContext<ShowResultsContextProps>({ all_boards: []}); function ShowResults(props: ShowResultsProps) { const { all_boards, ...rest } = props; return ( <ShowResultsContext.Provider value={{ all_boards }}> <Analysis /> <ShowAllBoards {...rest} /> </ShowResultsContext.Provider> ) } export default React.memo(ShowResults);
Analysis组件
import React, { useContext, useMemo, useState } from "react"; import ShowTricks from "../../Components/PlayBoard/ShowTricks"; import { analyzeOffline } from "../../Utils/utils"; import { ShowResultsContext } from "../Show/ShowResults"; import "./analysis.css" function MatrixAdd(a: number[][], b: (string | number)[][]) { const ret: number[][] = []; for (let i = 0; i < a.length; ++i) { ret[i] = []; for (let j = 0; j < a[0].length; ++j) { ret[i][j] = a[i][j] + Number(b[i][j]); } } return ret; } function MatrixDivide(a: number[][], b: number) { const ret: number[][] = []; for (let i = 0; i < a.length; ++i) { ret[i] = []; for (let j = 0; j < a[0].length; ++j) { ret[i][j] = a[i][j] / b; } } return ret; } function Analysis() { const [table, setTable] = useState(new Array(4).fill(0).map(() => new Array(5).fill(0))); const context = useContext(ShowResultsContext); const { all_boards } = context; useMemo(() => { async function countTricks() { let tmp: number[][] = []; table.forEach((row) => { tmp.push([...row]); }) for (const board of all_boards) { if (!board.ddtricks) { board.ddtricks = await analyzeOffline(board.board); } tmp = MatrixAdd(tmp, board.ddtricks as (string | number)[][]); } return Promise.resolve(tmp); } countTricks().then((res) => { if (all_boards.length > 0) { setTable(MatrixDivide(res, all_boards.length)); } }) }, [all_boards]) return ( <fieldset className="showStatistics"> <legend>The average tricks:</legend> <ShowTricks ddtricks={table} /> </fieldset> ) } export default Analysis;
解决方案
问题根源在于:
- Analysis组件中每次计算新牌局时,
tmp是基于之前的table状态初始化的,导致新结果与旧结果累加; - 使用
useMemo处理异步状态更新属于误用,useMemo用于缓存计算结果,而非处理副作用。
修改步骤如下:
1. 修正Analysis组件的计算逻辑
将useMemo替换为useEffect(专门处理异步、状态更新这类副作用),并每次从全0矩阵开始计算新牌局的平均值,而非基于旧table:
import React, { useContext, useEffect, useState } from "react"; import ShowTricks from "../../Components/PlayBoard/ShowTricks"; import { analyzeOffline } from "../../Utils/utils"; import { ShowResultsContext } from "../Show/ShowResults"; import "./analysis.css" function MatrixAdd(a: number[][], b: (string | number)[][]) { const ret: number[][] = []; for (let i = 0; i < a.length; ++i) { ret[i] = []; for (let j = 0; j < a[0].length; ++j) { ret[i][j] = a[i][j] + Number(b[i][j]); } } return ret; } function MatrixDivide(a: number[][], b: number) { const ret: number[][] = []; for (let i = 0; i < a.length; ++i) { ret[i] = []; for (let j = 0; j < a[0].length; ++j) { ret[i][j] = a[i][j] / b; } } return ret; } function Analysis() { const [table, setTable] = useState(new Array(4).fill(0).map(() => new Array(5).fill(0))); const context = useContext(ShowResultsContext); const { all_boards } = context; useEffect(() => { async function countTricks() { // 每次计算都从全0矩阵开始,避免旧结果累加 let tmp = new Array(4).fill(0).map(() => new Array(5).fill(0)); // 复制数组避免直接修改context中的原对象 const targetBoards = [...all_boards]; for (const board of targetBoards) { if (!board.ddtricks) { const ddtricks = await analyzeOffline(board.board); board.ddtricks = ddtricks; } tmp = MatrixAdd(tmp, board.ddtricks as (string | number)[][]); } if (targetBoards.length > 0) { setTable(MatrixDivide(tmp, targetBoards.length)); } else { // 无牌局时重置table为全0 setTable(new Array(4).fill(0).map(() => new Array(5).fill(0))); } } countTricks(); }, [all_boards]); return ( <fieldset className="showStatistics"> <legend>The average tricks:</legend> <ShowTricks ddtricks={table} /> </fieldset> ) } export default Analysis;
2. 可选优化:避免修改Context中的原对象
如果希望完全避免直接修改all_boards中的对象,可以在DealWithHands组件中提前处理牌局的计算,或者使用useReducer管理状态,防止状态突变引发的潜在问题。
内容的提问来源于stack exchange,提问作者SNORLAX
相关产品推荐
相关产品推荐

