Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/pages/batchupdate/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
} from "@App/app/service/service_worker/types";
import { dayFormat } from "@App/pkg/utils/day_format";
import { IconSync } from "@arco-design/web-react/icon";
import { useAppContext } from "../store/AppContext";
import { SCRIPT_STATUS_ENABLE } from "@App/app/repo/scripts";
import { subscribeMessage } from "@App/pages/store/global";

const CollapseItem = Collapse.Item;
const { GridItem } = Grid;
Expand All @@ -29,8 +29,6 @@ const { Text } = Typography;
const pageExecute: Record<string, (data: any) => void> = {};

function App() {
const { subscribeMessage } = useAppContext();

const AUTO_CLOSE_PAGE = 8; // after 8s, auto close
const getUrlParam = (key: string): string => {
return (location.search?.includes(`${key}=`) ? new URLSearchParams(location.search).get(`${key}`) : "") || "";
Expand Down
18 changes: 8 additions & 10 deletions src/pages/options/routes/ScriptList/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { loadScriptFavicons } from "@App/pages/store/favicons";
import { parseTags } from "@App/app/repo/metadata";
import { getCombinedMeta } from "@App/app/service/service_worker/utils";
import { cacheInstance } from "@App/app/cache";
import { useAppContext } from "@App/pages/store/AppContext";

// 组件与工具
import { type SearchFilterRequest } from "./SearchFilter";
Expand All @@ -39,6 +38,8 @@ import type {
TSortedScript,
} from "@App/app/service/queue";
import { type useTranslation } from "react-i18next";
import { subscribeMessage } from "@App/pages/store/global";
import { HookManager } from "@App/pkg/utils/hookManager";

export type TFilterKey = null | string | number;

Expand All @@ -64,7 +65,6 @@ export type TSelectFilterKeys = keyof TSelectFilter;
export function useScriptDataManagement() {
const [scriptList, setScriptList] = useState<ScriptLoading[]>([]);
const [loadingList, setLoadingList] = useState<boolean>(true);
const { subscribeMessage } = useAppContext();

// 初始化列表与 Favicon 加载
useEffect(() => {
Expand Down Expand Up @@ -188,18 +188,16 @@ export function useScriptDataManagement() {
},
} as const;

const unhooks = [
const hookMgr = new HookManager();
hookMgr.append(
subscribeMessage<TScriptRunStatus>("scriptRunStatus", pageApi.scriptRunStatus),
subscribeMessage<TInstallScript>("installScript", pageApi.installScript),
subscribeMessage<TDeleteScript[]>("deleteScripts", pageApi.deleteScripts),
subscribeMessage<TEnableScript[]>("enableScripts", pageApi.enableScripts),
subscribeMessage<TSortedScript[]>("sortedScripts", pageApi.sortedScripts),
];
return () => {
for (const unhook of unhooks) unhook();
unhooks.length = 0;
};
}, [subscribeMessage]);
subscribeMessage<TSortedScript[]>("sortedScripts", pageApi.sortedScripts)
);
return hookMgr.unhook;
}, []);

return { scriptList, setScriptList, loadingList };
}
Expand Down
17 changes: 7 additions & 10 deletions src/pages/options/routes/Setting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@ import { blackListSelfCheck } from "@App/pkg/utils/match";
import { obtainBlackList } from "@App/pkg/utils/utils";
import CustomTrans from "@App/pages/components/CustomTrans";
import { useSystemConfig } from "./utils";
import { useAppContext } from "@App/pages/store/AppContext";
import { subscribeMessage } from "@App/pages/store/global";
import { SystemConfigChange, type SystemConfigKey } from "@App/pkg/config/config";
import { type TKeyValue } from "@Packages/message/message_queue";
import { useEffect, useMemo } from "react";
import { systemConfig } from "@App/pages/store/global";
import { initRegularUpdateCheck } from "@App/app/service/service_worker/regular_updatecheck";
import { HookManager } from "@App/pkg/utils/hookManager";

function Setting() {
const { subscribeMessage } = useAppContext();

const [editorConfig, setEditorConfig, submitEditorConfig] = useSystemConfig("editor_config");
const [cloudSync, setCloudSync, submitCloudSync] = useSystemConfig("cloud_sync");
const [language, setLanguage, submitLanguage] = useSystemConfig("language");
Expand Down Expand Up @@ -84,7 +83,8 @@ function Setting() {
script_menu_display_type: setScriptMenuDisplayType,
editor_type_definition: setEditorTypeDefinition,
} as const;
const unhooks = [
const hookMgr = new HookManager();
hookMgr.append(
subscribeMessage<TKeyValue<SystemConfigKey>>(
SystemConfigChange,
({ key, value: _value }: TKeyValue<SystemConfigKey>) => {
Expand All @@ -102,12 +102,9 @@ function Setting() {
});
}
}
),
];
return () => {
for (const unhook of unhooks) unhook();
unhooks.length = 0;
};
)
);
return hookMgr.unhook;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Expand Down
32 changes: 14 additions & 18 deletions src/pages/popup/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import type { ScriptMenu, TPopupScript } from "@App/app/service/service_worker/t
import { systemConfig } from "@App/pages/store/global";
import { isChineseUser, localePath } from "@App/locales/locales";
import { getCurrentTab } from "@App/pkg/utils/utils";
import { useAppContext } from "../store/AppContext";
import { subscribeMessage } from "@App/pages/store/global";
import type { TDeleteScript, TEnableScript, TScriptRunStatus } from "@App/app/service/queue";
import { SCRIPT_RUN_STATUS_RUNNING } from "@App/app/repo/scripts";
import { HookManager } from "@App/pkg/utils/hookManager";

const CollapseItem = Collapse.Item;

Expand Down Expand Up @@ -138,17 +139,16 @@ function App() {
return url?.hostname ?? "";
}, [currentUrl]);

const { subscribeMessage } = useAppContext();
useEffect(() => {
let isMounted = true;
const hookMgr = new HookManager();

const updateScriptList = (update: TUpdateEntryFn, options?: TUpdateListOption) => {
// 当 启用/禁用/菜单改变 时,如有必要则更新 list 参考
setScriptList((prev) => updateList(prev, update, options));
setBackScriptList((prev) => updateList(prev, update, options));
};

const unhooks = [
hookMgr.append(
// 订阅脚本啟用状态变更(enableScripts),即时更新对应项目的 enable。
subscribeMessage<TEnableScript[]>("enableScripts", (data) => {
updateScriptList((item) => {
Expand Down Expand Up @@ -196,7 +196,7 @@ function App() {
});
if (!url) return;
popupClient.getPopupData({ url, tabId }).then((resp) => {
if (!isMounted) return;
if (!hookMgr.isMounted) return;

// 响应健全性检查:必须包含 scriptList,否则忽略此次更新
if (!resp || !resp.scriptList) {
Expand Down Expand Up @@ -225,16 +225,16 @@ function App() {
);
});
}
}),
];
})
);

const onCurrentUrlUpdated = (url: string, tabId: number) => {
pageTabIdRef.current = tabId;
checkScriptEnableAndUpdate();
popupClient
.getPopupData({ url, tabId })
.then((resp) => {
if (!isMounted) return;
if (!hookMgr.isMounted) return;

// 确保响应有效
if (!resp || !resp.scriptList) {
Expand All @@ -255,14 +255,14 @@ function App() {
})
.catch((error) => {
console.error("Failed to get popup data:", error);
if (!isMounted) return;
if (!hookMgr.isMounted) return;
// 设为安全预设,避免 UI 因错误状态而崩溃
setScriptList([]);
setBackScriptList([]);
setIsBlacklist(false);
})
.finally(() => {
if (!isMounted) return;
if (!hookMgr.isMounted) return;
setLoading(false);
});
};
Expand All @@ -272,15 +272,15 @@ function App() {
systemConfig.getEnableScript(),
systemConfig.getCheckUpdate(),
]);
if (!isMounted) return;
if (!hookMgr.isMounted) return;
setIsEnableScript(isEnableScript);
setCheckUpdate(checkUpdate);
};
const queryTabInfo = async () => {
// 仅在挂载时读取一次页签信息;不绑定 currentUrl 以避免重复查询
try {
const tab = await getCurrentTab();
if (!isMounted || !tab) return;
if (!hookMgr.isMounted || !tab) return;
const newUrl = tab.url || "";
setCurrentUrl((prev) => {
if (newUrl !== prev) {
Expand All @@ -296,12 +296,8 @@ function App() {

checkScriptEnableAndUpdate();
queryTabInfo();
return () => {
isMounted = false;
for (const unhook of unhooks) unhook();
unhooks.length = 0;
};
}, [subscribeMessage]);
return hookMgr.unhook;
}, []);

const { handleEnableScriptChange, handleSettingsClick, handleNotificationClick } = {
handleEnableScriptChange: (val: boolean) => {
Expand Down
22 changes: 6 additions & 16 deletions src/pages/store/AppContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useState, createContext, type ReactNode, useEffect, useContext } from "react";
import { messageQueue } from "./global";
import { HookManager } from "@App/pkg/utils/hookManager";
import { subscribeMessage } from "@App/pages/store/global";

export const fnPlaceHolder = {
setEditorTheme: null,
Expand All @@ -9,7 +11,6 @@ export type ThemeParam = { theme: "auto" | "light" | "dark" };
export interface AppContextType {
colorThemeState: "auto" | "light" | "dark";
updateColorTheme: (theme: "auto" | "light" | "dark") => void;
subscribeMessage: <T>(topic: string, handler: (msg: T) => void) => () => void;
// 指引模式
setGuideMode: (mode: boolean) => void;
guideMode: boolean;
Expand Down Expand Up @@ -66,15 +67,6 @@ export const AppProvider: React.FC<AppProviderProps> = ({ children }) => {
});
const [guideMode, setGuideMode] = useState(false);

const subscribeMessage = <T,>(topic: string, handler: (msg: T) => void) => {
return messageQueue.subscribe<T & { myMessage?: T }>(topic, (data) => {
const message = data?.myMessage || data;
if (typeof message === "object") {
handler(message as T);
}
});
};

useEffect(() => {
const pageApi = {
onColorThemeUpdated({ theme }: ThemeParam) {
Expand All @@ -83,11 +75,10 @@ export const AppProvider: React.FC<AppProviderProps> = ({ children }) => {
},
} as const;

const unhooks = [subscribeMessage<ThemeParam>("onColorThemeUpdated", pageApi.onColorThemeUpdated)];
return () => {
for (const unhook of unhooks) unhook();
unhooks.length = 0;
};
const hookMgr = new HookManager();
hookMgr.append(subscribeMessage<ThemeParam>("onColorThemeUpdated", pageApi.onColorThemeUpdated));

return hookMgr.unhook;
Comment on lines +78 to +81
Copy link
Member

@CodFrm CodFrm Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

就一个subscribe,为了统一的话,问题不大

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 统一写法
  2. 主要是用来避开 closure GC 问题

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 主要是用来避开 closure GC 问题

实例化一个HookManager可比closure GC开销大多了

}, []);

const updateColorTheme = (theme: "auto" | "light" | "dark") => {
Expand All @@ -100,7 +91,6 @@ export const AppProvider: React.FC<AppProviderProps> = ({ children }) => {
value={{
colorThemeState,
updateColorTheme,
subscribeMessage,
setGuideMode,
guideMode,
}}
Expand Down
9 changes: 9 additions & 0 deletions src/pages/store/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,13 @@ export const globalCache = new Map<string, any>();
export const message = new ExtensionMessage();
export const systemClient = new SystemClient(message);

export const subscribeMessage = <T extends object>(topic: string, handler: (msg: T) => void) => {
return messageQueue.subscribe<T & { myMessage?: T }>(topic, (data) => {
const payload = data?.myMessage || data;
if (typeof payload === "object") {
handler(payload as T);
}
});
};

initLocales(systemConfig);
18 changes: 18 additions & 0 deletions src/pkg/utils/hookManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export class HookManager {
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

文件名 hookManger.ts 似乎是 HookManager 的拼写误差(少了 a),且与类名不一致,后续检索/导入时容易踩坑。建议尽早统一为 hookManager.ts(或按 utils 目录既有命名风格改为 hook_manager.ts),并同步更新所有 import 路径。

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

呀! 打錯字了!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public isMounted: boolean = true;
// 存储卸载时调用的钩子函数;unhook 后置为 null
private unhooks: (() => void)[] | null = [];
public append(...fns: ((...args: any) => any)[]) {
// 已经 unhook 的情况下保持幂等,直接忽略追加
this.unhooks?.push(...fns);
}
public readonly unhook = () => {
// 已经 unhook 过则保持幂等
this.isMounted = false;
if (this.unhooks !== null) {
for (const unhook of this.unhooks!) unhook();
this.unhooks!.length = 0;
this.unhooks = null;
}
};
}
1 change: 1 addition & 0 deletions tests/pages/options/MainLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ vi.mock("@App/pages/store/global", () => ({
error: vi.fn(),
warning: vi.fn(),
},
subscribeMessage: () => vi.fn(),
}));

vi.mock("@App/pkg/utils/utils", () => ({
Expand Down
1 change: 1 addition & 0 deletions tests/pages/popup/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ vi.mock("@App/pages/store/global", () => ({
error: vi.fn(),
warning: vi.fn(),
},
subscribeMessage: () => vi.fn(),
}));

vi.mock("@App/pkg/utils/utils", () => ({
Expand Down
Loading