defineProps
defineProps
通常用于在单文件组件中,父组件向子组件传递数据。
父组件 blog.vue
// Blog.vue
<template>
<Aritice :title="AriticeItem.title" :info="AriticeItem.info" :author="AriticeItem.author">
Aritice>
template>
<script setup>
import { reactive } from 'vue';
import Article from '@/components/Article.vue';
const AriticeItem = reactive({
title: '文章标题',
info: '文章内容',
author: 'X',
});
script>
子组件 Article.vue
//Article.vue
<template>
<section>
<p>{{ title }}p>
<p>{{ info }}p>
<p>{{ author }}p>
section>
template>
<script setup>
const props = defineProps({
title: String,
info: String,
author: String,
});
// 对象形式声明 props
// 等价于以字符串数组声明 props
//const props = defineProps(['title', 'info', 'author']);
...
// 在setup中使用时则直接 props.title 即可。
const title1 = props.title;
script>
<style>style>
首先是关于声明方式:
以对象形式声明时,key
为对应的props名称,而value
则可以有多种写法。在上面的例子中,value
的值为该props对应的数据类型。
const props = defineProps({
title: String,
...
})
而实际上,value还可以为一个对象,其中对props的相关信息进行了设定,包括:
type
— props的类型,如果可能存在多个类型,则可以写成数组形式,例如:[String,Number]
default
— 该props的默认值require
— 是否为必传项,默认为false
。以上面的title
为例,其实际上可以写成:
const props = defineProps({
title: {
type: String,
default: '111',
require: false,
}
})
以字符串数组形式声明时,数组的每一项字面量就为prop对应的名称。
const props = defineProps(['title', 'info', 'author']);
其次,关于props的使用:
在 template 模版中,直接使用defineProps()
中声明的名称。
在 setup 中,只需要直接props.xxx
就可以获取到该props对应的值。
另外,还需要注意:
props一般是单向的,所以在子组件中不应该对 props 进行修改。如果需要在子组件中对props进行处理,应当使用ref
对props进行处理后,再进行修改。
const formatTitle = ref(props.title)
//这样,子组件中后续对 formatTitle 的修改,就与原props.title 无关联了
当父组件传递的为一个对象时,例如对于上面的例子:
<Aritice v-bind="AriticeItem">Aritice>
此时,子组件中的声明不需要改变,但是需要将该对象中将会用到的属性值通过defineProps
声明出来。
在向子组件传递数据时,注意组件上用v-bind
绑定的值不要重复,也不要与组件本身的属性重名!
defineEmits
同样地,defineEmits
也常用于单文件组件的组件间通信,但其多用于子组件向父组件传递数据。
父组件
<template>
<section class="parent">
<childVue :num="nums" @increase="handleIncrease">childVue>
section>
template>
<script setup>
import childVue from './child.vue';
import { ref } from 'vue';
const nums = ref(0);
const handleIncrease = () => {
nums.value++;
};
script>
子组件
<template>
<section class="box" @click="handelClick">{{ num }}section>
template>
<script setup>
const emits = defineEmits(['increase']);
const handelClick = () => {
emits('increase');
};
script>
其效果实际上就是点击后,子组件中的value加一。
下面对该示例进行说明。
首先,在父组件中,向子组件传递了一个num
的props用于展示数据。
同时,在父组件中定义了一个名为 increase 的监听事件以及事件触发后的处理函数(即把num
加一)。
而在子组件中,添加了一个点击事件,在该事件的处理函数中,通过对象emit
触发了父组件的 increase 监听事件,导致num
加一。
defineEmits
和defineProps
一样,不需要引入,可以在 setup 函数中直接使用。
defineEmits
可以用于声明对父组件上的一个或多个监听事件的触发,并返回一个触发函数对象emit
。
下面是声明方式:
const emits = defineEmits(['increase']);
// 声明多个触发
const emits = defineEmits(['increase', 'emit2', 'emit3']);
如何使用触发?
// 要触发哪个监听事件,就传入对应的事件名
emits('increase');
emits('emit2');
关于emits()
的参数:
例如:
//子组件发送,传递两个参数
emits('increase', {params1:'1',params2:'2'});
...
//父组件监听事件的处理函数
const handleIncrease = (params) => {
console.log(params.params1, params.params2);//{'1','2'}
nums.value += datas;
};
$ref
+ defineExpose
这种方法通常用于实现父子组件之间的通信。
在 vue2 中,有时候想访问$refs
绑定的组件的属性或者方法,我们会使用 $refs
。
但在 vue3 中,由于在 setup 中无法访问到 this,所以这种方式是不适用的!那么为了能够使用 $refs
,我们需要借助一个方法:getCurrentInstance()
。该对象返回当前的实例对象,但在使用该方法时,需要注意:
父组件
<template>
<div>
<Child ref="child">Child>
<button @click="show">Show Child Messagebutton>
div>
template>
<script setup>
import { getCurrentInstance } from '@vue/runtime-core';
import Child from './Child.vue';
const currentInstance = getCurrentInstance()
function show() {
currentInstance.ctx.$refs.child.alertMessage()
}
script>
子组件
<template>
<div>
<h1>{{ message }}h1>
div>
template>
<script setup>
import { ref } from '@vue/reactivity';
let message = ref('我是子元素').value
const alertMessage = function () {
alert(message)
}
//向外暴露指定属性
defineExpose({
message,
alertMessage
})
script>
这里需要注意的是:在使用语法糖时,组件是默认关闭的,即:在父组件中通过
$refs
来访问子组件中的值或在子组件中通过$parents
来访问父组件中的值时,都是取不到的!必须通过defineExpose
暴露对应值后,才可以被访问到。
实际上,在开发中,上面使用$refs
的方式并不多见,因为其必须要依靠getCurrentInstance()
方法。
使用较多的是下面这种方法:
<template>
<div><input type="text" ref="txt1" value="hello" />div>
template>
<script>
import { defineComponent, ref } from "vue";
export default defineComponent({
setup() {
const txt1 = ref();
const test = () => {
console.log(txt1.value.value);
}
return {
txt1,//一定不要忘记在return中返回给外部,因为这里相当于是声明一个响应式对象txt1,然后将其返回,并在模板中将其绑定为DOM元素的ref。
test
}
}
});
script>
或者可以这样:
<template>
<div><input type="text" ref="txt1" value="hello" />div>
template>
<script setup>
import { defineComponent, ref } from "vue";
const txt1 = ref();
const test = () => {
console.log(txt1.value.value);
}
script>
即在语法糖中,可以不用返回,因为其中的值为自动返回。
接下来就可以通过声明的响应式对象来使用对应组件中通过defineExpose
暴露出来的属性了!
vue3中除了以上三种组件间通信的方式,还有其它可以实现组件间通信的方法:
其中,v-model 并不能严格成为数据的传递方式,其实只是减少了代码量。
而关于 eventBus,虽然 vue3 中将其移除,但仍然可以通过第三方插件来使用。
还有 provide / inject,关于这个我会有单独的一篇文章进行介绍,因为我自身也没怎么接触过。
最后就是 vuex ,vue 的状态管理工具,基本上是项目里面必然会用到的,这里就不说了。