发布媒体回填的 revoke 移出 state updater

- VersionMediaApplyTarget 增加 getCover/getScreenshots,覆盖前先 revoke 旧的本地预览
- 两个调用点用 ref 提供当前媒体;updater 只做纯计算,StrictMode 重放不再撤销仍在用的 blob URL
This commit is contained in:
2026-10-07 14:39:57 +08:00
parent 06e6cf8523
commit dca8481031
4 changed files with 25 additions and 9 deletions
@@ -203,6 +203,8 @@ export function GamePublishPage({
title: detail.game.title,
});
void applyVersionMediaDefaults(detail, {
getCover: () => coverImageRef.current,
getScreenshots: () => screenshotImagesRef.current,
setCover: setCoverImage,
setScreenshots: setScreenshotImages,
isCancelled: () => cancelled,
@@ -285,6 +287,8 @@ export function GamePublishPage({
setPriceInput('');
}
void applyVersionMediaDefaults(detail, {
getCover: () => coverImageRef.current,
getScreenshots: () => screenshotImagesRef.current,
setCover: setCoverImage,
setScreenshots: setScreenshotImages,
isCancelled: () => cancelled,
@@ -132,6 +132,8 @@ export function GameWorkMetadataEditor({
.then((detail) => {
if (cancelled) return;
return applyVersionMediaDefaults(detail, {
getCover: () => coverImageRef.current,
getScreenshots: () => screenshotImagesRef.current,
setCover: setCoverImage,
setScreenshots: setScreenshotImages,
isCancelled: () => cancelled,
@@ -44,6 +44,8 @@ describe('applyVersionMediaDefaults', () => {
} as unknown as GameDistributionVersionDetail;
await applyVersionMediaDefaults(detail, {
getCover: () => cover,
getScreenshots: () => screenshots,
setCover: (updater) => {
cover = updater(cover);
},
@@ -7,6 +7,13 @@ import {
} from './gamePublishAssets';
type VersionMediaApplyTarget = {
/**
* 调用瞬间的当前封面。用 ref 读最新值而不是接收 updater:释放 blob URL 是副作用,放进
* `setState` 的 updater 里会被 StrictMode 双调用/重放,撤销到仍被渲染引用的 URL。
*/
getCover: () => GamePublishImage | null;
/** 调用瞬间的当前截图,理由同 {@link getCover}。 */
getScreenshots: () => GamePublishImage[];
setCover: (
updater: (current: GamePublishImage | null) => GamePublishImage | null,
) => void;
@@ -48,19 +55,20 @@ export async function applyVersionMediaDefaults(
.filter(Boolean);
if (apply.isCancelled()) return;
// 覆盖前先释放被替换掉的本地预览 blob URL:这一步之后旧对象不再有人引用,不 revoke 就泄漏。
apply.setCover((current) => {
revokeGamePublishImagePreview(current);
return coverObjectKey ? buildCover('') : null;
});
apply.setScreenshots((current) => {
for (const image of current) revokeGamePublishImagePreview(image);
return screenshotObjectKeys.map((objectKey, index) => ({
// revoke 在 dispatch 之前做,updater 只负责纯计算,重放不会撤销到仍在用的 URL。
revokeGamePublishImagePreview(apply.getCover());
for (const image of apply.getScreenshots()) {
revokeGamePublishImagePreview(image);
}
apply.setCover(() => (coverObjectKey ? buildCover('') : null));
apply.setScreenshots(() =>
screenshotObjectKeys.map((objectKey, index) => ({
id: createGamePublishImageId(),
objectKey,
src: '',
name: `截图 ${index + 1}`,
}));
});
})),
);
const coverPreview = await resolveGamePublishImagePreview(
coverObjectKey,