Skip to content

Commit 9c67e4a

Browse files
authored
🐛 处理popup页面的问题 (#1100)
* 处理popup问题 * 调整缓存逻辑 * 通过单元测试
1 parent 379dc85 commit 9c67e4a

File tree

4 files changed

+61
-75
lines changed

4 files changed

+61
-75
lines changed

src/app/repo/repo.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,33 @@ export abstract class Repo<T> {
204204
});
205205
}
206206

207+
public getRecord(keys: string[]): Promise<Partial<Record<string, T>>> {
208+
keys = keys.map((key) => this.joinKey(key));
209+
if (this.useCache) {
210+
return loadCache().then((cache) => {
211+
const record: Partial<Record<string, T>> = {};
212+
for (const key of keys) {
213+
if (cache[key]) {
214+
record[key] = Object.assign({}, cache[key]);
215+
} else {
216+
record[key] = cache[key];
217+
}
218+
}
219+
return record;
220+
});
221+
}
222+
return new Promise((resolve) => {
223+
chrome.storage.local.get(keys, (result) => {
224+
const lastError = chrome.runtime.lastError;
225+
if (lastError) {
226+
console.error("chrome.runtime.lastError in chrome.storage.local.get:", lastError);
227+
// 无视storage API错误,继续执行
228+
}
229+
resolve(result as Partial<Record<string, T>>);
230+
});
231+
});
232+
}
233+
207234
private filter(data: { [key: string]: T }, filters?: (key: string, value: T) => boolean): T[] {
208235
const ret: T[] = [];
209236
for (const key in data) {

src/pages/components/ScriptMenuList/index.tsx

Lines changed: 24 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import type {
3333
ScriptMenuItemOption,
3434
} from "@App/app/service/service_worker/types";
3535
import { popupClient, runtimeClient, scriptClient } from "@App/pages/store/features/script";
36-
import { i18nLang, i18nName } from "@App/locales/locales";
36+
import { i18nName } from "@App/locales/locales";
3737

3838
// 用于读取 metadata
3939
const scriptDAO = new ScriptDAO();
@@ -329,8 +329,6 @@ type ScriptMenuEntry = ScriptMenu & {
329329
metadata: SCMetadata;
330330
};
331331

332-
let scriptDataAsyncCounter = 0;
333-
334332
// Popup 页面使用的脚本/选单清单元件:只负责渲染与互动,状态与持久化交由外部 client 处理。
335333
const ScriptMenuList = React.memo(
336334
({
@@ -346,15 +344,6 @@ const ScriptMenuList = React.memo(
346344
currentUrl: string;
347345
menuExpandNum: number;
348346
}) => {
349-
// extraData 为 undefined 时先等待异步加载完成,避免重复渲染
350-
const [extraData, setExtraData] = useState<
351-
| {
352-
uuids: string;
353-
lang: string;
354-
metadata: Record<string, SCMetadata>;
355-
}
356-
| undefined
357-
>(undefined);
358347
const [scriptMenuList, setScriptMenuList] = useState<ScriptMenuEntry[]>([]);
359348
const { t } = useTranslation();
360349

@@ -417,48 +406,34 @@ const ScriptMenuList = React.memo(
417406
return url;
418407
}, [currentUrl]);
419408

420-
// string memo 避免 uuids 以外的改变影响
421-
const uuids = useMemo(() => script.map((item) => item.uuid).join("\n"), [script]);
422-
// eslint-disable-next-line react-hooks/exhaustive-deps
423-
const lang = useMemo(() => i18nLang(), [t]); // 当 t 改变时,重新检查当前页面语言
424-
409+
const cache = useMemo(() => new Map<string, SCMetadata | undefined>(), []);
425410
// 以 异步方式 取得 metadata 放入 extraData
426411
// script 或 extraData 的更新时都会再次执行
427412
useEffect(() => {
428-
if (extraData && extraData.uuids === uuids && extraData.lang === lang) {
429-
// extraData 已取得
430-
// 把 getPopupData() 的 scriptMenuList 和 异步结果 的 metadata 合并至 scriptMenuList
431-
const metadata = extraData.metadata;
432-
const newScriptMenuList = script.map((item) => ({ ...item, metadata: metadata[item.uuid] || {} }));
433-
updateScriptMenuList(newScriptMenuList);
434-
} else {
435-
// 取得 extraData
436-
scriptDataAsyncCounter = (scriptDataAsyncCounter % 255) + 1; // 轮出 1 ~ 255
437-
const lastCounter = scriptDataAsyncCounter;
438-
scriptDAO.gets(uuids.split("\n")).then((res) => {
439-
if (lastCounter !== scriptDataAsyncCounter) {
440-
// 由于 state 改变,在结果取得前 useEffect 再次执行,因此需要忽略上次结果
441-
return;
442-
}
443-
const metadataRecord = {} as Record<string, SCMetadata>;
444-
const nameKey = `name:${lang}`;
445-
for (const entry of res) {
446-
if (entry) {
447-
const m = entry.metadata;
448-
const [icon] = m.icon || m.iconurl || m.icon64 || m.icon64url || [];
449-
// metadataRecord 的储存量不影响 storage.session 但影响页面的记忆体
450-
// 按需要可以增加其他 metadata, 例如 @match @include @exclude
451-
metadataRecord[entry.uuid] = {
452-
icon: [icon], // 只储存单个 icon
453-
[nameKey]: [i18nName(entry)], // 只储存 i18n 的 name
454-
} satisfies SCMetadata;
413+
let isMounted = true;
414+
// 先从 cache 读取,避免重复请求相同 uuid 的 metadata
415+
Promise.all(
416+
script.map(async (item) => {
417+
let metadata = cache.get(item.uuid);
418+
if (!metadata) {
419+
const script = await scriptDAO.get(item.uuid);
420+
if (script) {
421+
metadata = script.metadata || {};
455422
}
423+
cache.set(item.uuid, metadata);
456424
}
457-
setExtraData({ uuids, lang, metadata: metadataRecord });
458-
// 再次触发 useEffect
459-
});
460-
}
461-
}, [script, uuids, lang, extraData]);
425+
return { ...item, metadata: metadata || {} };
426+
})
427+
).then((newScriptMenuList) => {
428+
if (!isMounted) {
429+
return;
430+
}
431+
updateScriptMenuList(newScriptMenuList);
432+
});
433+
return () => {
434+
isMounted = false;
435+
};
436+
}, [cache, script]);
462437

463438
useEffect(() => {
464439
// 注册菜单快速键(accessKey):以各分组第一个项目的 accessKey 作为触发条件。

src/pages/popup/App.tsx

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,39 +59,23 @@ function App() {
5959
const { t } = useTranslation();
6060
const pageTabIdRef = useRef(0);
6161

62-
// 只随 script 数量和启动状态而改变的state
63-
const normalEnables = useMemo(() => {
64-
// 返回字串让 React 比对 state 有否改动
65-
return scriptList.map((script) => (script.enable ? 1 : 0)).join(",");
66-
}, [scriptList]);
67-
68-
// 只随 script 数量和启动状态而改变的state
69-
const backEnables = useMemo(() => {
70-
// 返回字串让 React 比对 state 有否改动
71-
return backScriptList.map((script) => (script.enable ? 1 : 0)).join(",");
72-
}, [backScriptList]);
73-
7462
const normalScriptCounts = useMemo(() => {
75-
// 拆回array
76-
const enables = normalEnables.split(",");
7763
// 计算已开启了的数量
78-
const running = enables.reduce((p, c) => p + (+c ? 1 : 0), 0);
64+
const running = scriptList.reduce((p, c) => p + (c.enable ? 1 : 0), 0);
7965
return {
8066
running,
81-
total: enables.length, // 总数
67+
total: scriptList.length, // 总数
8268
};
83-
}, [normalEnables]);
69+
}, [scriptList]);
8470

8571
const backScriptCounts = useMemo(() => {
86-
// 拆回array
87-
const enables = backEnables.split(",");
8872
// 计算已开启了的数量
89-
const running = enables.reduce((p, c) => p + (+c ? 1 : 0), 0);
73+
const running = backScriptList.reduce((p, c) => p + (c.enable ? 1 : 0), 0);
9074
return {
9175
running,
92-
total: enables.length, // 总数
76+
total: backScriptList.length, // 总数
9377
};
94-
}, [backEnables]);
78+
}, [backScriptList]);
9579

9680
const urlHost = useMemo(() => {
9781
let url: URL | undefined;

tests/pages/popup/App.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ describe("Popup App Component", () => {
169169
await waitFor(
170170
() => {
171171
// 检查是否存在折叠面板结构
172-
expect(screen.getByText("current_page_scripts (0/1)")).toBeInTheDocument();
173-
expect(screen.getByText("enabled_background_scripts (0/1)")).toBeInTheDocument();
172+
expect(screen.getByText("current_page_scripts (0/0)")).toBeInTheDocument();
173+
expect(screen.getByText("enabled_background_scripts (0/0)")).toBeInTheDocument();
174174
},
175175
{ timeout: 3000 }
176176
);
@@ -191,8 +191,8 @@ describe("Popup App Component", () => {
191191
await waitFor(
192192
() => {
193193
expect(screen.getByText("ScriptCat")).toBeInTheDocument();
194-
expect(screen.getByText("current_page_scripts (0/1)")).toBeInTheDocument();
195-
expect(screen.getByText("enabled_background_scripts (0/1)")).toBeInTheDocument();
194+
expect(screen.getByText("current_page_scripts (0/0)")).toBeInTheDocument();
195+
expect(screen.getByText("enabled_background_scripts (0/0)")).toBeInTheDocument();
196196
expect(screen.getByText("v" + ExtVersion)).toBeInTheDocument();
197197
},
198198
{ timeout: 3000 }

0 commit comments

Comments
 (0)