Vue 组件通信机制详解
父子组件数据传递
Props 单向数据流
父组件通过自定义属性向子组件传递数据,子组件通过 props 选项接收。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Props 示例</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<user-card :username="userInfo.name" :age="userInfo.age"></user-card>
</div>
<script>
const UserCard = {
props: {
username: {
type: String,
required: true
},
age: {
type: Number,
default: 18
}
},
template: `
<div class="card">
<h3>{{ username }}</h3>
<p>年龄: {{ age }}</p>
</div>
`
};
new Vue({
el: '#root',
data: {
userInfo: {
name: '张三',
age: 25
}
},
components: {
'user-card': UserCard
}
});
</script>
</body>
</html>
自定义事件向上通信
子组件通过 $emit 触发事件,父组件监听并接收数据。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>自定义事件</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p>当前计数: {{ counter }}</p>
<counter-btn @increment="addCount"></counter-btn>
</div>
<script>
const CounterBtn = {
data() {
return {
step: 5
};
},
template: `
<button @click="notifyParent">
增加 {{ step }}
</button>
`,
methods: {
notifyParent() {
this.$emit('increment', this.step);
}
}
};
new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
addCount(value) {
this.counter += value;
}
},
components: {
'counter-btn': CounterBtn
}
});
</script>
</body>
</html>
跨层级组件通信
依赖注入 provide / inject
适用于祖先组件向后代组件传递数据,无需逐层传递。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>依赖注入</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="container">
<level-one></level-one>
</div>
<script>
const LevelThree = {
inject: ['themeColor', 'getUser'],
template: `
<div :style="{ color: themeColor }">
深层组件: {{ getUser() }}
</div>
`
};
const LevelTwo = {
template: `
<div>
<level-three></level-three>
</div>
`,
components: {
'level-three': LevelThree
}
};
const LevelOne = {
template: `
<div>
<level-two></level-two>
</div>
`,
components: {
'level-two': LevelTwo
}
};
new Vue({
el: '#container',
provide() {
return {
themeColor: '#1890ff',
getUser: () => this.user.name
};
},
data: {
user: {
name: '管理员',
role: 'super'
}
},
components: {
'level-one': LevelOne
}
});
</script>
</body>
</html>
组件实例访问
根实例访问 $root
任何组件可通过 $root 访问根实例的数据和方法。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>根实例访问</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="main">
<deep-child></deep-child>
</div>
<script>
const DeepChild = {
template: `
<div>
<p>全局消息: {{ globalMsg }}</p>
<button @click="triggerGlobal">调用根方法</button>
</div>
`,
data() {
return {
globalMsg: this.$root.appTitle
};
},
methods: {
triggerGlobal() {
this.$root.showAlert('来自深层组件的调用');
}
}
};
new Vue({
el: '#main',
data: {
appTitle: 'Vue 应用'
},
methods: {
showAlert(msg) {
window.alert(msg);
}
},
components: {
'deep-child': DeepChild
}
});
</script>
</body>
</html>
父组件访问 $parent
子组件通过 $parent 直接访问父组件实例。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>父组件访问</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="wrapper">
<parent-box></parent-box>
</div>
<script>
const InnerChild = {
template: `
<div>
<span>父级数据: {{ parentData }}</span>
<button @click="callParentMethod">执行父级操作</button>
</div>
`,
data() {
return {
parentData: this.$parent.sharedState
};
},
methods: {
callParentMethod() {
this.$parent.updateState('子组件触发更新');
}
}
};
const ParentBox = {
data() {
return {
sharedState: '初始状态'
};
},
template: `
<div class="parent">
<p>当前状态: {{ sharedState }}</p>
<inner-child></inner-child>
</div>
`,
methods: {
updateState(newVal) {
this.sharedState = newVal;
}
},
components: {
'inner-child': InnerChild
}
};
new Vue({
el: '#wrapper',
components: {
'parent-box': ParentBox
}
});
</script>
</body>
</html>
子组件引用 $refs
父组件通过 ref 属性获取子组件实例,直接调用其方法或访问数据。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>组件引用</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="getChildInfo">获取子组件信息</button>
<button @click="runChildTask">执行子组件任务</button>
<info-panel ref="panel"></info-panel>
</div>
<script>
const InfoPanel = {
data() {
return {
content: '面板内容',
version: '1.0.0'
};
},
template: `
<div class="panel">
<h4>{{ content }}</h4>
<p>版本: {{ version }}</p>
</div>
`,
methods: {
refreshData() {
this.content = '数据已刷新 ' + Date.now();
return '刷新完成';
},
getVersion() {
return this.version;
}
}
};
new Vue({
el: '#app',
methods: {
getChildInfo() {
console.log('子组件数据:', this.$refs.panel.content);
console.log('子组件版本:', this.$refs.panel.getVersion());
},
runChildTask() {
const result = this.$refs.panel.refreshData();
console.log('任务结果:', result);
}
},
components: {
'info-panel': InfoPanel
}
});
</script>
</body>
</html>
特性继承与动态组件
$attrs 与 inheritAttrs
$attrs 包含父作用域中未被识别为 props 的特性绑定,inheritAttrs 控制是否将特性应用到根元素。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>特性继承</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="mount">
<base-input
placeholder="请输入内容"
data-id="1001"
class="custom-input"
@focus="onFocus"
></base-input>
</div>
<script>
const BaseInput = {
inheritAttrs: false,
props: ['placeholder'],
template: `
<div class="input-wrapper">
<input
v-bind="$attrs"
:placeholder="placeholder"
/>
<span>额外信息</span>
</div>
`,
mounted() {
console.log('未被props接收的特性:', this.$attrs);
}
};
new Vue({
el: '#mount',
methods: {
onFocus() {
console.log('输入框获得焦点');
}
},
components: {
'base-input': BaseInput
}
});
</script>
</body>
</html>
动态组件与 keep-alive
<component> 配合 is 属性实现动态切换,keep-alive 缓存组件状态避免重复渲染。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>动态组件</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
.tab-nav { display: flex; gap: 10px; margin-bottom: 20px; }
.tab-nav button { padding: 8px 16px; cursor: pointer; }
.tab-nav button.active { background: #1890ff; color: white; }
.tab-content { padding: 20px; border: 1px solid #ddd; }
</style>
</head>
<body>
<div id="dynamic">
<div class="tab-nav">
<button
v-for="item in menuList"
:key="item.code"
:class="{ active: current === item.code }"
@click="switchTab(item.code)"
>
{{ item.label }}
</button>
</div>
<keep-alive :include="cachedViews">
<component :is="activeComponent" class="tab-content"></component>
</keep-alive>
</div>
<script>
const ViewHome = {
name: 'ViewHome',
data() {
return { inputVal: '' };
},
template: `
<div>
<h3>首页</h3>
<input v-model="inputVal" placeholder="输入会被缓存" />
</div>
`,
activated() {
console.log('首页被激活');
},
deactivated() {
console.log('首页被缓存');
}
};
const ViewList = {
name: 'ViewList',
template: '<div><h3>列表页</h3><ul><li>项目1</li><li>项目2</li></ul></div>'
};
const ViewSettings = {
name: 'ViewSettings',
template: '<div><h3>设置页</h3><p>配置选项</p></div>'
};
new Vue({
el: '#dynamic',
data: {
current: 'home',
menuList: [
{ code: 'home', label: '首页' },
{ code: 'list', label: '列表' },
{ code: 'settings', label: '设置' }
],
cachedViews: ['ViewHome', 'ViewSettings']
},
computed: {
activeComponent() {
return 'view-' + this.current;
}
},
methods: {
switchTab(code) {
this.current = code;
}
},
components: {
'view-home': ViewHome,
'view-list': ViewList,
'view-settings': ViewSettings
}
});
</script>
</body>
</html>
异步更新队列
Vue 的 DOM 更新是异步执行的,如需在数据变化后操作 DOM,应使用 $nextTick。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>异步更新</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="async">
<p ref="msgBox">{{ message }}</p>
<button @click="updateAndCheck">修改并检测</button>
</div>
<script>
new Vue({
el: '#async',
data: {
message: '初始文本'
},
methods: {
updateAndCheck() {
this.message = '更新后的内容';
// 同步获取,DOM 尚未更新
console.log('同步获取:', this.$refs.msgBox.textContent);
// 异步获取,DOM 已更新
this.$nextTick(() => {
console.log('nextTick 获取:', this.$refs.msgBox.textContent);
});
}
}
});
</script>
</body>
</html>