Skip to content

Commit a2f09ce

Browse files
committed
Merge branch 'release/v1.3' into develop/scripteditor-1
2 parents a6c8655 + d1161b7 commit a2f09ce

File tree

3 files changed

+101
-55
lines changed

3 files changed

+101
-55
lines changed

src/pages/components/ScriptMenuList/index.tsx

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,45 @@ ListMenuItem.displayName = "ListMenuItem";
324324

325325
type TGrouppedMenus = Record<string, GroupScriptMenuItemsProp> & { __length__?: number };
326326

327-
type ScriptMenuEntry = ScriptMenu & {
327+
type ScriptMenuEntryBase = ScriptMenu & {
328328
menuUpdated?: number;
329+
};
330+
331+
// ScriptMenuEntryBase 加了 metadata 后变成 ScriptMenuEntry
332+
type ScriptMenuEntry = ScriptMenuEntryBase & {
329333
metadata: SCMetadata;
330334
};
331335

336+
const cacheMetadata = new Map<string, SCMetadata | undefined>();
337+
// 使用 WeakMap:当 ScriptMenuEntryBase 替换后,ScriptMenuEntryBase的引用会失去,ScriptMenuEntry能被自动回收。
338+
const cacheMergedItem = new WeakMap<ScriptMenuEntryBase, ScriptMenuEntry>();
339+
// scriptList 更新后会合并 从异步取得的 metadata 至 mergedList
340+
const fetchMergedList = async (item: ScriptMenuEntryBase) => {
341+
const uuid = item.uuid;
342+
// 检查 cacheMetadata 有没有记录
343+
let metadata = cacheMetadata.get(uuid);
344+
if (!metadata) {
345+
// 如没有记录,对 scriptDAO 发出请求 (通常在首次React元件绘画时进行)
346+
const script = await scriptDAO.get(uuid);
347+
metadata = script?.metadata || {}; // 即使 scriptDAO 返回失败也 fallback 一个空物件
348+
cacheMetadata.set(uuid, metadata);
349+
}
350+
// 检查 cacheMergedItem 有没有记录
351+
let merged = cacheMergedItem.get(item);
352+
if (!merged || merged.uuid !== item.uuid) {
353+
// 如没有记录或记录不正确,则重新生成记录 (新物件参考)
354+
merged = { ...item, metadata };
355+
cacheMergedItem.set(item, merged);
356+
}
357+
// 如 cacheMergedItem 的记录中的 metadata 跟 (新)metadata 物件参考不一致,则更新 merged
358+
if (merged.metadata !== metadata) {
359+
// 新物件参考触发 React UI 重绘
360+
merged = { ...merged, metadata: metadata };
361+
cacheMergedItem.set(item, merged);
362+
}
363+
return merged;
364+
};
365+
332366
// Popup 页面使用的脚本/选单清单元件:只负责渲染与互动,状态与持久化交由外部 client 处理。
333367
const ScriptMenuList = React.memo(
334368
({
@@ -337,9 +371,7 @@ const ScriptMenuList = React.memo(
337371
currentUrl,
338372
menuExpandNum,
339373
}: {
340-
script: (ScriptMenu & {
341-
menuUpdated?: number;
342-
})[];
374+
script: ScriptMenuEntryBase[];
343375
isBackscript: boolean;
344376
currentUrl: string;
345377
menuExpandNum: number;
@@ -406,34 +438,18 @@ const ScriptMenuList = React.memo(
406438
return url;
407439
}, [currentUrl]);
408440

409-
const cache = useMemo(() => new Map<string, SCMetadata | undefined>(), []);
410-
// 以 异步方式 取得 metadata 放入 extraData
411-
// script 或 extraData 的更新时都会再次执行
412441
useEffect(() => {
413442
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 || {};
422-
}
423-
cache.set(item.uuid, metadata);
424-
}
425-
return { ...item, metadata: metadata || {} };
426-
})
427-
).then((newScriptMenuList) => {
443+
Promise.all(script.map(fetchMergedList)).then((newList) => {
428444
if (!isMounted) {
429445
return;
430446
}
431-
updateScriptMenuList(newScriptMenuList);
447+
updateScriptMenuList(newList);
432448
});
433449
return () => {
434450
isMounted = false;
435451
};
436-
}, [cache, script]);
452+
}, [script]);
437453

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

src/pages/components/ScriptSetting/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const ScriptSetting: React.FC<{
9494
},
9595
];
9696
return ret;
97-
}, [script, scriptRunEnv, scriptRunAt, t]);
97+
}, [script.uuid, scriptRunEnv, scriptRunAt, t]);
9898

9999
useEffect(() => {
100100
const scriptDAO = new ScriptDAO();

src/pages/popup/App.tsx

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,32 @@ const scriptListSorter = (a: ScriptMenu, b: ScriptMenu) =>
4141
b.runNum - a.runNum ||
4242
b.updatetime - a.updatetime;
4343

44+
type TUpdateEntryFn = (item: ScriptMenu) => ScriptMenu | undefined;
45+
46+
type TUpdateListOption = { sort?: boolean };
47+
48+
const updateList = (list: ScriptMenu[], update: TUpdateEntryFn, options: TUpdateListOption | undefined) => {
49+
// 如果更新跟当前 list 的子项无关,则不用更改 list 的物件参考
50+
const newList: ScriptMenu[] = [];
51+
let changed = false;
52+
for (let i = 0; i < list.length; i++) {
53+
const oldItem = list[i];
54+
const newItem = update(oldItem); // 如没有更改,物件参考会保持一致
55+
if (newItem !== oldItem) changed = true;
56+
if (newItem) {
57+
newList.push(newItem);
58+
}
59+
}
60+
if (options?.sort) {
61+
newList.sort(scriptListSorter);
62+
}
63+
if (!changed && list.map((e) => e.uuid).join(",") !== newList.map((e) => e.uuid).join(",")) {
64+
// 单一项未有改变,但因为 sort值改变 而改变了次序
65+
changed = true;
66+
}
67+
return changed ? newList : list; // 如子项没任何变化,则返回原list参考
68+
};
69+
4470
function App() {
4571
const [loading, setLoading] = useState(true);
4672
const [scriptList, setScriptList] = useState<(ScriptMenu & { menuUpdated?: number })[]>([]);
@@ -59,23 +85,48 @@ function App() {
5985
const { t } = useTranslation();
6086
const pageTabIdRef = useRef(0);
6187

88+
// ------------------------------ 重要! 不要隨便更改 ------------------------------
89+
// > scriptList 會隨著 (( 任何 )) 子項狀態更新而進行物件參考更新
90+
// > (( 必須 )) 把物件參考更新切換成 原始类型(例如字串)
91+
92+
// normalEnables: 只随 script 数量和启动状态而改变的state
93+
// 故意生成一个字串 memo 避免因 scriptList 的参考频繁改动而导致 normalScriptCounts 的物件参考出现非预期更改。
94+
const normalEnables = useMemo(() => {
95+
// 返回字串让 React 比对 state 有否改动
96+
return scriptList.map((script) => (script.enable ? 1 : 0)).join(",");
97+
}, [scriptList]);
98+
99+
// backEnables: 只随 script 数量和启动状态而改变的state
100+
// 故意生成一个字串 memo 避免因 scriptList 的参考频繁改动而导致 backScriptCounts 的物件参考出现非预期更改。
101+
const backEnables = useMemo(() => {
102+
// 返回字串让 React 比对 state 有否改动
103+
return backScriptList.map((script) => (script.enable ? 1 : 0)).join(",");
104+
}, [backScriptList]);
105+
// ------------------------------ 重要! 不要隨便更改 ------------------------------
106+
107+
// normalScriptCounts 的物件參考只會隨 原始类型(字串)的 normalEnables 狀態更新而重新生成
62108
const normalScriptCounts = useMemo(() => {
109+
// 拆回array
110+
const enables = normalEnables.split(",").filter(Boolean);
63111
// 计算已开启了的数量
64-
const running = scriptList.reduce((p, c) => p + (c.enable ? 1 : 0), 0);
112+
const running = enables.reduce((p, c) => p + (+c ? 1 : 0), 0);
65113
return {
66114
running,
67-
total: scriptList.length, // 总数
115+
total: enables.length, // 总数
68116
};
69-
}, [scriptList]);
117+
}, [normalEnables]);
70118

119+
// backScriptCounts 的物件參考只會隨 原始类型(字串)的 backEnables 狀態更新而重新生成
71120
const backScriptCounts = useMemo(() => {
121+
// 拆回array
122+
const enables = backEnables.split(",").filter(Boolean);
72123
// 计算已开启了的数量
73-
const running = backScriptList.reduce((p, c) => p + (c.enable ? 1 : 0), 0);
124+
const running = enables.reduce((p, c) => p + (+c ? 1 : 0), 0);
74125
return {
75126
running,
76-
total: backScriptList.length, // 总数
127+
total: enables.length, // 总数
77128
};
78-
}, [backScriptList]);
129+
}, [backEnables]);
79130

80131
const urlHost = useMemo(() => {
81132
let url: URL | undefined;
@@ -91,31 +142,10 @@ function App() {
91142
useEffect(() => {
92143
let isMounted = true;
93144

94-
const updateScriptList = (
95-
update: (item: ScriptMenu) => ScriptMenu | undefined,
96-
options?: {
97-
sort?: boolean;
98-
}
99-
) => {
100-
const updateList = (list: ScriptMenu[], update: (item: ScriptMenu) => ScriptMenu | undefined) => {
101-
const newList = [];
102-
for (let i = 0; i < list.length; i++) {
103-
const newItem = update(list[i]);
104-
if (newItem) {
105-
newList.push(newItem);
106-
}
107-
}
108-
if (options?.sort) {
109-
newList.sort(scriptListSorter);
110-
}
111-
return newList;
112-
};
113-
setScriptList((prev) => {
114-
return updateList(prev, update);
115-
});
116-
setBackScriptList((prev) => {
117-
return updateList(prev, update);
118-
});
145+
const updateScriptList = (update: TUpdateEntryFn, options?: TUpdateListOption) => {
146+
// 当 启用/禁用/菜单改变 时,如有必要则更新 list 参考
147+
setScriptList((prev) => updateList(prev, update, options));
148+
setBackScriptList((prev) => updateList(prev, update, options));
119149
};
120150

121151
const unhooks = [

0 commit comments

Comments
 (0)