コンポーネント間の双方向のデータ バインディングを実装する Vue.js の v-model
ディレクティブについては誰もがよく知っています。ただし、カスタム コンポーネントの v-model
を手動で実装すると、通常、いくつかの問題が発生します。
通常のアプローチは次のとおりです:
const props = defineProps(['modelValue']); const emit = defineEmits(['update:modelValue']); <template></template>
コンポーネント内の modelValue
プロパティの値は変更しないことに注意してください。代わりに、更新された値を emit
メソッド経由で親コンポーネントに返し、親コンポーネントが実際の変更を行います。その理由は次のとおりです。子コンポーネントは親コンポーネントの状態に影響を与えるべきではありません。これにより、データ フローが複雑になり、デバッグが困難になります。
Vue のドキュメントに記載されているように、props は子コンポーネント内で変更すべきではありません。これを行うと、Vue はコンソールに警告を出します。
対象者の調子はどうですか?
JavaScript のオブジェクトと配列は参照によって渡されるため、特殊なケースです。これは、コンポーネントがオブジェクト プロパティのネストされたプロパティを直接変更できることを意味します。ただし、Vue は、ネストされたオブジェクトのプロパティの変更について警告しません (これらの変更を追跡すると、パフォーマンスが低下します)。したがって、このような予期しない変更により、アプリケーションで検出やデバッグが困難な問題が発生する可能性があります。
ほとんどの場合、基本値を v-model
として使用します。ただし、フォームコンポーネントを構築する場合など、場合によっては、オブジェクトを処理できるカスタム v-model
が必要になる場合があります。これは重要な疑問につながります:
上記の落とし穴を回避しながらオブジェクトを処理するカスタム
v-model
を実装するにはどうすればよいですか?
1 つの方法は、書き込み可能な計算プロパティまたは defineModel
ヘルパー関数を使用することです。ただし、どちらのソリューションにも重大な欠点があります。元のオブジェクトを直接変更するため、明確なデータ フローを維持するという目的が損なわれます。
この問題を説明するために、「フォーム」コンポーネントの例を見てみましょう。このコンポーネントは、フォーム内の値が変更されたときに、オブジェクトの更新されたコピーを親コンポーネントに返すように設計されています。書き込み可能な計算プロパティを使用してこれを実現しようとします。
この例では、書き込み可能な計算プロパティによって元のオブジェクトが変更されます。
import { computed } from 'vue'; import { cloneDeep } from 'lodash-es'; type Props = { modelValue: { name: string; email: string; }; }; const props = withDefaults(defineProps<Props>(), { modelValue: () => ({ name: '', email: '' }), }); const emit = defineEmits<{ 'update:modelValue': [value: Props['modelValue']]; }>(); const formData = computed({ // 返回的getter对象仍然是可变的 get() { return props.modelValue; }, // 注释掉setter仍然会修改prop set(newValue) { emit('update:modelValue', cloneDeep(newValue)); }, });
ゲッターから返されたオブジェクトはまだ 変更可能であるため、これは 機能しません。元のオブジェクトが予期せず変更されてしまいます。
defineModel
同じことです。 update:modelValue
はコンポーネントから出力されないため、オブジェクトのプロパティは警告なしに変更されます。
この状況に対処する「Vue の方法」は、内部リアクティブ値を使用してオブジェクトを表し、2 つのオブザーバーを実装することです。
modelValue
プロパティの変更を監視し、内部値を更新します。これにより、内部状態が親コンポーネントによって渡された最新の prop 値を反映するようになります。 これら 2 つのオブザーバー間の無限のフィードバック ループを防ぐには、modelValue
プロパティの更新によって内部値のオブザーバーが誤って再トリガーされないようにする必要があります。
const props = defineProps(['modelValue']); const emit = defineEmits(['update:modelValue']); <template></template>
「これはやりすぎだ!」と思っていることはわかります。さらに簡素化する方法を見てみましょう。
このロジックを再利用可能な合成関数に抽出することは、プロセスを簡素化する優れた方法です。しかし良いニュースは、その必要さえないということです。 VueUse の useVModel
結合関数は、この問題に対処するのに役立ちます。
VueUse は強力な Vue ユーティリティ ライブラリであり、複合ユーティリティの「スイス アーミー ナイフ」とも呼ばれます。完全にツリーシェイク可能なので、パッケージのサイズが大きくなる心配がなく、必要な部分だけを使用できます。
useVModel
を使用してリファクタリングする前の例は次のとおりです:
import { computed } from 'vue'; import { cloneDeep } from 'lodash-es'; type Props = { modelValue: { name: string; email: string; }; }; const props = withDefaults(defineProps<Props>(), { modelValue: () => ({ name: '', email: '' }), }); const emit = defineEmits<{ 'update:modelValue': [value: Props['modelValue']]; }>(); const formData = computed({ // 返回的getter对象仍然是可变的 get() { return props.modelValue; }, // 注释掉setter仍然会修改prop set(newValue) { emit('update:modelValue', cloneDeep(newValue)); }, });
はるかに簡単です!
それだけです!子コンポーネントから直接変更せずに、Vue で v-model
を含むオブジェクトを適切に使用する方法を検討してきました。オブザーバーを使用したり、VueUse の useVModel
のような構成関数を活用したりすることで、アプリケーション内で明確で予測可能な状態管理を維持できます。
この記事のすべての例を含む Stackblitz リンクは次のとおりです。自由に探索して実験してください。
読んでいただきありがとうございます。コーディングを楽しんでください!
以上がVue で v-model でオブジェクトを使用する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。