Sakura工作区任务列表有时无法在点击「排队Sakura」后更新,以及稳定复现此bug的方法
一般来说,在任何一个tab点击「排队Sakura」时,如果此时有tab处在「Sakura工作区」页面,那么应该会自动将这个任务添加进来,然而实际有时并不能保持同步。
复现方法:
- 我自己是新开了一个 Firefox 隐私浏览窗口。
- 打开小说站,切换到 Sakura 页面,不过在此之前先要登录。这一步打开的 tab 记作 Tab0。
- 新开一个 tab,随便切到某个小说页面点击「排队Sakura」,新打开的 tab 记作 Tab1。
- Tab0 的任务队列正确地新增了 Tab1 排队的任务。
- 将 Tab1 切换到「Sakura工作区」页面,这个时候两个tab都是「Sakura工作区」。
- Tab0 随便切换到某个小说页面点击「排队Sakura」。
- 发现 Tab1 的任务队列没有新增排队任务。这个 bug 就复现完成了。
复现以后,查看 localStorage 可以发现,在 Tab0 点击「排队 Sakura」时,实际上 localStorage 完全没有更新,因此 Tab1 自然无法新增排队任务了。
实际上并不是 localStorage 的 hook 出了问题,而是某些情况下无法正确监测并更新 localStorage,所以大概这个问题还是比较好定位和修复的吧,所以先只是描述了具体问题,假若能简单修复就再好不过了。
这个问题我觉得大概是来自vueuse本身的实现,可能不好搞。
这个问题我觉得大概是来自vueuse本身的实现,可能不好搞。
我看了一下,大概是useLocalStorage()返回的ref在切换一次页面以后就不再和 localStorage 同步了,确实大概不好搞,尝试更新了vueuse/core也无法解决。姑且想到几个解决办法:
-
在每次调用
addJob()等方法时,多写一行const ref = useLocalStorage(...)。这样可以保证写时与 localStorage 同步,但是无法保证读时与 localStorage 同步。 -
直接把
Locator.sakuraWorkspaceRepository: lazy(...)套的lazy()直接去掉,每次获取workspace时,都是一个新的 Ref。这样就可以保证读写时均与 localStorage 同步,如果可以保证,切换页面时,必定调用Locator.sakura...()方法,那么所有的 Ref 就都是有效的。然而既然套了一层lazy,大概是有其他作用,所以不是很想用这种方法,感觉以后会出问题。 -
提前
Locator.sakuraWorkspaceRepository()第一次调用的时机。在App.vue里面额外加入一行const _ = Locator.sakuraWorkspaceRepository()(空读一次),似乎确实可以解决问题。但是这样说实话还是不太优雅,而且我不太清楚为什么这样可以 work,是凭借直觉猜出来的。不过确实仅这一行代码就可以解决问题(不过我还把vueuse升级到11.0.1了),当然或许也要对 GPT Workspace 如法炮制。 -
当然了,还有一个方法就是把
useLocalStorage()整个丢掉,如果觉得前面的解决方案都不行的话,整个丢掉也不是不行。问题在于动刀有点狠,不好说有没有新bug。不过优点是看不见的地方变少了,就算出bug也好调试。
上面的大概就是我自己搞了搞之后的报告。我自己是想用方法3啦。
无关:我自己调试前端的时候,会爆大量的500错误,此时命令行显示:
ERROR 00:44:59 [vite] http proxy error: /api/wenku/6707c74ff223ee75285fc643 Error: read ECONNRESET at TLSWrap.onStreamRead (node:internal/stream_base_commons:217:20)
这个是正常的吗?
500错误我这边没见到,wenku元数据没理由出错才对,搞不懂。
解决方案我明天看下。
const lazy = <T>(factory: () => T) => {
let value: T | undefined;
const get = () => {
if (value === undefined) {
value = factory();
}
return value;
};
return get;
};
这个bug的是因 lazy 函数造成的
useLocalstorage 内部使用了 watch 去检测 data 变化更新 localstorage
然而 watch 是 Vue 的响应式系统挂载在当前组件,组件卸载后会被清理,lazy 函数只会初始化执行一次,相当于 watch 在新的页面里没有重新执行导致整体功能失效了
有两种修复方式
- 删除 lazy
- 使用 onUnmounted 在卸载时清除缓存
const lazy = <T>(factory: () => T) => {
let value: T | undefined;
const get = () => {
if (value === undefined) {
value = factory();
+ onUnmounted(()=>{
+ value = undefined;
+ })
}
return value;
};
return get;
};
进一步分析后,发现是node_modules/@vueuse/core/index.mjs里useStorage的这一段用了tryOnMounted来添加listener。当组件卸载时,listener就会被移除,并且无视外面的effectScope或是createGlobalState。删掉tryOnMounted直接加listener就正常了。
if (window && listenToStorageChanges) {
tryOnMounted(() => {
useEventListener(window, "storage", update);
useEventListener(window, customStorageEventName, updateFromCustomEvent);
if (initOnMounted)
update();
});
}
但是诡异的是在复现这个bug的时候,用createGlobalState包装了下就好了,还要再看看为什么。
复现代码:https://stackblitz.com/edit/vitejs-vite-47i1zttd?file=src%2Fstorage.ts
function effectScope(detached?: boolean): EffectScope
内部使用了 effectScope(true),应该是 detached 参数把 watch 之类的与组件分离了吧
这里刚好有 lazy 情况的描述 https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md#detached-nested-scopes
function effectScope(detached?: boolean): EffectScope
内部使用了 effectScope(true),应该是 detached 参数把 watch 之类的与组件分离了吧
这里刚好有 lazy 情况的描述 https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md#detached-nested-scopes
我这边测试,在机翻站代码里没用,在复现的用例里面有用,非常神秘
已反馈给VueUse
https://github.com/vueuse/vueuse/issues/4695
修复后会引入新问题,ls无法写入,刷新后数据失效
这次应该修好了