fix(web): stale-closure + focus trap + 去边界强转 + a11y/性能打磨 + 类型化客户端
P1-6 useStyleLearn/useKimiOauth 修 stale-closure 依赖。 P1-7 CommandPalette/Drawer 加 focus trap(新 lib/a11y/focusTrap)。 P1-8 SSE 解析与 server.ts 去 as 强转改类型守卫/运行时校验。 P2 删冗余强转、开 noUncheckedIndexedAccess、Toast 清理+稳定 key、列表 key 稳定化、 rel=noopener、useMemo 大文本、ConflictCard role=group、useInjection 加锁、 client.test 真断言。 codegen 重生成 schema.d.ts + 消费 DimensionEntry/ReviewConflictView 强类型。
This commit is contained in:
@@ -4,7 +4,9 @@ import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
@@ -25,13 +27,28 @@ const TOAST_TTL_MS = 4000;
|
||||
|
||||
export function ToastProvider({ children }: { children: ReactNode }) {
|
||||
const [items, setItems] = useState<ToastItem[]>([]);
|
||||
// 单调计数器作 id(不再用 Date.now()+random,避免极小概率碰撞且稳定可测)。
|
||||
const nextIdRef = useRef(0);
|
||||
// 在途定时器集合:卸载时统一清理,避免 setState-after-unmount 泄漏。
|
||||
const timersRef = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());
|
||||
|
||||
const show = useCallback<ShowToast>((message, kind = "info") => {
|
||||
const id = Date.now() + Math.random();
|
||||
const id = nextIdRef.current++;
|
||||
setItems((prev) => [...prev, { id, message, kind }]);
|
||||
setTimeout(() => {
|
||||
const timer = setTimeout(() => {
|
||||
timersRef.current.delete(timer);
|
||||
setItems((prev) => prev.filter((t) => t.id !== id));
|
||||
}, TOAST_TTL_MS);
|
||||
timersRef.current.add(timer);
|
||||
}, []);
|
||||
|
||||
// 卸载清理所有在途定时器。
|
||||
useEffect(() => {
|
||||
const timers = timersRef.current;
|
||||
return () => {
|
||||
timers.forEach((t) => clearTimeout(t));
|
||||
timers.clear();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const value = useMemo(() => show, [show]);
|
||||
|
||||
Reference in New Issue
Block a user