useRelation 🔗
介绍
想要在父子组件之间建立一座沟通的桥梁吗?useRelation 就是你的最佳选择!它基于 Vue 的 provide 和 inject 机制,让父子组件之间的数据传递和方法调用变得轻松愉快 ✨
无论是构建复杂的表单组件、导航菜单,还是任何需要父子组件协作的场景,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 兼容的浏览器环境。
相关文档 📖
核心概念
- Vue 3 Provide/Inject - 了解底层实现原理
- 组合式 API - Vant Use 组合式 API 介绍
- 组件通信 - Vue 组件通信方式
相关 Hooks
- useEventListener - 事件监听管理
- useToggle - 布尔值状态切换
- useCustomFieldValue - 自定义表单字段