Skip to content

useRelation 🔗

介绍

想要在父子组件之间建立一座沟通的桥梁吗?useRelation 就是你的最佳选择!它基于 Vue 的 provideinject 机制,让父子组件之间的数据传递和方法调用变得轻松愉快 ✨

无论是构建复杂的表单组件、导航菜单,还是任何需要父子组件协作的场景,useRelation 都能帮你轻松搞定!

代码演示

基础用法 🚀

让我们从一个简单的计数器例子开始,看看父子组件是如何优雅地协作的:

js
// 父组件 - Counter.vue
import { ref } 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 兼容的浏览器环境。

相关文档 📖

核心概念

相关 Hooks

实际应用

进阶主题

基於Vant構建的企業級移動端解決方案