useRelation 🔗
はじめに
父子コンポーネント間にコミュニケーションの架け橋を作りたいですか?useRelation が最適な選択です!Vue の provide と inject メカニズムに基づいており、父子コンポーネント間のデータ受け渡しとメソッド呼び出しを簡単かつ快適にします ✨
複雑なフォームコンポーネント、ナビゲーションメニュー、あるいは父子コンポーネントの協力が必要なあらゆるシナリオでも、useRelation があれば簡単に実現できます!
コードデモ
基本的な使い方 🚀
簡単なカウンターの例から始めて、父子コンポーネントがどのように優雅に協力するか見てみましょう:
js
// 親コンポーネント - Counter.vue
import { ref, computed } from 'vue';
import { useChildren } from '@vant/use';
const COUNTER_KEY = Symbol('counter-relation');
export default {
setup() {
const { linkChildren, children } = useChildren(COUNTER_KEY);
const count = ref(0);
const add = () => {
count.value++;
console.log(`現在のカウント: ${count.value}`);
};
const reset = () => {
count.value = 0;
console.log('カウンターがリセットされました!');
};
// 子コンポーネントにデータとメソッドを提供
linkChildren({
count,
add,
reset,
getChildrenCount: () => children.length
});
return {
count,
childrenCount: computed(() => children.length)
};
},
};js
// 子コンポーネント - CounterButton.vue
import { useParent } from '@vant/use';
export default {
setup() {
const { parent, index } = useParent(COUNTER_KEY);
const handleClick = () => {
if (parent) {
parent.add();
console.log(`私は${index.value + 1}番目のボタンです`);
}
};
return {
handleClick,
parent,
index
};
},
};フォームコンポーネントの関連 📝
スマートなフォームを構築し、フォーム項目が親フォームに自動登録されるようにしましょう:
js
// 親コンポーネント - SmartForm.vue
import { ref, reactive } from 'vue';
import { useChildren } from '@vant/use';
const FORM_KEY = Symbol('form-relation');
export default {
setup() {
const { linkChildren, children } = useChildren(FORM_KEY);
const formData = reactive({});
const errors = reactive({});
const validateAll = async () => {
const results = await Promise.all(
children.map(child => child.validate?.())
);
return results.every(result => result === true);
};
const resetForm = () => {
children.forEach(child => child.reset?.());
Object.keys(formData).forEach(key => {
formData[key] = '';
});
};
linkChildren({
formData,
errors,
updateField: (name, value) => {
formData[name] = value;
// 該当フィールドのエラーをクリア
if (errors[name]) {
delete errors[name];
}
},
setError: (name, error) => {
errors[name] = error;
}
});
return {
formData,
errors,
validateAll,
resetForm
};
}
};js
// 子コンポーネント - FormField.vue
import { ref, watch } from 'vue';
import { useParent } from '@vant/use';
export default {
props: {
name: String,
rules: Array,
modelValue: String
},
setup(props, { emit }) {
const { parent, index } = useParent(FORM_KEY);
const localValue = ref(props.modelValue);
// 検証メソッド
const validate = () => {
if (!props.rules) return true;
for (const rule of props.rules) {
const result = rule(localValue.value);
if (result !== true) {
parent?.setError(props.name, result);
return false;
}
}
return true;
};
// リセットメソッド
const reset = () => {
localValue.value = '';
emit('update:modelValue', '');
};
// 値の変化を監視
watch(localValue, (newValue) => {
emit('update:modelValue', newValue);
parent?.updateField(props.name, newValue);
});
// 親コンポーネントへメソッドを公開
const instance = getCurrentInstance();
instance.validate = validate;
instance.reset = reset;
return {
localValue,
validate,
reset,
fieldIndex: index
};
}
};ナビゲーションメニューの関連 🧭
スマートなナビゲーションメニューを作成し、子メニュー項目が親メニューに自動登録されます:
js
// 親コンポーネント - Navigation.vue
import { ref, computed } from 'vue';
import { useChildren } from '@vant/use';
const NAV_KEY = Symbol('nav-relation');
export default {
setup() {
const { linkChildren, children } = useChildren(NAV_KEY);
const activeIndex = ref(0);
const setActive = (index) => {
activeIndex.value = index;
// すべての子コンポーネントに状態更新を通知
children.forEach((child, i) => {
child.updateActive?.(i === index);
});
};
const getMenuItems = () => {
return children.map((child, index) => ({
index,
title: child.title,
icon: child.icon,
active: index === activeIndex.value
}));
};
linkChildren({
activeIndex,
setActive,
getMenuItems
});
return {
activeIndex,
setActive,
menuItems: computed(() => getMenuItems())
};
}
};アコーディオンコンポーネントの関連 🎵
アコーディオンコンポーネントを実装し、同時に一つのパネルのみ展開されるようにします:
js
// 親コンポーネント - Accordion.vue
import { ref } from 'vue';
import { useChildren } from '@vant/use';
const ACCORDION_KEY = Symbol('accordion-relation');
export default {
setup() {
const { linkChildren, children } = useChildren(ACCORDION_KEY);
const activePanel = ref(null);
const togglePanel = (panelIndex) => {
// 現在アクティブなパネルがクリックされた場合は閉じます
if (activePanel.value === panelIndex) {
activePanel.value = null;
} else {
activePanel.value = panelIndex;
}
// すべてのパネルの状態を更新
children.forEach((child, index) => {
child.updateExpanded?.(index === activePanel.value);
});
};
linkChildren({
activePanel,
togglePanel,
isActive: (index) => activePanel.value === index
});
return {
activePanel,
togglePanel
};
}
};API 参考 📚
型定義
ts
function useParent<T>(key: string | symbol): {
parent?: T;
index?: Ref<number>;
};
function useChildren(key: string | symbol): {
children: ComponentPublicInstance[];
linkChildren: (value: any) => void;
};useParent の戻り値
| パラメータ | 説明 | 型 |
|---|---|---|
| parent | 親コンポーネントが提供する値(データとメソッド) | any |
| index | 現在のコンポーネントが親の子コンポーネント一覧でのインデックス | Ref<number> |
useChildren の戻り値
| パラメータ | 説明 | 型 |
|---|---|---|
| children | 子コンポーネントのインスタンス一覧 | ComponentPublicInstance[] |
| linkChildren | 子コンポーネントへ値を提供するメソッド | (value: any) => void |
実際の利用シーン 🎯
1. 複雑なフォーム管理
- フォーム検証: すべてのフォーム項目の検証状態を一元管理
- データ収集: すべてのフォーム項目の値を自動収集
- エラー処理: フォームエラーを集中表示・処理
2. ナビゲーションコンポーネント
- 状態同期: ナビゲーション項目のアクティブ状態を同期
- ルーティング管理: ナビゲーション遷移のロジックを一元化
- 権限制御: 権限に応じてナビゲーション項目を動的表示
3. データ表示コンポーネント
- テーブルコンポーネント: 行の選択状態を管理
- カードリスト: カードの展開/折りたたみ状態を制御
- 画像ギャラリー: 画像のプレビュー状態を一元管理
ベストプラクティス 💡
1. 関連キーとして Symbol を使用する
js
// ✅ 推奨: 名前の衝突を避けるため Symbol を使用
const FORM_KEY = Symbol('form-relation');
// ❌ 非推奨: 文字列は衝突を招く可能性がある
const FORM_KEY = 'form-relation';2. 適切なデータ構造の設計
js
// ✅ 推奨: 明確なインターフェースを提供
linkChildren({
// データ
formData,
errors,
// メソッド
updateField,
setError,
validate,
// 状態
isSubmitting
});3. コンポーネントのアンマウント処理
js
// コンポーネントのアンマウント時に参照をクリーンアップ
onUnmounted(() => {
// useChildren は子コンポーネントの削除を自動で処理します
// 追加のクリーンアップが必要な場合はここで対応します
});4. 型安全
ts
interface FormContext {
formData: Record<string, any>;
updateField: (name: string, value: any) => void;
setError: (name: string, error: string) => void;
}
const { parent } = useParent<FormContext>(FORM_KEY);デバッグのコツ 🔧
1. 関連状態を確認
js
// 親コンポーネントで子コンポーネント数を確認
console.log('子コンポーネント数:', children.length);
// 子コンポーネントで関連付けが成功したか確認
console.log('親コンポーネントと関連付けられているか:', !!parent);
console.log('現在のインデックス:', index.value);2. 関連の変化を監視
js
// 子コンポーネント数の変化を監視
watch(() => children.length, (newCount, oldCount) => {
console.log(`子コンポーネント数が ${oldCount} から ${newCount} に変化`);
});3. データ伝達の検証
js
// 子コンポーネントで受け取ったデータを検証
watch(() => parent, (newParent) => {
if (newParent) {
console.log('親コンポーネントのデータを受信:', newParent);
}
}, { immediate: true });ブラウザ互換性 🌐
useRelation は Vue 3 の provide/inject API に基づいており、Vue 3 に互換性のあるすべてのブラウザ環境をサポートします。
関連ドキュメント 📖
コア概念
- Vue 3 Provide/Inject - 基本原理の理解
- コンポジション API - Vant Use のコンポジション API 概要
- コンポーネント間通信 - Vue の通信方式
関連フック
- useEventListener - イベント監視の管理
- useToggle - ブール状態の切り替え
- useCustomFieldValue - カスタムフォームフィールド
実際の応用
- フォームコンポーネント設計 - Vant のフォームコンポーネント
- ナビゲーションコンポーネント - ナビゲーションバー
- アコーディオンコンポーネント - 折りたたみパネル
上級トピック
- コンポーネント設計パターン - Vue のコンポーザブル
- 状態管理 - Pinia による状態管理
- コンポーネントライブラリ開発 - SFC 開発