From 443e1ed29e11dfed85a7a40c58ac2901c0183f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 2 Jan 2026 21:34:43 +0900 Subject: [PATCH] =?UTF-8?q?refactor(frontend):=20prefer.model,=20store.mod?= =?UTF-8?q?el=E3=81=A7=E3=81=AFcustomRef=E3=82=92=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#17058)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(frontend): prefer.model, store.modelではcustomRefを使用するように * fix: watchの解除に失敗してもエラーで落ちないように * Update packages/frontend/src/lib/pizzax.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../frontend/src/components/MkPostForm.vue | 4 +- packages/frontend/src/lib/pizzax.ts | 63 +++++++++---------- .../frontend/src/pages/settings/navbar.vue | 2 +- .../src/pages/settings/preferences.vue | 2 +- .../frontend/src/pages/settings/profile.vue | 2 +- packages/frontend/src/preferences/manager.ts | 57 +++++++++-------- .../frontend/src/ui/_common_/navbar-h.vue | 2 +- 7 files changed, 67 insertions(+), 65 deletions(-) diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 52684bc815..4b027cf105 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -329,8 +329,8 @@ const canSaveAsServerDraft = computed((): boolean => { return canPost.value && (textLength.value > 0 || files.value.length > 0 || poll.value != null); }); -const withHashtags = computed(store.makeGetterSetter('postFormWithHashtags')); -const hashtags = computed(store.makeGetterSetter('postFormHashtags')); +const withHashtags = store.model('postFormWithHashtags'); +const hashtags = store.model('postFormHashtags'); watch(text, () => { checkMissingMention(); diff --git a/packages/frontend/src/lib/pizzax.ts b/packages/frontend/src/lib/pizzax.ts index 80543d10e4..0dd8a82957 100644 --- a/packages/frontend/src/lib/pizzax.ts +++ b/packages/frontend/src/lib/pizzax.ts @@ -7,7 +7,7 @@ // TODO: Misskeyのドメイン知識があるのでutilityなどに移動する -import { onUnmounted, ref, watch } from 'vue'; +import { customRef, ref, watch, onScopeDispose } from 'vue'; import { BroadcastChannel } from 'broadcast-channel'; import type { Ref } from 'vue'; import { $i } from '@/i.js'; @@ -223,44 +223,43 @@ export class Pizzax { } /** - * 特定のキーの、簡易的なgetter/setterを作ります + * 特定のキーの、簡易的なcomputed refを作ります * 主にvue上で設定コントロールのmodelとして使う用 */ - // TODO: 廃止 - public makeGetterSetter( + public model( + key: K, + ): Ref; + public model>( + key: K, + getter: (v: T[K]['default']) => R, + setter: (v: R) => T[K]['default'], + ): Ref; + + public model( key: K, getter?: (v: T[K]['default']) => R, setter?: (v: R) => T[K]['default'], - ): { - get: () => R; - set: (value: R) => void; - } { - const valueRef = ref(this.s[key]); + ): Ref { + return customRef((track, trigger) => { + const watchStop = watch(this.r[key], () => { + trigger(); + }); - const stop = watch(this.r[key], val => { - valueRef.value = val; + onScopeDispose(() => { + watchStop(); + }, true); + + return { + get: () => { + track(); + return (getter != null ? getter(this.s[key]) : this.s[key]) as R; + }, + set: (value) => { + const val = setter != null ? setter(value) : value; + this.set(key, val as T[K]['default']); + }, + }; }); - - // NOTE: vueコンポーネント内で呼ばれない限りは、onUnmounted は無意味なのでメモリリークする - onUnmounted(() => { - stop(); - }); - - // TODO: VueのcustomRef使うと良い感じになるかも - return { - get: () => { - if (getter) { - return getter(valueRef.value); - } else { - return valueRef.value; - } - }, - set: (value) => { - const val = setter ? setter(value) : value; - this.set(key, val); - valueRef.value = val; - }, - }; } // localStorage => indexedDBのマイグレーション diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue index d25708dcb4..baa8fdc967 100644 --- a/packages/frontend/src/pages/settings/navbar.vue +++ b/packages/frontend/src/pages/settings/navbar.vue @@ -78,7 +78,7 @@ const items = ref(prefer.s.menu.map(x => ({ }))); const itemTypeValues = computed(() => items.value.map(x => x.type)); -const menuDisplay = computed(store.makeGetterSetter('menuDisplay')); +const menuDisplay = store.model('menuDisplay'); const showNavbarSubButtons = prefer.model('showNavbarSubButtons'); async function addItem() { diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 972b50f8cd..aa7f0dabbb 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -855,7 +855,7 @@ const $i = ensureSignin(); const lang = ref(miLocalStorage.getItem('lang')); const dataSaver = ref(prefer.s.dataSaver); -const realtimeMode = computed(store.makeGetterSetter('realtimeMode')); +const realtimeMode = store.model('realtimeMode'); const overridedDeviceKind = prefer.model('overridedDeviceKind'); const pollingInterval = prefer.model('pollingInterval'); diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index 7d3da470d6..8e4c39c8bb 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -190,7 +190,7 @@ const $i = ensureSignin(); const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); -const reactionAcceptance = computed(store.makeGetterSetter('reactionAcceptance')); +const reactionAcceptance = store.model('reactionAcceptance'); function assertVaildLang(lang: string | null): lang is keyof typeof langmap { return lang != null && lang in langmap; diff --git a/packages/frontend/src/preferences/manager.ts b/packages/frontend/src/preferences/manager.ts index 13ba0000e4..1a1ec2b345 100644 --- a/packages/frontend/src/preferences/manager.ts +++ b/packages/frontend/src/preferences/manager.ts @@ -3,11 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { computed, onUnmounted, ref, watch } from 'vue'; +import { customRef, ref, watch, onScopeDispose } from 'vue'; import { EventEmitter } from 'eventemitter3'; import { host, version } from '@@/js/config.js'; import { PREF_DEF } from './def.js'; -import type { Ref, WritableComputedRef } from 'vue'; +import type { Ref } from 'vue'; import type { MenuItem } from '@/types/menu.js'; import { genId } from '@/utility/id.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; @@ -299,36 +299,39 @@ export class PreferencesManager extends EventEmitter { * 特定のキーの、簡易的なcomputed refを作ります * 主にvue上で設定コントロールのmodelとして使う用 */ - public model = ValueOf>( + public model>( + key: K, + ): Ref; + public model>>( + key: K, + getter: (v: ValueOf) => V, + setter: (v: V) => ValueOf, + ): Ref; + + public model( key: K, getter?: (v: ValueOf) => V, setter?: (v: V) => ValueOf, - ): WritableComputedRef { - const valueRef = ref(this.s[key]); + ): Ref { + return customRef((track, trigger) => { + const watchStop = watch(this.r[key], () => { + trigger(); + }); - const stop = watch(this.r[key], val => { - valueRef.value = val; - }); + onScopeDispose(() => { + watchStop(); + }, true); - // NOTE: vueコンポーネント内で呼ばれない限りは、onUnmounted は無意味なのでメモリリークする - onUnmounted(() => { - stop(); - }); - - // TODO: VueのcustomRef使うと良い感じになるかも - return computed({ - get: () => { - if (getter) { - return getter(valueRef.value); - } else { - return valueRef.value; - } - }, - set: (value) => { - const val = setter ? setter(value) : value; - this.commit(key, val); - valueRef.value = val; - }, + return { + get: () => { + track(); + return (getter != null ? getter(this.s[key]) : this.s[key]) as V; + }, + set: (value) => { + const val = setter != null ? setter(value) : value; + this.commit(key, val as ValueOf); + }, + }; }); } diff --git a/packages/frontend/src/ui/_common_/navbar-h.vue b/packages/frontend/src/ui/_common_/navbar-h.vue index ad0632965b..594c398d8b 100644 --- a/packages/frontend/src/ui/_common_/navbar-h.vue +++ b/packages/frontend/src/ui/_common_/navbar-h.vue @@ -67,7 +67,7 @@ const props = defineProps<{ const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD); const menu = ref(prefer.s.menu); -// const menuDisplay = computed(store.makeGetterSetter('menuDisplay')); +// const menuDisplay = store.model('menuDisplay'); const otherNavItemIndicated = computed(() => { for (const def in navbarItemDef) { if (menu.value.includes(def)) continue;