From f29974c111db876a081b8c861c48e476ab610371 Mon Sep 17 00:00:00 2001 From: kyuuseiryuu Date: Mon, 16 Mar 2026 23:46:22 +0900 Subject: [PATCH] refactor(BattleTable): extract resultMap logic and simplify table building - Move `initResultMap` and `buildMatchGroupTable` outside the component to reduce closure dependencies. - Update `ClubEventList` to remove obsolete page key storage logic. - Add `MATCH_RESULT_MAP_KEY` constant to `utils/constants.ts`. - Refactor `useMemo` and `useCallback` hooks to rely on the extracted functions. - Ensure consistent dependency arrays in callbacks for reliable re-renders. --- src/components/BattleTable.tsx | 94 ++++++++++++++++---------------- src/components/ClubEventList.tsx | 6 -- src/utils/constants.ts | 3 +- 3 files changed, 49 insertions(+), 54 deletions(-) diff --git a/src/components/BattleTable.tsx b/src/components/BattleTable.tsx index a3ec331..3aef4fc 100644 --- a/src/components/BattleTable.tsx +++ b/src/components/BattleTable.tsx @@ -6,6 +6,7 @@ import styled from "styled-components"; import { cloneDeep } from "lodash"; import { ClearOutlined, EditOutlined, ShareAltOutlined } from "@ant-design/icons"; import { useLocation } from "react-router"; +import { MATCH_RESULT_MAP_KEY } from "../utils/constants"; const StyledContainer = styled(Flex)` min-width: 300px; @@ -30,6 +31,42 @@ function getMatchKey(player1: BasePlayer, player2: BasePlayer) { return `${player1.uid}-${player2.uid}`; } +function initResultMap() { + const cache = localStorage.getItem('match-result-map'); + const cacheEntries: [string, Result][] = cache ? Object.entries(JSON.parse(cache)) : []; + return new Map(cacheEntries); +} + +const buildMatchGroupTable = (list: (BasePlayer | null)[], resultMap: Map) => { + const roundNum = list.length - 1; + const dataList = cloneDeep(list); + const roundTable = new Array(roundNum).fill(0).map((_, i) => { + const [left, right] = getRoundTable(dataList, i); + left?.forEach((l, i) => { + const r = right?.[i]; + const key = getMatchKey(l!, r!); + const winer = resultMap.get(key)?.winer; + const score = resultMap.get(key)?.score; + if (winer && l && r) { + if (winer === l?.uid) { + l.score = (+l.score + (score ?? 0)).toFixed(); + r.score = (+r.score - (score ?? 0)).toFixed(); + } else { + r.score = (+r.score + (score ?? 0)).toFixed(); + l.score = (+l.score - (score ?? 0)).toFixed(); + } + } + }); + return cloneDeep([left, right]); + }); + const cache = JSON.stringify(Object.fromEntries(resultMap.entries())); + localStorage.setItem(MATCH_RESULT_MAP_KEY, cache); + return roundTable; +}; + function BattleTable({ players: list, }: { @@ -40,44 +77,7 @@ function BattleTable({ score: '', name: '-', }], [list]); - const resultMap = useMemo(() => { - const cache = localStorage.getItem('match-result-map'); - const cacheEntries: [string, Result][] = cache ? Object.entries(JSON.parse(cache)) : []; - return new Map(cacheEntries); - }, []); - useEffect(() => { - return () => { - const cache = JSON.stringify(Object.fromEntries(resultMap.entries())); - localStorage.setItem('match-result-map', cache); - } - }, [resultMap]); - const buildMatchGroupTable = useCallback((list: (BasePlayer | null)[]) => { - const roundNum = list.length - 1; - const dataList = cloneDeep(list); - const roundTable = new Array(roundNum).fill(0).map((_, i) => { - const [left, right] = getRoundTable(dataList, i); - left?.forEach((l, i) => { - const r = right?.[i]; - const key = getMatchKey(l!, r!); - const winer = resultMap.get(key)?.winer; - const score = resultMap.get(key)?.score; - if (winer && l && r) { - if (winer === l?.uid) { - l.score = (+l.score + (score ?? 0)).toFixed(); - r.score = (+r.score - (score ?? 0)).toFixed(); - } else { - r.score = (+r.score + (score ?? 0)).toFixed(); - l.score = (+l.score - (score ?? 0)).toFixed(); - } - } - }); - return cloneDeep([left, right]); - }); - return roundTable; - }, []); + const resultMap = useMemo(() => initResultMap(), []); const [matchGroupTable, setMatchGroupTable] = useState>([]); const matchKeys = useMemo(() => { const keys: string[] = []; @@ -99,16 +99,16 @@ function BattleTable({ } else { resultMap.set(key, { winer: winer?.uid!, score }); } - setMatchGroupTable(buildMatchGroupTable(playerList)); - }, []); + setMatchGroupTable(buildMatchGroupTable(playerList, resultMap)); + }, [playerList, resultMap]); const handleClear = useCallback((left?: (BasePlayer | null | undefined)[], right?: (BasePlayer | null | undefined)[]) => { left?.forEach((l, i) => { const r = right?.[i]; const key = getMatchKey(l!, r!); resultMap.delete(key); }); - setMatchGroupTable(buildMatchGroupTable(playerList)); - }, [matchGroupTable, playerList]); + setMatchGroupTable(buildMatchGroupTable(playerList, resultMap)); + }, [playerList, resultMap]); const handleClearAll = useCallback(() => { matchGroupTable.forEach(([left, right]) => { left?.forEach((l, i) => { @@ -117,8 +117,8 @@ function BattleTable({ resultMap.delete(key); }); }); - setMatchGroupTable(buildMatchGroupTable(playerList)); - }, [matchGroupTable, playerList]); + setMatchGroupTable(buildMatchGroupTable(playerList, resultMap)); + }, [matchGroupTable, playerList, resultMap]); const scoreDiff = useMemo(() => { const [left = [], right = []] = matchGroupTable?.[matchGroupTable.length - 1] ?? []; const newScoreMap = Object.fromEntries([...left, ...right].map(e => [e?.uid, e?.score])); @@ -154,9 +154,9 @@ function BattleTable({ setImportModalVisible(false); }, []); useEffect(() => { - const roundTable = buildMatchGroupTable(playerList); + const roundTable = buildMatchGroupTable(playerList, resultMap); setMatchGroupTable(roundTable); - }, [playerList]); + }, [playerList, resultMap]); const [shareCode, setShareCode] = useState(''); const handleImportCode = useCallback(async () => { const data: Record = await fetch(`/api/battle/${eventId}?code=${shareCode}`) @@ -166,7 +166,7 @@ function BattleTable({ const { winer = '', score = 0 } = v ?? {}; resultMap.set(k, { winer, score }); }); - setMatchGroupTable(buildMatchGroupTable(playerList)); + setMatchGroupTable(buildMatchGroupTable(playerList, resultMap)); setImportModalVisible(false); }, [shareCode, playerList]); // console.debug('matchGroupTable', matchGroupTable, gameResultMap); diff --git a/src/components/ClubEventList.tsx b/src/components/ClubEventList.tsx index ba940d8..41b04ed 100644 --- a/src/components/ClubEventList.tsx +++ b/src/components/ClubEventList.tsx @@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { EventCard } from "./EventCard"; import { useRequest } from "ahooks"; import { CalendarOutlined } from "@ant-design/icons"; -import { STORE_PAGE_LIST_KEY } from "../utils/constants"; interface Props { clubId: string; @@ -44,11 +43,6 @@ export const ClubEvenList = (props: Props) => { }, [showFinishedEvents, requestEvents.data?.data]); const onPageInit = useCallback(async () => { const storePageKey = getStorageKey(props.clubId); - const keys: string[] = JSON.parse(localStorage.getItem(STORE_PAGE_LIST_KEY) ?? '[]'); - if (!keys.includes(storePageKey)) { - keys.push(storePageKey); - localStorage.setItem(STORE_PAGE_LIST_KEY, JSON.stringify(keys)); - } const prePage = Number(sessionStorage.getItem(storePageKey)) || 1; const data = await requestEvents.runAsync(props.clubId, prePage).then(res => res.data); setPage(prePage); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index be4cfe8..c66197c 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,3 +1,4 @@ export const LOGTO_RESOURCE = 'https://tt.ksr.la'; export const CLUB_SELECTOR_KEY = 'CLUB_SELECTOR'; -export const STORE_PAGE_LIST_KEY = 'events-page-keys'; \ No newline at end of file +export const STORE_PAGE_LIST_KEY = 'events-page-keys'; +export const MATCH_RESULT_MAP_KEY = 'match-result-map'; \ No newline at end of file