summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2026-01-02 21:34:43 +0900
committerGitHub <noreply@github.com>2026-01-02 21:34:43 +0900
commit443e1ed29e11dfed85a7a40c58ac2901c0183f88 (patch)
tree1f417f5ae40506c390ebb247ed3d4ad1b700e043
parentfix(frontend): 登録日によるソートの場合はpaginator側のソー... (diff)
downloadmisskey-443e1ed29e11dfed85a7a40c58ac2901c0183f88.tar.gz
misskey-443e1ed29e11dfed85a7a40c58ac2901c0183f88.tar.bz2
misskey-443e1ed29e11dfed85a7a40c58ac2901c0183f88.zip
refactor(frontend): prefer.model, store.modelではcustomRefを使用するように (#17058)
* 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>
-rw-r--r--packages/frontend/src/components/MkPostForm.vue4
-rw-r--r--packages/frontend/src/lib/pizzax.ts61
-rw-r--r--packages/frontend/src/pages/settings/navbar.vue2
-rw-r--r--packages/frontend/src/pages/settings/preferences.vue2
-rw-r--r--packages/frontend/src/pages/settings/profile.vue2
-rw-r--r--packages/frontend/src/preferences/manager.ts57
-rw-r--r--packages/frontend/src/ui/_common_/navbar-h.vue2
7 files changed, 66 insertions, 64 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<T extends StateDef> {
}
/**
- * 特定のキーの、簡易的なgetter/setterを作ります
+ * 特定のキーの、簡易的なcomputed refを作ります
* 主にvue上で設定コントロールのmodelとして使う用
*/
- // TODO: 廃止
- public makeGetterSetter<K extends keyof T, R = T[K]['default']>(
+ public model<K extends keyof T, R = T[K]['default']>(
+ key: K,
+ ): Ref<R>;
+ public model<K extends keyof T, R extends Exclude<any, T[K]['default']>>(
+ key: K,
+ getter: (v: T[K]['default']) => R,
+ setter: (v: R) => T[K]['default'],
+ ): Ref<R>;
+
+ public model<K extends keyof T, R>(
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<R> {
+ return customRef<R>((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();
+ 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']);
+ },
+ };
});
-
- // 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<PreferencesManagerEvents> {
* 特定のキーの、簡易的なcomputed refを作ります
* 主にvue上で設定コントロールのmodelとして使う用
*/
- public model<K extends keyof PREF, V extends ValueOf<K> = ValueOf<K>>(
+ public model<K extends keyof PREF, V = ValueOf<K>>(
+ key: K,
+ ): Ref<V>;
+ public model<K extends keyof PREF, V extends Exclude<any, ValueOf<K>>>(
+ key: K,
+ getter: (v: ValueOf<K>) => V,
+ setter: (v: V) => ValueOf<K>,
+ ): Ref<V>;
+
+ public model<K extends keyof PREF, V>(
key: K,
getter?: (v: ValueOf<K>) => V,
setter?: (v: V) => ValueOf<K>,
- ): WritableComputedRef<V> {
- const valueRef = ref(this.s[key]);
-
- const stop = watch(this.r[key], val => {
- valueRef.value = val;
- });
+ ): Ref<V> {
+ return customRef<V>((track, trigger) => {
+ const watchStop = watch(this.r[key], () => {
+ trigger();
+ });
- // NOTE: vueコンポーネント内で呼ばれない限りは、onUnmounted は無意味なのでメモリリークする
- onUnmounted(() => {
- stop();
- });
+ onScopeDispose(() => {
+ watchStop();
+ }, true);
- // 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<K>);
+ },
+ };
});
}
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<boolean>(() => {
for (const def in navbarItemDef) {
if (menu.value.includes(def)) continue;