import type {ComponentType} from 'react' import {createExternalState} from '../../utils/createExternalState' import {Counter} from '../../utils/sundry' /** * @zh 堆栈中的一个屏幕条目。`id` 由内部计数器生成,作为 React key 保证 keep-alive 稳定。 * @en A single screen entry in the stack. `id` is generated by an internal counter and used as * the React key to keep-alive state stable across pushes/pops. */ export interface StackEntry

{ id: number Component: ComponentType

params: P } /** * @zh 堆栈状态操作接口。每个 `AppStackRouter` 实例独立持有一个 store,互不串扰。 * @en Stack state operations. Each `AppStackRouter` instance owns an isolated store. */ export interface StackStore { /** * @zh 当前堆栈(只读快照,不含根屏幕)。 * @en Current stack snapshot (read-only, excludes the root screen). */ getStack: () => StackEntry[] /** * @zh 压入一个新屏幕到栈顶。 * @en Push a new screen onto the top of the stack. */ push:

(Component: ComponentType

, params?: P) => void /** * @zh 弹出栈顶屏幕。栈空时为空操作。 * @en Pop the top screen. No-op when the stack is empty. */ pop: () => StackEntry | undefined /** * @zh 替换栈顶屏幕,不改变栈深度。 * @en Replace the top screen without changing stack depth. */ replace:

(Component: ComponentType

, params?: P) => void /** * @zh 清空整个堆栈,回到根屏幕。 * @en Clear the entire stack, returning to the root screen. */ reset: () => void /** * @zh 堆栈是否可出栈(深度 > 0)。 * @en Whether the stack can be popped (depth > 0). */ canPop: () => boolean /** * @zh 当前栈深度(不含根屏幕)。 * @en Current stack depth (excludes the root screen). */ getSize: () => number /** * @zh 在组件中订阅堆栈快照。栈内容变化时重渲染。 * @en Subscribe to the stack snapshot from a component. Re-renders when stack content changes. */ useStack: () => StackEntry[] /** * @zh 在组件中只订阅栈深度(数字)。仅当深度变化时重渲染,比 `useStack` 更细粒度。 * @en Subscribe to only the stack depth (number) from a component. Re-renders only when the depth * changes; finer-grained than `useStack`. */ useSize: () => number } /** * @zh 创建一个独立的堆栈 store。内部基于 `createExternalState`,通过 `useSyncExternalStore` 订阅。 * @en Create an isolated stack store. Built on `createExternalState`, subscribed via `useSyncExternalStore`. */ export function createStackStore(maxStackSize = Number.POSITIVE_INFINITY): StackStore { const counter = new Counter() const state = createExternalState([]) const getStack = state.get const push: StackStore['push'] = (Component, params) => { const entry: StackEntry = { id: counter.next(), Component: Component as ComponentType, params: (params ?? {}) as any, } state.set((prev: StackEntry[]) => { const next = [...prev, entry] // 超过上限时,丢弃最底层(保持栈顶不动,移除离根最近的屏幕以释放内存) if (next.length > maxStackSize) { next.splice(0, next.length - maxStackSize) } return next }) } const pop: StackStore['pop'] = () => { const prev = state.get() if (prev.length === 0) return undefined const removed = prev[prev.length - 1] state.set(prev.slice(0, -1)) return removed } const replace: StackStore['replace'] = (Component, params) => { const entry: StackEntry = { id: counter.next(), Component: Component as ComponentType, params: (params ?? {}) as any, } state.set((prev: StackEntry[]) => { if (prev.length === 0) { // 空栈时 replace 退化为 push,保证语义合理 return [entry] } const next = prev.slice(0, -1) next.push(entry) return next }) } const reset = () => { state.set([]) } const canPop = () => state.get().length > 0 const getSize = () => state.get().length const useStack = () => state.useState()[0] // 细粒度订阅:仅深度变化时重渲染(数字比较,内容变但深度不变则不触发) const useSize = () => state.useSelector((stack) => stack.length) return { getStack, push, pop, replace, reset, canPop, getSize, useStack, useSize, } }