0.1.39 P1: MemberCard memo 化 + FamilyPanel useMemo 排序缓存(脱离 store 订阅)

This commit is contained in:
2026-09-13 13:04:10 +08:00
parent fdc9d750f2
commit 818c6025f4
2 changed files with 47 additions and 34 deletions
+27 -19
View File
@@ -1,4 +1,4 @@
import { useGameStore } from '../store'
import { memo } from 'react'
import { Character } from '../../game/types/domain'
import { describeRealm } from '../../game/data/realms'
import { describeRoots } from '../../game/data/elements'
@@ -13,18 +13,34 @@ const STATE_LABEL: Record<string, { label: string; cls: string }> = {
dead: { label: '已殁', cls: 'bad' }
}
export function MemberCard({ c }: { c: Character }) {
const world = useGameStore((s) => s.world)
const setSelected = useGameStore((s) => s.setSelected)
if (!world) return null
const C_STATE: Record<string, string> = {
idle: '',
meditation: 'good',
expedition: 'warn',
wounded: 'bad',
dead: 'bad'
}
/**
* 0.1.39 渲染提效:MemberCard 改为 memo 纯展示组件——
* 不再从 store 获取 world(之前每次 revision bump 全部卡片重渲染)。
* 所需外部数据(surname/isHead/age)经 props 传入,只在 c 或依赖 props 变化时重渲染。
*/
export interface MemberCardProps {
c: Character
surname: string
isHead: boolean
age: number
onSelect: (id: string) => void
}
export const MemberCard = memo(function MemberCard({ c, surname, isHead, age, onSelect }: MemberCardProps) {
const dead = !c.alive
const isHead = world.state.family.headId === c.id
const state = STATE_LABEL[dead ? 'dead' : c.state]
const blocked = !dead && c.realmProgress >= 100 && c.realm.major !== 'spirit'
return (
<div className={`member-card ${dead ? 'dead' : ''} ${blocked ? 'blocked' : ''}`} onClick={() => setSelected(c.id)}>
<div className={`member-card ${dead ? 'dead' : ''} ${blocked ? 'blocked' : ''}`} onClick={() => onSelect(c.id)}>
<div className="mc-head">
<div className="m-avatar">{world.state.family.surname}</div>
<div className="m-avatar">{surname}</div>
<div style={{ flex: 1 }}>
<div className="member-name">
<span>{c.name}</span>
@@ -34,7 +50,7 @@ export function MemberCard({ c }: { c: Character }) {
<div className="member-realm">
{dead
? `殁于${c.deathYear}年 · ${c.deathCause ?? '寿终'}`
: `${describeRealm(c.realm)} · ${world.ageOf(c)}岁 · 修为${Math.floor(c.realmProgress)}%`}
: `${describeRealm(c.realm)} · ${age}岁 · 修为${Math.floor(c.realmProgress)}%`}
</div>
</div>
</div>
@@ -55,12 +71,4 @@ export function MemberCard({ c }: { c: Character }) {
)}
</div>
)
}
const C_STATE: Record<string, string> = {
idle: '',
meditation: 'good',
expedition: 'warn',
wounded: 'bad',
dead: 'bad'
}
})
+15 -10
View File
@@ -1,4 +1,5 @@
import { useGameStore } from '../store'
import { useMemo } from 'react'
import { MemberCard } from '../components/MemberCard'
import { MemberModal } from '../components/MemberModal'
import { Character } from '../../game/types/domain'
@@ -11,25 +12,29 @@ export default function FamilyPanel() {
void revision
if (!world) return null
const s = world.state
const surname = s.family.surname
const headId = s.family.headId
const { alive, dead, sorted, marriageReady } = useMemo(() => {
const alive = Object.values(s.members).filter((c) => c.alive)
const dead = Object.values(s.members).filter((c) => !c.alive)
const sorted = [...alive].sort((a, b) => {
const aHead = a.id === s.family.headId ? -1 : 0
const bHead = b.id === s.family.headId ? -1 : 0
const aHead = a.id === headId ? -1 : 0
const bHead = b.id === headId ? -1 : 0
return aHead - bHead || world.ageOf(b) - world.ageOf(a)
})
const selected = selectedMemberId ? s.members[selectedMemberId] as Character | undefined : undefined
const marriageReady = (() => {
if (!world) return []
return alive.filter(
const marriageReady = alive.filter(
(c) =>
world.ageOf(c) >= 16 &&
world.ageOf(c) <= 46 &&
c.state !== 'expedition' &&
(!c.spouseId || world.isWidowed(c))
)
})()
return { alive, dead, sorted, marriageReady }
}, [s.members, headId, world])
const selected = selectedMemberId ? s.members[selectedMemberId] as Character | undefined : undefined
// 0.1.39:稳定回调引用——避免每次渲染新建函数导致 MemberCard memo 失效
const onSelect = setSelected
return (
<div>
@@ -78,10 +83,10 @@ export default function FamilyPanel() {
)}
<div className="member-grid">
{sorted.map((c) => (
<MemberCard key={c.id} c={c} />
<MemberCard key={c.id} c={c} surname={surname} isHead={c.id === headId} age={world.ageOf(c)} onSelect={onSelect} />
))}
{dead.map((c) => (
<MemberCard key={c.id} c={c} />
<MemberCard key={c.id} c={c} surname={surname} isHead={false} age={world.ageOf(c)} onSelect={onSelect} />
))}
</div>
{alive.length === 0 && (