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 { Character } from '../../game/types/domain'
|
||||||
import { describeRealm } from '../../game/data/realms'
|
import { describeRealm } from '../../game/data/realms'
|
||||||
import { describeRoots } from '../../game/data/elements'
|
import { describeRoots } from '../../game/data/elements'
|
||||||
@@ -13,18 +13,34 @@ const STATE_LABEL: Record<string, { label: string; cls: string }> = {
|
|||||||
dead: { label: '已殁', cls: 'bad' }
|
dead: { label: '已殁', cls: 'bad' }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MemberCard({ c }: { c: Character }) {
|
const C_STATE: Record<string, string> = {
|
||||||
const world = useGameStore((s) => s.world)
|
idle: '',
|
||||||
const setSelected = useGameStore((s) => s.setSelected)
|
meditation: 'good',
|
||||||
if (!world) return null
|
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 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'
|
const blocked = !dead && c.realmProgress >= 100 && c.realm.major !== 'spirit'
|
||||||
return (
|
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="mc-head">
|
||||||
<div className="m-avatar">{world.state.family.surname}</div>
|
<div className="m-avatar">{surname}</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<div className="member-name">
|
<div className="member-name">
|
||||||
<span>{c.name}</span>
|
<span>{c.name}</span>
|
||||||
@@ -34,7 +50,7 @@ export function MemberCard({ c }: { c: Character }) {
|
|||||||
<div className="member-realm">
|
<div className="member-realm">
|
||||||
{dead
|
{dead
|
||||||
? `殁于${c.deathYear}年 · ${c.deathCause ?? '寿终'}`
|
? `殁于${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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,12 +71,4 @@ export function MemberCard({ c }: { c: Character }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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 { useGameStore } from '../store'
|
||||||
|
import { useMemo } from 'react'
|
||||||
import { MemberCard } from '../components/MemberCard'
|
import { MemberCard } from '../components/MemberCard'
|
||||||
import { MemberModal } from '../components/MemberModal'
|
import { MemberModal } from '../components/MemberModal'
|
||||||
import { Character } from '../../game/types/domain'
|
import { Character } from '../../game/types/domain'
|
||||||
@@ -11,25 +12,29 @@ export default function FamilyPanel() {
|
|||||||
void revision
|
void revision
|
||||||
if (!world) return null
|
if (!world) return null
|
||||||
const s = world.state
|
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 alive = Object.values(s.members).filter((c) => c.alive)
|
||||||
const dead = 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 sorted = [...alive].sort((a, b) => {
|
||||||
const aHead = a.id === s.family.headId ? -1 : 0
|
const aHead = a.id === headId ? -1 : 0
|
||||||
const bHead = b.id === s.family.headId ? -1 : 0
|
const bHead = b.id === headId ? -1 : 0
|
||||||
return aHead - bHead || world.ageOf(b) - world.ageOf(a)
|
return aHead - bHead || world.ageOf(b) - world.ageOf(a)
|
||||||
})
|
})
|
||||||
const selected = selectedMemberId ? s.members[selectedMemberId] as Character | undefined : undefined
|
const marriageReady = alive.filter(
|
||||||
|
|
||||||
const marriageReady = (() => {
|
|
||||||
if (!world) return []
|
|
||||||
return alive.filter(
|
|
||||||
(c) =>
|
(c) =>
|
||||||
world.ageOf(c) >= 16 &&
|
world.ageOf(c) >= 16 &&
|
||||||
world.ageOf(c) <= 46 &&
|
world.ageOf(c) <= 46 &&
|
||||||
c.state !== 'expedition' &&
|
c.state !== 'expedition' &&
|
||||||
(!c.spouseId || world.isWidowed(c))
|
(!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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -78,10 +83,10 @@ export default function FamilyPanel() {
|
|||||||
)}
|
)}
|
||||||
<div className="member-grid">
|
<div className="member-grid">
|
||||||
{sorted.map((c) => (
|
{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) => (
|
{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>
|
</div>
|
||||||
{alive.length === 0 && (
|
{alive.length === 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user