Vue 中可以创建和复用组件。每个组件都有自己独立的状态、逻辑和生命周期,可以看作一个相对独立的功能单元。在组件树中,如果一个组件在模板中使用了另一个组件,那么使用者称为父组件(Parent Component),被使用者称为子组件(Child Component)。
在实际开发中,父子组件之间经常需要进行通信和数据传递。那么,Vue 中的父子组件该如何传值呢?
Vue 为父子组件之间的通信提供了多种方式。其中,最常用的方式就是通过 Props 由父组件向子组件传递数据。
Props定义
子组件如果想要从外部接受数据,需要主动声明
const ChildComponent = {
// 省略...
props: ['msg'] // 声明接收一个名叫 msg 的属性
};子组件通过 Props 声明了自己需要接收哪些数据,那么父组件该如何向子组件传递这些数据呢?
父组件向子组件传递数据有两种方式:静态传值和动态传值。无论采用哪种方式,父组件传递的属性名都需要与子组件声明的 Props 名称保持一致。
所谓静态传值,其实就是写死的值,不可变的,直接将固定的值写在组件标签的属性上,用于向子组件传递数据。
<!--浏览器环境大小写不敏感,所系需要用下面这种方式引入驼峰的子组件-->
<child-component msg="Hello World"></child-component>动态传值需要借助 v-bind 指令,它可以将父组件中的变量或响应式数据绑定到子组件的 Props 上。
下例中的:msg是v-bind指令的缩写。
<child-component :msg="text"></child-component>
<script>
// 【父组件/根应用】
Vue.createApp({
data() {
return {
text: 'Hello World'
}
}
}).mount('#root');
</script>下面来看一个 Props 动态传值的完整示例。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 极简传值</title>
<script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js"></script>
</head>
<body>
<div id="root"></div>
<template id="parent-box">
<div>
<h2>我是父组件</h2>
<child-component :msg="text"></child-component>
</div>
</template>
<template id="child-box">
<div>
<h3>我是子组件</h3>
<p>父组件传给我的内容是:{{ msg }}</p>
</div>
</template>
</body>
<script>
// 【子组件配置】
constChildComponent = {
template: '#child-box',
props: ['msg'] // 声明接收一个名叫 msg 的属性
};
// 【父组件/根应用】
Vue.createApp({
template: '#parent-box',
components: {
'child-component': ChildComponent// 注册子组件
},
data() {
return {
text: 'Hello World'// 父组件里的数据
}
}
}).mount('#root');
</script>
</html>单向数据流
Props 采用单向数据流设计,数据只能从父组件流向子组件。当父组件更新传递给 Props 的数据时,子组件会同步接收到最新的值。子组件不能直接修改 Props 的值,即使尝试修改,也不会真正改变父组件中的数据,并且 Vue 会在开发环境下给出警告。
下面的这个运行后会收到到警告。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 极简传值</title>
<script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js"></script>
</head>
<body>
<div id="root"></div>
<template id="parent-box">
<div>
<h2>我是父组件</h2>
<child-component :msg="text"></child-component>
</div>
</template>
<template id="child-box">
<div>
<h3>我是子组件</h3>
<p>父组件传给我的内容是:{{ msg }}</p>
</div>
</template>
</body>
<script>
// 【子组件配置】
constChildComponent = {
template: '#child-box',
props: ['msg'],// 声明接收一个名叫 msg 的属性
mounted() {
this.msg = "子组件修改了父组件传来的值"; // 试图修改父组件传来的值
}
};
// 【父组件/根应用】
Vue.createApp({
template: '#parent-box',
components: {
'child-component': ChildComponent// 注册子组件
},
data() {
return {
text: 'Hello World'// 父组件里的数据
}
}
}).mount('#root');
</script>
</html>Props校验
虽然 Props 具有单向数据流的特性,但这并不意味着子组件会无条件地接收父组件传递的数据。
Vue 提供了完善的 Props 校验机制,开发者可以在子组件中对接收到的数据进行约束和验证,例如限制数据类型、设置默认值以及标记某个 Props 是否为必填项等。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 极简传值</title>
<script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js"></script>
</head>
<body>
<div id="root"></div>
<template id="parent-box">
<div>
<h2>我是父组件</h2>
<child-component :msg="text"></child-component>
</div>
</template>
<template id="child-box">
<div>
<h3>我是子组件</h3>
<p>父组件传给我的内容是:{{ msg }}</p>
</div>
</template>
</body>
<script>
// 【子组件配置】
constChildComponent = {
template: '#child-box',
props: {
msg: {
type: String,
required: false,
default: "Hello World",
validator: function(value) {
return value == 'Hello World';
}
}
}
};
// 【父组件/根应用】
Vue.createApp({
template: '#parent-box',
components: {
'child-component': ChildComponent// 注册子组件
},
data() {
return {
text: 'Hello World!'// 父组件里的数据
}
}
}).mount('#root');
</script>
</html>从这个例子中可以看到,Vue 为 Props 提供了丰富的校验选项:
• type:用于校验 Props 的数据类型。 • required:用于指定该 Props 是否为必传项。 • default:用于设置默认值,当父组件未传递该 Props 时,将使用这里定义的默认值。 • validator:用于自定义校验规则。它接收当前 Props 的值作为参数,并返回一个布尔值,true 表示校验通过,false 表示校验失败。 以上只是 Props 校验的常见用法,更多高级配置请参考官方文档。
https://cn.vuejs.org/guide/components/props#prop-validation
注意:Props 校验并不是强制性的。 当父组件传递的数据不符合子组件定义的规则时,Vue 只会在控制台输出警告信息,而不会阻止组件继续渲染。
换句话说,Props 校验的主要目的是帮助开发者在开发阶段尽早发现问题,而不是在运行时强制中断程序的执行。
优雅的对象传参
Props 不仅支持字符串、数字、布尔值等基本数据类型,还支持对象、数组等复杂数据类型。
当需要向子组件传递多组相关数据时,与其定义多个 Props,不如将它们封装成一个对象统一传递,这样代码会更加简洁、易于维护。
下面通过一个示例来演示如何使用 Props 传递对象数据。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3传递对象属性示例</title>
<script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js"></script>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
}
/* 子组件卡片样式 */
.card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 04px6pxrgba(0,0,0,0.1);
margin-bottom: 20px;
}
.cardh2 {
margin-top: 0;
color: #333;
}
.cardp {
color: #666;
margin: 5px0;
}
.highlight {
color: #42b983;
font-weight: bold;
}
</style>
</head>
<body>
<div id="root" class="container">
<h2>父组件区域</h2>
<p>当前父组件中的数据对象 <code>userProfile</code> 包含 4 个属性。</p>
<hr>
<h2>子组件展示区域</h2>
<user-card :user-profile="userProfile"></user-card>
</div>
<script>
// 1. 创建父组件
const app = Vue.createApp({
data() {
return {
// 定义一个包含多个属性的对象
userProfile: {
name: '打工人小张',
age: 26,
role: '高级全栈开发工程师',
city: '北京'
}
}
}
});
// 2. 注册子组件 'user-card'
app.component('user-card', {
// 子组件正常声明它需要接收的单项属性
props: ['userProfile'],
// 在模板中直接像普通 prop 一样使用它们
template: `
<div class="card">
<h3>👤 用户信息卡片 (子组件)</h3>
<p><strong>姓名:</strong> <span class="highlight">{{ userProfile.name }}</span></p>
<p><strong>年龄:</strong> {{ userProfile.age }} 岁</p>
<p><strong>职位:</strong> {{ userProfile.role }}</p>
<p><strong>城市:</strong> {{ userProfile.city }}</p>
</div>
`
});
// 3. 挂载到页面上
app.mount('#root');
</script>
</body>
</html>本例通过 Props 传递了一个 userProfile 对象。相比逐个传递多个属性,将相关数据封装成对象统一传递更加简洁、易维护。子组件只需声明一个 Props,即可访问对象中的所有字段。同时需要牢记,Props 中的对象同样遵循单向数据流原则,子组件负责读取和展示数据,而不应直接修改数据内容。
这里还有一种简写形式。通过 v-bind 直接绑定一个对象,Vue 会自动展开对象中的所有属性,子组件只需声明自己需要的 Props 即可。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3 v-bind 批量绑定对象属性示例</title>
<script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js"></script>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
}
/* 子组件卡片样式 */
.card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 04px6pxrgba(0,0,0,0.1);
margin-bottom: 20px;
}
.cardh2 {
margin-top: 0;
color: #333;
}
.cardp {
color: #666;
margin: 5px0;
}
.highlight {
color: #42b983;
font-weight: bold;
}
</style>
</head>
<body>
<div id="root" class="container">
<h2>父组件区域</h2>
<p>当前父组件中的数据对象 <code>userProfile</code> 包含 4 个属性。</p>
<hr>
<h2>子组件展示区域</h2>
<user-card v-bind="userProfile"></user-card>
</div>
<script>
// 1. 创建父组件
const app = Vue.createApp({
data() {
return {
// 定义一个包含多个属性的对象
userProfile: {
name: '打工人小张',
age: 26,
role: '高级全栈开发工程师',
city: '北京'
}
}
}
});
// 2. 注册子组件 'user-card'
app.component('user-card', {
// 子组件正常声明它需要接收的单项属性
props: ['name', 'age', 'role', 'city'],
// 在模板中直接像普通 prop 一样使用它们
template: `
<div class="card">
<h3>👤 用户信息卡片 (子组件)</h3>
<p><strong>姓名:</strong> <span class="highlight">{{ name }}</span></p>
<p><strong>年龄:</strong> {{ age }} 岁</p>
<p><strong>职位:</strong> {{ role }}</p>
<p><strong>城市:</strong> {{ city }}</p>
</div>
`
});
// 3. 挂载到页面上
app.mount('#root');
</script>
</body>
</html>
在这个例子中,父组件使用了 v-bind="userProfile" 的写法,将整个对象一次性传递给子组件。Vue 会自动将对象中的每个属性展开,并将其作为独立的 Props 进行传递。
在子组件中,我们不再需要声明一个 userProfile 对象,而是可以根据实际需求按需声明所需的 Props,例如 name、age、role 和 city。
这种写法能够减少重复代码,使模板更加简洁。当对象中包含大量属性时,使
用 v-bind 对象展开语法往往比逐个绑定 Props 更加优雅和高效。
不过需要注意的是,子组件声明的 Props 名称必须与对象中的属性名保持一致,否则对应的数据将无法正确接收。
两种方式如何选择?
一般来说,如果子组件需要整体操作一个对象,可以直接传递对象;如果子组件只关心对象中的部分字段,那么使用 v-bind 对象展开语法会更加灵活。这样既能保持组件接口清晰,又能降低父组件编写模板时的复杂度。