[v1.3] 把 metadata 从 chrome.storage.session 抽走#1027
[v1.3] 把 metadata 从 chrome.storage.session 抽走#1027CodFrm merged 6 commits intoscriptscat:release/v1.3from
Conversation
There was a problem hiding this comment.
Pull request overview
此 PR 通过将脚本元数据(metadata)从 chrome.storage.session 迁移到按需从 IndexedDB 异步加载的方式,解决了 storage.session 存储空间不足的问题(issue #1026)。
主要变更:
- 在 Popup 页面组件中实现异步加载 metadata,仅提取必要的图标和国际化名称数据
- 从
ScriptMenu类型中移除 metadata 字段,减少 session storage 占用 - 提取
i18nLang()辅助函数以支持语言变化检测
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
src/pages/components/ScriptMenuList/index.tsx |
主要改动:新增异步加载 metadata 的逻辑,通过 ScriptDAO 从 IndexedDB 读取并合并到脚本列表中 |
src/locales/locales.ts |
提取 i18nLang() 辅助函数,用于获取当前语言标识 |
src/app/service/service_worker/types.ts |
从 ScriptMenu 类型定义中移除 metadata 字段,添加说明注释 |
src/app/service/service_worker/popup_scriptmenu.ts |
注释掉 metadata 赋值逻辑,与类型定义保持一致 |
src/app/service/service_worker/popup.ts |
注释掉不再需要的 getCombinedMeta 调用 |
| } | ||
| } | ||
| setExtraData({ uuids, lang, metadata: metadataRecord }); | ||
| // 再次触发 useEffect |
There was a problem hiding this comment.
Promise 缺少错误处理。如果 scriptDAO.gets() 失败(例如 IndexedDB 访问错误),将会导致未捕获的 Promise 拒绝。建议添加 .catch() 处理错误情况,例如:
scriptDAO.gets(uuids.split("\n")).then((res) => {
// ... 现有逻辑
}).catch((error) => {
console.error("Failed to load script metadata:", error);
// 降级处理:使用空的 metadata
setExtraData({ uuids, lang, metadata: {} });
});| // 再次触发 useEffect | |
| // 再次触发 useEffect | |
| }).catch((error) => { | |
| console.error("Failed to load script metadata:", error); | |
| // 降级处理:使用空的 metadata | |
| setExtraData({ uuids, lang, metadata: {} }); |
| if (extraData && extraData.uuids === uuids && extraData.lang === lang) { | ||
| // extraData 已取得 | ||
| // 把 getPopupData() 的 scriptMenuList 和 异步结果 的 metadata 合并至 scriptMenuList | ||
| const metadata = extraData.metadata; | ||
| const newScriptMenuList = script.map((item) => ({ ...item, metadata: metadata[item.uuid] || {} })); | ||
| updateScriptMenuList(newScriptMenuList); | ||
| } else { | ||
| // 取得 extraData | ||
| scriptDataAsyncCounter = (scriptDataAsyncCounter % 255) + 1; // 轮出 1 ~ 255 | ||
| const lastCounter = scriptDataAsyncCounter; | ||
| scriptDAO.gets(uuids.split("\n")).then((res) => { | ||
| if (lastCounter !== scriptDataAsyncCounter) { | ||
| // 由于 state 改变,在结果取得前 useEffect 再次执行,因此需要忽略上次结果 | ||
| return; | ||
| } | ||
| const metadataRecord = {} as Record<string, SCMetadata>; | ||
| const nameKey = `name:${lang}`; | ||
| for (const entry of res) { | ||
| if (entry) { | ||
| const m = entry?.metadata; | ||
| const [icon] = m.icon || m.iconurl || m.icon64 || m.icon64url || []; | ||
| // metadataRecord 的储存量不影响 storage.session 但影响页面的记忆体 | ||
| // 按需要可以增加其他 metadata, 例如 @match @include @exclude | ||
| metadataRecord[entry.uuid] = { | ||
| icon: [icon], // 只储存单个 icon | ||
| [nameKey]: [i18nName(entry)], // 只储存 i18n 的 name | ||
| } satisfies SCMetadata; | ||
| } | ||
| } | ||
| setExtraData({ uuids, lang, metadata: metadataRecord }); | ||
| // 再次触发 useEffect | ||
| }); | ||
| } | ||
| }, [script, uuids, lang, extraData]); |
There was a problem hiding this comment.
useEffect 的依赖数组包含 extraData 会导致额外的 effect 执行。每次 setExtraData 调用都会触发 effect 重新运行,即使第 428-433 行的逻辑可以正确处理,这仍然是不必要的性能开销。
建议优化:将第 432-433 行的逻辑移到 Promise 的 then 回调中,直接更新 scriptMenuList,而不是通过 setExtraData 触发另一次 effect:
scriptDAO.gets(uuids.split("\n")).then((res) => {
if (lastCounter !== scriptDataAsyncCounter) return;
const metadataRecord = {} as Record<string, SCMetadata>;
// ... 构建 metadataRecord 的逻辑 ...
const newScriptMenuList = script.map((item) => ({
...item,
metadata: metadataRecord[item.uuid] || {}
}));
updateScriptMenuList(newScriptMenuList);
setExtraData({ uuids, lang, metadata: metadataRecord });
});然后从依赖数组中移除 extraData。
| if (extraData && extraData.uuids === uuids && extraData.lang === lang) { | |
| // extraData 已取得 | |
| // 把 getPopupData() 的 scriptMenuList 和 异步结果 的 metadata 合并至 scriptMenuList | |
| const metadata = extraData.metadata; | |
| const newScriptMenuList = script.map((item) => ({ ...item, metadata: metadata[item.uuid] || {} })); | |
| updateScriptMenuList(newScriptMenuList); | |
| } else { | |
| // 取得 extraData | |
| scriptDataAsyncCounter = (scriptDataAsyncCounter % 255) + 1; // 轮出 1 ~ 255 | |
| const lastCounter = scriptDataAsyncCounter; | |
| scriptDAO.gets(uuids.split("\n")).then((res) => { | |
| if (lastCounter !== scriptDataAsyncCounter) { | |
| // 由于 state 改变,在结果取得前 useEffect 再次执行,因此需要忽略上次结果 | |
| return; | |
| } | |
| const metadataRecord = {} as Record<string, SCMetadata>; | |
| const nameKey = `name:${lang}`; | |
| for (const entry of res) { | |
| if (entry) { | |
| const m = entry?.metadata; | |
| const [icon] = m.icon || m.iconurl || m.icon64 || m.icon64url || []; | |
| // metadataRecord 的储存量不影响 storage.session 但影响页面的记忆体 | |
| // 按需要可以增加其他 metadata, 例如 @match @include @exclude | |
| metadataRecord[entry.uuid] = { | |
| icon: [icon], // 只储存单个 icon | |
| [nameKey]: [i18nName(entry)], // 只储存 i18n 的 name | |
| } satisfies SCMetadata; | |
| } | |
| } | |
| setExtraData({ uuids, lang, metadata: metadataRecord }); | |
| // 再次触发 useEffect | |
| }); | |
| } | |
| }, [script, uuids, lang, extraData]); | |
| // 取得 extraData | |
| scriptDataAsyncCounter = (scriptDataAsyncCounter % 255) + 1; // 轮出 1 ~ 255 | |
| const lastCounter = scriptDataAsyncCounter; | |
| scriptDAO.gets(uuids.split("\n")).then((res) => { | |
| if (lastCounter !== scriptDataAsyncCounter) { | |
| // 由于 state 改变,在结果取得前 useEffect 再次执行,因此需要忽略上次结果 | |
| return; | |
| } | |
| const metadataRecord = {} as Record<string, SCMetadata>; | |
| const nameKey = `name:${lang}`; | |
| for (const entry of res) { | |
| if (entry) { | |
| const m = entry?.metadata; | |
| const [icon] = m.icon || m.iconurl || m.icon64 || m.icon64url || []; | |
| // metadataRecord 的储存量不影响 storage.session 但影响页面的记忆体 | |
| // 按需要可以增加其他 metadata, 例如 @match @include @exclude | |
| metadataRecord[entry.uuid] = { | |
| icon: [icon], // 只储存单个 icon | |
| [nameKey]: [i18nName(entry)], // 只储存 i18n 的 name | |
| } satisfies SCMetadata; | |
| } | |
| } | |
| const newScriptMenuList = script.map((item) => ({ ...item, metadata: metadataRecord[item.uuid] || {} })); | |
| updateScriptMenuList(newScriptMenuList); | |
| }); | |
| }, [script, uuids, lang]); |
There was a problem hiding this comment.
ExtraData不是只用來單次儲取
下次也能用
否則每次scripts參考改變就會fetch一次
相反useEffect時裡面沒有複雜的指令,不會有效能問題
| } | ||
| | undefined | ||
| >(undefined); | ||
| const [scriptMenuList, setScriptMenuList] = useState<ScriptMenuEntry[]>([]); |
There was a problem hiding this comment.
初始状态下 scriptMenuList 为空数组,会导致在异步加载 metadata 期间显示"无数据",即使实际上有脚本存在。这会造成不好的用户体验(闪烁)。
建议的解决方案:
- 初始状态设为
undefined来区分"加载中"和"无数据":
const [scriptMenuList, setScriptMenuList] = useState<ScriptMenuEntry[] | undefined>(undefined);- 在渲染时增加加载状态判断:
{scriptMenuList === undefined ? (
<Spin /> // 或其他加载指示器
) : scriptMenuList.length === 0 ? (
<Empty description={t("no_data")} />
) : (
scriptMenuList.map(...)
)}Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
概述 Descriptions
CACHE_KEY_TAB_SCRIPT(popup 用的)tabScript 不要储存 metadata #1026close #1026
变更内容 Changes
截图 Screenshots