0.1.40 P1-2: UI渲染效率memo化4处 (NpcRow/NewsList/groupedEntries/specialtyId)

This commit is contained in:
2026-09-13 14:12:44 +08:00
parent dc970ff13b
commit 2c7596cec7
3 changed files with 129 additions and 112 deletions
+3 -2
View File
@@ -1,5 +1,5 @@
import { useGameStore } from '../store'
import { useMemo, useState } from 'react'
import { useMemo, useState, memo } from 'react'
const CAT_NAME: Record<string, string> = {
birth: '诞庆',
@@ -61,6 +61,7 @@ export default function ChroniclePanel() {
if (!world) return null
const battles = world.state.battles.slice().reverse()
const battle = battles.find((b) => b.id === battleId)
const groupedEntries = useMemo(() => grouped(entries), [entries])
return (
<div>
@@ -99,7 +100,7 @@ export default function ChroniclePanel() {
))}
</div>
<div className="chronicle-list">
{grouped(entries).map((g) => (
{groupedEntries.map((g) => (
<div key={g.year}>
<div className="ch-yearbar">{g.year} <span className="dim2">{g.items.length} · {statOf(g)}</span></div>
{g.items.map((e) => (
+33 -27
View File
@@ -1,35 +1,21 @@
import { useGameStore } from '../store'
import { npcById } from '../../game/data/npcs'
import { giftNpc, makePeace, marryNpcFamily, calcGiftGain, GIFT_TIERS } from '../../game/engine/runtime/Systems/diplomacy'
import { useMemo } from 'react'
import { useMemo, memo } from 'react'
import { MAJOR_NAMES } from '../../game/data/realms'
import type { World } from '../../game/engine/runtime/World'
import type { NpcFamilyState } from '../../game/types/domain'
const REL_TYPE = (r: number) =>
r >= 80 ? ['死生之交', 'good'] : r >= 50 ? ['通好之势', 'good'] : r >= 25 ? ['友善', 'warn'] : r >= 10 ? ['温和', 'warn'] : r > -10 ? ['中立', ''] : r > -30 ? ['嫌隙', ''] : r > -50 ? ['敌对', 'bad'] : ['仇雠', 'bad']
export default function DiplomacyPanel() {
const world = useGameStore((s) => s.world)
const bump = useGameStore((s) => s.bump)
const revision = useGameStore((s) => s.revision)
void revision
if (!world) return null
const w = world
const npcs = Object.values(w.state.npcFamilies)
const sorted = useMemo(() => npcs, [npcs])
return (
<div>
<div className="help-text">
</div>
{sorted.map((npc) => {
const NpcRow = memo(function NpcRow({ npc, world, bump }: { npc: NpcFamilyState; world: World; bump: () => void }) {
const def = npcById(npc.id)
if (!def) return null
const [relLabel, relCls] = REL_TYPE(npc.relation)
const married = npc.allied
return (
<div key={npc.id} className="npc-row">
<div className="npc-row">
<div className="npc-name">
<div style={{ fontSize: '1.15rem' }}>{npc.name}</div>
<div className="dim2" style={{ fontSize: '0.78rem' }}>{def.style} · {npc.region}</div>
@@ -55,9 +41,9 @@ export default function DiplomacyPanel() {
<button
key={t}
className="btn btn-sm"
disabled={w.state.family.stones < t}
disabled={world.state.family.stones < t}
title={`关系 +${calcGiftGain(t)}`}
onClick={() => { giftNpc(w, npc.id, t); bump() }}
onClick={() => { giftNpc(world, npc.id, t); bump() }}
>
{t}(+{calcGiftGain(t)})
</button>
@@ -67,7 +53,7 @@ export default function DiplomacyPanel() {
className="btn btn-sm"
disabled={npc.relation < 25 || married}
title={npc.relation < 25 ? `需关系≥25(当前${npc.relation}` : married ? '一姓已结亲' : '关系≥25方可联姻'}
onClick={() => { marryNpcFamily(w, npc.id); bump() }}
onClick={() => { marryNpcFamily(world, npc.id); bump() }}
>
{married ? '已联姻' : '联姻'}
</button>
@@ -76,7 +62,7 @@ export default function DiplomacyPanel() {
title="关系-20,可能招致劫掠;每年一次"
onClick={() => {
if (window.confirm(`${npc.name}寻衅?关系将下降 20,可能招致劫掠。`)) {
w.tauntNpc(npc.id)
world.tauntNpc(npc.id)
bump()
}
}}
@@ -86,9 +72,9 @@ export default function DiplomacyPanel() {
{npc.relation < -30 && (
<button
className="btn btn-sm"
disabled={w.state.family.stones < 200}
title={w.state.family.stones < 200 ? '需灵石 200' : '与对方结下和约'}
onClick={() => { makePeace(w, npc.id); bump() }}
disabled={world.state.family.stones < 200}
title={world.state.family.stones < 200 ? '需灵石 200' : '与对方结下和约'}
onClick={() => { makePeace(world, npc.id); bump() }}
>
</button>
@@ -96,7 +82,27 @@ export default function DiplomacyPanel() {
</div>
</div>
)
})}
})
export default function DiplomacyPanel() {
const world = useGameStore((s) => s.world)
const bump = useGameStore((s) => s.bump)
const revision = useGameStore((s) => s.revision)
void revision
if (!world) return null
const w = world
const npcs = Object.values(w.state.npcFamilies)
const sorted = useMemo(() => npcs, [npcs])
return (
<div>
<div className="help-text">
</div>
{sorted.map((npc) => (
<NpcRow key={npc.id} npc={npc} world={w} bump={bump} />
))}
<div className="dim2" style={{ marginTop: 12 }}>
</div>
+45 -35
View File
@@ -1,5 +1,5 @@
import { useGameStore } from '../store'
import { useMemo } from 'react'
import { useMemo, memo } from 'react'
import { npcById } from '../../game/data/npcs'
import { MAJOR_NAMES } from '../../game/data/realms'
import type { WorldSimState } from '../../game/engine/sim/worldsim-data'
@@ -7,10 +7,53 @@ import { POOL_BASE, WORLDSIM, ERA_CONF } from '../../game/engine/sim/worldsim-da
import { combatPowerOf } from '../../game/engine/runtime/Systems/combat'
import { sellItem, buyItem, marketPrice } from '../../game/engine/sim/Market'
import { useState } from 'react'
import type { World } from '../../game/engine/runtime/World'
const RES_LABEL: Record<string, string> = { lingcao: '灵草', lingkuang: '灵矿', beastcore: '兽核' }
const REALM_IDX = ['mortal', 'qi', 'foundation', 'core', 'nascent', 'spirit'] as const
/** 0.1.40 memo 化天下快讯——120 条 newsFeed 不随主面板其他状态变动重渲染 */
const NewsList = memo(function NewsList({ news, world, bump }: { news: { year: number; month: number; src: string; text: string; itemId?: string; kind?: string }[]; world: World; bump: () => void }) {
return (
<>
{news.length === 0 && <div className="dim2"></div>}
{news.map((row, i) => {
const canBuy = row.kind === 'quote' && row.itemId && world.state.family.stones >= marketPrice(world, row.itemId) * 5
const canSell = row.kind === 'quote' && row.itemId && (world.state.family.inventory[row.itemId] ?? 0) >= 5
const isCrisis = row.kind === 'calamity'
return (
<div key={i} className="ch-item" style={{ padding: '5px 8px' }}>
<span className="ch-month">{row.year}{row.month}</span>
<span>{row.text}</span>
{isCrisis && world.state.family.stones >= 50 && (
<span style={{ marginLeft: 8 }}>
<button
className="btn btn-sm"
title="引粥施药,换两家之谊"
onClick={() => {
world.reliefCalamity(50)
bump()
}}
></button>
</span>
)}
{canBuy && (
<span style={{ marginLeft: 8 }}>
<button className="btn btn-sm" title="行情见底,出手吃进(买5)" onClick={() => { buyItem(world, row.itemId!, 5); bump() }}>5</button>
</span>
)}
{canSell && !canBuy && (
<span style={{ marginLeft: 8 }}>
<button className="btn btn-sm" title="行情见顶,集市抛售(卖5)" onClick={() => { sellItem(world, row.itemId!, 5); bump() }}>5</button>
</span>
)}
</div>
)
})}
</>
)
})
export default function WorldPanel() {
const world = useGameStore((s) => s.world)
const bump = useGameStore((s) => s.bump)
@@ -123,40 +166,7 @@ export default function WorldPanel() {
<div>
<h4 className="dim" style={{ margin: '4px 0 6px' }}></h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, maxHeight: 380, overflowY: 'auto' }}>
{news.length === 0 && <div className="dim2"></div>}
{news.map((row, i) => {
const canBuy = row.kind === 'quote' && row.itemId && w.state.family.stones >= marketPrice(w, row.itemId) * 5
const canSell = row.kind === 'quote' && row.itemId && (w.state.family.inventory[row.itemId] ?? 0) >= 5
const isCrisis = row.kind === 'calamity'
return (
<div key={i} className="ch-item" style={{ padding: '5px 8px' }}>
<span className="ch-month">{row.year}{row.month}</span>
<span>{row.text}</span>
{isCrisis && w.state.family.stones >= 50 && (
<span style={{ marginLeft: 8 }}>
<button
className="btn btn-sm"
title="引粥施药,换两家之谊"
onClick={() => {
w.reliefCalamity(50)
bump()
}}
></button>
</span>
)}
{canBuy && (
<span style={{ marginLeft: 8 }}>
<button className="btn btn-sm" title="行情见底,出手吃进(买5)" onClick={() => { buyItem(w, row.itemId!, 5); bump() }}>5</button>
</span>
)}
{canSell && !canBuy && (
<span style={{ marginLeft: 8 }}>
<button className="btn btn-sm" title="行情见顶,集市抛售(卖5)" onClick={() => { sellItem(w, row.itemId!, 5); bump() }}>5</button>
</span>
)}
</div>
)
})}
<NewsList news={news} world={w} bump={bump} />
</div>
</div>
<div>