Skip to content

useRelation 🔗

はじめに

父子コンポーネント間にコミュニケーションの架け橋を作りたいですか?useRelation が最適な選択です!Vue の provideinject メカニズムに基づいており、父子コンポーネント間のデータ受け渡しとメソッド呼び出しを簡単かつ快適にします ✨

複雑なフォームコンポーネント、ナビゲーションメニュー、あるいは父子コンポーネントの協力が必要なあらゆるシナリオでも、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 に互換性のあるすべてのブラウザ環境をサポートします。

関連ドキュメント 📖

コア概念

関連フック

実際の応用

上級トピック

Vant に基づく企業向けモバイルソリューション