0.1.39 P1: MemberCard memo 化 + FamilyPanel useMemo 排序缓存(脱离 store 订阅)
This commit is contained in:
@@ -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'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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 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
|
||||
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 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 === headId ? -1 : 0
|
||||
const bHead = b.id === headId ? -1 : 0
|
||||
return aHead - bHead || world.ageOf(b) - world.ageOf(a)
|
||||
})
|
||||
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 && (
|
||||
|
||||
Reference in New Issue
Block a user