리팩토링 2단계: 성능 최적화 (메모화)
- SymbolCard / CharacterCard를 React.memo로 감쌈 (심볼 그리드에서 형제 카드 변경 시 불필요 리렌더 방지) - Liberation의 computeCompletionDate() 호출을 useMemo로 감쌈 (520회 루프가 매 렌더마다 돌던 것을 관련 state 변경 시만 실행) - Symbol.jsx의 로컬 formatMesoKorean 중복 정의 제거 (utils import) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c6ac3366cc
commit
f6f1e79b82
2 changed files with 13 additions and 18 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect, useLayoutEffect } from 'react'
|
||||
import { useState, useEffect, useLayoutEffect, useMemo } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { api } from '../../../api/client'
|
||||
|
|
@ -261,7 +261,12 @@ export default function Liberation() {
|
|||
return { start: ws, end: ws.add(6, 'day') }
|
||||
}
|
||||
|
||||
const completionDate = computeCompletionDate()
|
||||
const completionDate = useMemo(
|
||||
() => computeCompletionDate(),
|
||||
// 의도적으로 state 전체 + calcMode 만 의존. 내부 함수는 클로저 안의 값만 읽음
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[state, calcMode, alreadyDone, remaining, weeklyEarn, doneEarn, monthlyEarn, monthlyDoneThisMonth],
|
||||
)
|
||||
const isDone = completionDate !== null
|
||||
|
||||
const [resetOpen, setResetOpen] = useState(false)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect, useLayoutEffect, useMemo } from 'react'
|
||||
import { memo, useState, useEffect, useLayoutEffect, useMemo } from 'react'
|
||||
import { useQuery, useQueries, useMutation } from '@tanstack/react-query'
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
|
|
@ -9,6 +9,7 @@ import Select from '../../../components/common/Select'
|
|||
import Tooltip from '../../../components/common/Tooltip'
|
||||
import CharacterSuggestDropdown from '../../../components/common/CharacterSuggestDropdown'
|
||||
import { useSymbolStore } from '../store'
|
||||
import { formatMesoKorean } from '../../../utils/formatting'
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
|
|
@ -44,20 +45,9 @@ function computeCompletion({ remainingSymbols, daily, weeklyPerWeek, extra, dail
|
|||
return { days: null, date: null }
|
||||
}
|
||||
|
||||
function formatMesoKorean(n) {
|
||||
const v = Number(n) || 0
|
||||
if (v <= 0) return '0'
|
||||
const eok = Math.floor(v / 100_000_000)
|
||||
const man = Math.floor((v % 100_000_000) / 10_000)
|
||||
const parts = []
|
||||
if (eok) parts.push(`${eok.toLocaleString()}억`)
|
||||
if (man) parts.push(`${man.toLocaleString()}만`)
|
||||
return parts.length ? parts.join(' ') : v.toLocaleString()
|
||||
}
|
||||
|
||||
const TYPE_ORDER = ['아케인', '어센틱', '그랜드 어센틱']
|
||||
|
||||
function CharacterCard({ char, active, onSelect, onRemove }) {
|
||||
const CharacterCard = memo(function CharacterCard({ char, active, onSelect, onRemove }) {
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
|
|
@ -109,9 +99,9 @@ function CharacterCard({ char, active, onSelect, onRemove }) {
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
function SymbolCard({ symbol, equipped, charId }) {
|
||||
const SymbolCard = memo(function SymbolCard({ symbol, equipped, charId }) {
|
||||
const progress = useSymbolStore((s) => s.progress?.[charId]?.[symbol.id])
|
||||
const updateSymbol = useSymbolStore((s) => s.updateSymbol)
|
||||
|
||||
|
|
@ -367,7 +357,7 @@ function SymbolCard({ symbol, equipped, charId }) {
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default function Symbol() {
|
||||
const { setFullscreen } = useLayout()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue