Vue2 与 Vue3 父子组件传值及方法调用

在 Vue的开发过程中,父子组件之间的通信是极为常见且重要的操作,其中传值以及方法调用是实现组件间交互的关键环节。Vue2 和 Vue3 在这方面既有相似之处,又存在一些显著差异。本文将通过具体代码呈现 Vue2 与 Vue3 父子组件传值及方法调用的差异。

1. vue3中的父子组件交互

1.1. 父组件向子组件传值

Vue3 引入了 defineProps 宏。defineProps 有两种使用方式,数组形式和对象形式,与 Vue2 的 props 接收方式类似,但更加简洁。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 父组件 -->
<template>
<div>
<ChildComponent :message="parentMessage" :count="parentCount" />
</div>
</template>

<script setup>
import { ref } from 'vue';

const parentMessage = ref('Hello from parent');
const parentCount = ref(10);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 子组件 -->
<template>
<div>
<p>{{ message }}</p>
<p>{{ count }}</p>
</div>
</template>

<script setup>
// 数组形式接收
const props = defineProps(['message', 'count']);

// 对象形式接收(示例注释)
// const props = defineProps({
// message: {
// type: String,
// required: true
// },
// count: {
// type: Number,
// default: 0
// }
// });
</script>

1.2. 父组件调用子组件方法

Vue3 中父组件调用子组件的方法同样使用 ref,但在 <script setup> 中需要先定义 ref 变量来引用子组件。与 Vue2 不同的是,子组件需要通过 defineExpose 来显式暴露要被父组件访问的方法和属性,否则父组件无法直接访问。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 父组件 -->
<template>
<div>
<ChildComponent ref="childRef" />
<button @click="callChildMethod">Call Child Method</button>
</div>
</template>

<script setup>
import { ref } from 'vue';

const childRef = ref(null);

const callChildMethod = () => {
if (childRef.value) {
childRef.value.childMethod();
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 子组件 -->
<template>
<div>
<p>Child Component</p>
</div>
</template>

<script setup>
import { defineExpose } from 'vue';

const childMethod = () => {
console.log('Child method called');
};

defineExpose({
childMethod
});
</script>

1.3. 子组件调用父组件方法并传值

子组件向父组件传值同样是通过触发自定义事件,在 Vue3 中可以使用 defineEmits 宏来声明要触发的自定义事件。子组件通过 emit 函数触发事件并传递数据,父组件监听子组件标签上的自定义事件来接收数据。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 子组件 -->
<template>
<button @click="sendDataToParent">Send Data</button>
</template>

<script setup>
const emit = defineEmits(['childEvent']);

const sendDataToParent = () => {
const data = 'Hello from child';
emit('childEvent', data);
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 父组件 -->
<template>
<div>
<ChildComponent @childEvent="handleChildEvent" />
</div>
</template>

<script setup>
const handleChildEvent = (data) => {
console.log('Received from child:', data);
};
</script>

2. vue2中的父子组件交互

2.1. 父组件向子组件传值

在 Vue2 中,父组件向子组件传值主要通过 props 属性。父组件在引用子组件时,通过在子组件标签上定义自定义属性来传递数据。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 父组件 -->
<template>
<div>
<ChildComponent :message="parentMessage" :count="parentCount" />
</div>
</template>

<script>
export default {
data() {
return {
parentMessage: 'Hello from parent',
parentCount: 10
};
}
};
</script>

子组件则通过 props 选项来接收父组件传递的数据,接收方式有数组形式和对象形式。数组形式较为简单,直接列出要接收的属性名;对象形式可以对接收的数据进行类型校验、设置默认值等操作。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 子组件 -->
<template>
<div>
<p>{{ message }}</p>
<p>{{ count }}</p>
</div>
</template>

<script>
export default {
// 数组形式接收
props: ['message', 'count'],
// 对象形式接收(示例注释)
// props: {
// message: {
// type: String,
// required: true
// },
// count: {
// type: Number,
// default: 0
// }
// }
};
</script>

2.2. 父组件调用子组件方法

在 Vue2 中,父组件调用子组件的方法主要通过 $refs。父组件在引用子组件时,给子组件添加 ref 属性,然后在需要调用子组件方法时,通过 this.$refs 获取子组件实例,进而调用其方法。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 父组件 -->
<template>
<div>
<ChildComponent ref="childRef" />
<button @click="callChildMethod">Call Child Method</button>
</div>
</template>

<script>
export default {
methods: {
callChildMethod() {
this.$refs.childRef.childMethod();
}
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 子组件 -->
<template>
<div>
<p>Child Component</p>
</div>
</template>

<script>
export default {
methods: {
childMethod() {
console.log('Child method called');
}
}
};
</script>

2.3. 子组件向父组件传值

子组件向父组件传值则是通过 $emit 方法触发自定义事件来实现。子组件在需要向父组件传递数据时,调用 $emit 并传入自定义事件名和要传递的数据。父组件在引用子组件时,通过在子组件标签上监听该自定义事件来接收数据。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 子组件 -->
<template>
<button @click="sendDataToParent">Send Data</button>
</template>

<script>
export default {
methods: {
sendDataToParent() {
const data = 'Hello from child';
this.$emit('childEvent', data);
}
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 父组件 -->
<template>
<div>
<ChildComponent @childEvent="handleChildEvent" />
</div>
</template>

<script>
export default {
methods: {
handleChildEvent(data) {
console.log('Received from child:', data);
}
}
};
</script>

2.4. 子组件调用父组件方法

子组件访问父组件的方法既可以通过$emit 的方式,也可以通过 this.$parent,直接访问父组件的实例,进而调用其方法。但这种方式在多层嵌套组件中可能会导致依赖混乱,不推荐过度使用。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 子组件 -->
<template>
<button @click="callParentMethod">Call Parent Method</button>
</template>

<script>
export default {
methods: {
callParentMethod() {
this.$parent.parentMethod();
}
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 父组件 -->
<template>
<div>
<ChildComponent />
</div>
</template>

<script>
export default {
methods: {
parentMethod() {
console.log('Parent method called');
}
}
};
</script>

3. 差异对比

  1. 语法差异
    Vue2 使用选项式 API,代码结构基于 datamethodsprops 等选项。
    Vue3 推荐使用组合式 API,特别是 <script setup> 语法糖,使得代码更加简洁、逻辑更加集中。例如在接收父组件传值时,Vue2 需要在 props 选项中定义,而 Vue3 通过 defineProps<script setup> 中直接定义,减少了样板代码。

  2. 方法调用的访问权限差异
    Vue2 中子组件的所有方法和属性默认对父组件可见,父组件可以直接通过 $refs 访问子组件的任何方法和属性。
    Vue3 中,子组件需要通过 defineExpose 显式暴露方法和属性,父组件才能访问,这增强了组件的封装性和安全性,避免了不必要的属性和方法暴露。