文章目录
- 组件间传值的方法
-
- 总结
- 什么是单向数据流
- 父组件给子组件传值
-
- [方式1: props](#方式1: props)
-
- [options API写法](#options API写法)
- [composition API | defineProps编译宏](#composition API | defineProps编译宏)
-
- [props类型声明的默认值 | widthDefaults编译宏](#props类型声明的默认值 | widthDefaults编译宏)
- 方法2:组件身上的属性与事件
-
- [vue2 $attrs + $listeners](#vue2 $attrs + $listeners)
- [vue3 useAttrs方法](#vue3 useAttrs方法)
- [方法3: $parent + defineExpose编译宏 | 子组件内部获取到父组件实例](#方法3: $parent + defineExpose编译宏 | 子组件内部获取到父组件实例)
- [方法4:插槽 结构父 -> 子](#方法4:插槽 结构父 -> 子)
- 子组件给父组件传递
-
- 自定义事件与原生DOM事件
- [方法1:自定义事件/原生DOM事件 + emit 方法](#方法1:自定义事件/原生DOM事件 + emit 方法)
-
- [options API写法 | $emit()](#options API写法 | $emit())
- [composition API写法 | defineEmits编译宏](#composition API写法 | defineEmits编译宏)
- [方法2 ref + defineExpose编译宏 | 父组件获取子组件的值/方法](#方法2 ref + defineExpose编译宏 | 父组件获取子组件的值/方法)
- 父子组件数据同步
-
- [vue2 v-model的原理](#vue2 v-model的原理)
- [vue2 sync 修饰符原理](#vue2 sync 修饰符原理)
- [vue3 v-model原理](#vue3 v-model原理)
- 祖先组件向子孙组件传值
组件间传值的方法
总结
- 父组件传子组件
- 父组件在使用子组件时通过设置属性传值,子组件使用props接收
- 子组件传父组件
- 自定义事件 父组件将自定义事件传递给子组件,子组件emit接受
什么是单向数据流
- 什么是单向数据流
数据流是指组件之间数据的流向,单向数据流指数据只能从父组件向子组件传递,子组件无法改变父组件的props,如果想修改有其他的方式。 - 为什么不能是双向的
父组件的数据发生改变,会通过props来通知子组件自动更新。 防止多个子组件都尝试修改父组件状态时,导致数据混乱
单向数据流的好处
- 单向数据流会使所有状态的改变可记录、可跟踪,源头易追溯;
- 所有数据只有一份,组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性。
父组件给子组件传值
方式1: props
通用说明
1.props是只读属性,子组件不可以修改传入的值。
2.props方式的传值是浅拷贝
传递方法
1.父组件上使用子组件,通过子组件的属性进行传值。
2.子组件props
接受父组件的传值。
options API写法
- 子组件通过
props
参数接收数据,数据优先被设置在vc组件上(vc.数据)- 如果子组件不使用
props
,子组件的$attrs
里会存储传过来的属性,子组件的vc实例上不会存储。 - 如果子组件使用
props
,子组件的vc实例上会直接存储传过来的属性,子组件的$attrs
里不会存储
- 如果子组件不使用
js
//1.父组件通过属性传值
<Student :name=name :sex=sex :age=age/>
//option API
//写法1:子组件接收值,简单接收
props:['name','sex','age']
//写法2:子组件接收值,设置传来值的类型
props:{
name:String,
sex:String,
age:Number
}
//写法3: 子组件接收值,完整写法
props:{
name:{
type:String,
required:true //是否必须
default: "table"
},
age:{
type:Number,//类型
default:99 //默认值
},
rowClick: {
type: Function,
default: () =>{}
},
columns: {
type: Array,
default:() =>[]
},
api: {
type: Object,
default:()=>({})
},
}
default默认值
1.使用default定义默认值时,如果父组件有传值,则用父值渲染。如果父组件没有传值,则使用默认值。
2.没有定义默认值时,如果父组件有传值,则用父值渲染。如果父组件没有传值,则使用的是该类型的默认值。
js
String ''
Number 0
Array []
Object {}
props 默认值只在没有传参时才会被读取,如果传入的是不完整对象,并不会为对象没有值的属性补齐默认值
所有props默认值(vue2和vue3)的写法都需要遵守
基本数据类型:直接赋值
对象数据类型:用函数赋值()=>{}
如果直接采用{}和[],当多个使用该组件的父组件都没有传递props而使用默认值。假设其中一个父组件修改了默认的值(不推荐,会报警告),其他的父组件由于指向的是同一个内存中的引用地址,也会发生改变。
使用了函数的形式去返回,保证每次函数执行出来的都是返回一个新的对象。
composition API | defineProps编译宏
defineProps()
不需要定义和引用就可以直接使用
返回值:返回一个由接受属性组成的代理对象
说明
defineProps
和defineEmits
都是只能在<script setup>
中使用的编译器宏。他们不需要导入,且会随着<script setup>
的处理过程一同被编译掉。defineProps
或defineEmits
要么使用运行时声明,要么使用类型声明。同时使用两种声明方式会导致编译报错。- 在模板中使用时,props可以省略。
js
//第一种,简单接收
const props = defineProps(["name"]);
//第二种设置接收的类型
defineProps({
page:Number
});
//第三种设置接收的类型和默认值
defineProps({
page:{
type:Number,
default:2
}
});
//第四种设置传来值的多种类型
defineProps({
page:[String,Number]
})
运行时声明 指对于 props 的类型的声明,这种声明方式 IED 是无法检测和给出提示的,只有在运行后才会给出提示,props API属于运行时声明。
类型声明 在这里类型声明指基于 ts 的类型检查,对 props 进行类型的约束,支持IDE的类型推断和检查。因此,要使用类型声明,需要基于 ts,即 <script setup lang="ts">
composition API运行时声明的写法
js
const props = defineProps({
foo: String,
bar: {
type: Number,
required: true
}
})
</script>
composition API类型声明的写法
js
<script setup lang='ts'>
interface List {
id: number,
content: string
}
const props = defineProps<{
foo?: string
list: List // 接口
}>()
</script>
props类型声明的默认值 | widthDefaults编译宏
js
<script setup lang="ts">
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
</script>
方法2:组件身上的属性与事件
vue2 $attrs + $listeners
多层嵌套组件传递数据时,如果只是传递数据,而不做中间处理的话就可以用这个,比如父组件向孙子组件传递数据时
$attrs
:包含父作用域里除class
和style
除外的非props
属性集合。通过this.$attrs
获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过v-bind="$attrs"
$listeners
:包含父作用域里.native
除外的监听事件集合。如果还要继续传给子组件内部的其他组件,就可以通过v-on="$linteners"
vue3 useAttrs方法
useAttrs()
: 获取组件身上的属性与事件,只能接受父作用域里除 class
和 style
除外的非 props
属性集合,原生DOM事件和自定义事件都可以接受。
js
<template>
<el-button :="$attrs"></el-button>
</template>
<script setup lang="ts">
import { useAttrs } from 'vue';
let $attrs = useAttrs(); //返回的attrs是一个响应式代理对象
</script>
:="$attrs"
写法的意思,解构$attrs
对象,key
为属性名,value
为属性值
js
<h1 v-bind="{a:1,b:2}">123</h1>
//页面中显示
<h1 a="1" b="2">123</h1>
方法3: $parent + defineExpose编译宏 | 子组件内部获取到父组件实例
$parent
可以在子组件内部获取到父组件的实例
js
//子组件
<button @click="handler($parent)"></button>
//父组件暴露
defineExpose({})
使用 script setup 的组件是默认关闭的 ,通过模板 ref 或者 $parent 链获取到的组件的实例,不会暴露任何在 script setup 中声明数据。
需要使用 defineExpose 编译器宏将数据暴露
方法4:插槽 结构父 -> 子
默认情况引用组件后, 父组件模板里面使用组件,组件开闭标签内部的结构不会被渲染出来。如果想将其渲染出来,也就是由父组件传递html结构给子组件,就可以使用插槽。
插槽作用:让父组件可以向子组件指定位置插入html结构
插槽的种类
- 默认插槽:父组件将html结构传递给子组件(相当于子组件留html坑,由父组件传递html填坑)。
- 具名插槽(slot标签的name属性命名):由父组件决定将html结构传递哪个插槽。
- 作用域插槽:父组件将html结构传递给子组件,子组件将数据传回父组件 。数据在子组件,但是根据数据生成的结构需要父组件来决定
默认插槽
同一组件默认插槽可以有多个
js
//Parent.vue
<Child>
<div>默认插槽</div>
</Child>
//Child.vue
<slot></slot> //默认插槽 <div>默认插槽</div>
<slot></slot>
具名插槽
使用<slot name="xxx">
给插槽命名,父组件选择插入哪个插槽(①②都是vue3写法)
①使用<template v-slot:xxx>
②简写形式<template #xxx>
同一组件剧名插槽可以有多个
js
//Child.vue
<slot name="a"></slot>
//Parent.vue
<template #a>
<div>我是填充具名插槽a位置结构</div>
</template>
作用域插槽
作用域插槽:数据: 子 -> 父 结构:父 -> 子
使用场景:数据在插槽所在的组件,但是根据数据生成的结构需要父组件来决定。
数据在子组件(作用域),结构由父组件传。
步骤
1.<slot :xxxx="yyy">
将数据传给父组件,slot的固定写法。传递一个对象,key为xxx,值为yyy。
2.父组件使用作用插槽 <template v-slot="{xxxx}">
,这里需要解构使用
js
<Child :todos="todos">
//v-slot接受子组件传递回来的数据
<template v-slot="{ row, index }">
<p :style="{ color: row.done ? 'green' : 'red' }">
{{row.title }}--{{ index }}
</p>
</template>
</Child>
let todos = ref([
{ id: 1, title: "ranran", done: true },
{ id: 2, title: "ranran1", done: false },
{ id: 3, title: "ranran2", done: true },
{ id: 4, title: "ranran3", done: false },
]);
//Child.vue
//子组件回传数据给父组件
<ul>
<li v-for="(item, index) in todos" :key="item.id">
<!--作用域插槽:可以讲数据回传给父组件-->
<slot :row="item" :index="index"></slot>
</li>
</ul>
<script setup lang="ts">
//通过props接受父组件传递数据
defineProps(["todos"]);
</script>
子组件给父组件传递
自定义事件与原生DOM事件
js
// 在vue2中,这种写法为自定义事件
// 在vue3中,这种写法为原生DOM事件
<Child @click=""></Child>
- 组件在绑定事件时,vue2默认绑定的是自定义事件。在vue2中,可以使用
.native
标识符比如@click.native
,将自定义事件变为原生的DOM事件,将事件绑定在子组件的根节点上。 - vue3原生的DOM事件不管放在标签上、组件上都是原生事件。
虽然vue3的原生DOM事件绑定在组件上也是原生事件,但是只要子组件通过defineEmits
接受了父组件传递过来的函数,该事件会被当作自定义事件触发。
方法1:自定义事件/原生DOM事件 + emit 方法
options API写法 | $emit()
- 将自定义事件绑定在子组件的实例vc上,回调函数是在父组件中,通过回调函数接收参数
- 写法1: 在父组件中使用
@
或v-on:
将回调函数绑定在子组件的vc上@自定义事件='事件回调'
- 写法2: 在父组件中
this.$refs.xxx
获取到子组件的实例,采用$on('事件名',回调函数)
绑定自定义事件
- 子组件
$emit()
触发自定义事件并传递参数,子组件this.$off()
解绑自定义事件
js
//父组件
<template>
<div>
<h1>我是父组件</h1>
<Son :info="info" @change="fn"></Son>
</div>
</template>
<script>
import Son from "./Son.vue";
export default {
data() {
return {
info: "我是父组件中的数据",
};
},
components: {
Son,
},
methods: {
fn(info) {
this.info = info + "我是父组件中点击修改后的数据";
},
},
};
</script>
//子组件
<template>
<div>
<h2>我是子组件</h2>
<p>{{ info }}</p>
<button @click="fn">修改数据(子)</button>
</div>
</template>
<script>
export default {
props: ["info"], //父传子
methods: {
fn() {
//这种直接赋值prop是不可取的,vue会直接报错
//this.info=this.info+"子组件直接赋值prop"
// 修改数据
this.$emit('change',this.info + ",我现在被子组件emit了"); //触发自定义事件并传值,父组件自定义事件的回调函数触发
},
},
};
</script>
composition API写法 | defineEmits编译宏
defineEmits接受父组件传递过来的自定义函数
返回值:(event, ...args) => instance.emit(event, ...args)
返回一个函数,第一个参数为自定义事件名,第二个参数开始为传递给自定义函数的参数。
运行时声明的写法
js
//子组件
<template>
<button @click="butFn">改变page值:{{page}}</button>
</template>
<script setup>
import { defineEmits } from "vue";
const emit = defineEmits(["pageFn"]); //定义一个变量来接收父组件传来的方法
const butFn=()=>{
emit("pageFn",5)//触发该方法,5为传递的参数
}
</script>
composition API类型声明的写法
js
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
// 3.3+:另一种更简洁的语法
const emit = defineEmits<{
change: [id: number] // 具名元组语法
update: [value: string]
}>()
方法2 ref + defineExpose编译宏 | 父组件获取子组件的值/方法
ref
可以获取真实的DOM节点,也可以获取子组件实例VC
$parent
可以在子组件内部获取到父组件的实例
setup中获取子组件的实例
1.父组件中设置子组件属性ref
2.同名变量接受
js
//父组件
<Son ref='son'> </son>
<script setup lang="ts">
const son= ref(null);
</script>
存在问题:ref
可以获取子组件实例,但是获取不到子组件数据
原因:组件内部数据对外关闭
解决办法:组件defineExpose()
对外暴露值或方法
js
//子组件暴露
defineExpose({
money,
})
//父组件使用
<Son ref='son'> </son>
<script setup lang="ts">
const son= ref(null);
conselo.log(son.value);
</script>
父子组件数据同步
vue2 v-model的原理
v-model语法糖 = props(父传子) + 绑定事件监听@input/触发事件监听emit(子传父)
v-model实现原理
默认传递的属性是value,自定义事件名为input
js
<!--v-model简写-->
<Son v-model="msg" />
<!--原始写法-->
<Son :value="msg" @input= "val => msg=val "/>
text 和 textarea 元素使用 value property 和 input 事件;
checkbox 和 radio 使用 checked property 和 change 事件;
select 字段使用 value property和change 作为事件。
vue2 sync 修饰符原理
sync 修饰符 = props(父传子) + 绑定事件监听@update:属性名/触发事件监听emit (子传父)
.sync
修饰符可以实现和v-model
同样的功能,而且它比v-model
更加灵活。
v-model
一个组件只能用一个.sync
可以有多个。
xxx.sync
的原理
①父组件给字子组件传递props:属性名
②给当前子组件绑定了一个自定义事件,事件名为update:属性名
,该事件会更新xxx
的值
js
// 正常父传子:
<son :info="str" :title="str2"></son>
// 加上sync之后父传子(.sync没有数量限制):
<son :info.sync="str" .title.sync="str2"></son>
// 它等价于
<son
:info="str" @update:info="val=>str=val"
:title="str2" @update:title="val=>str2=val">
</son>
//子组件emit触发事件
this.$emit('update:info',this.info + ",我现在被子组件emit了")
vue3 v-model原理
vue3 v-model
的原理是vue2中sync
修饰符的原理,特点也一样,一个组件可以有多个v-model
。
绑定多个v-model:v-model:value = 值
,每个v-model之间互不影响。
js
<Child1 v-model:pageNo="pageNo" v-model:pageSize="pageSize"></Child1>
祖先组件向子孙组件传值
provide/inject
provide
提供数据:指定想要提供给后代组件的数据或方法inject
在组件中注入数据:在任何后代组件中使用inject
接收provide
提供的数据或方法,不管组件嵌套多深都可以直接拿来用
vue2中的使用
inject
在data/props
之前初始化,provide
在data/props
之后初始化,注入内容时,是将内容注入到当前vc的__provide
中
inject
配置key先在当前组件读取内容(__provide
),读取不到则取它的父组件读取,找到最终内容保存到当前实例(vc)中,这样可以直接通过this读取到inject注入的内容。
vue 不会对 provide 中的变量进行响应式处理。所以,要想 inject 接受的变量是响应式的,provide 提供的变量本身就需要是响应式的。
js
// 父组件
export default {
provide: {
name: "父组件数据",
say() {
console.log("say say say");
},
},
// 当需要用到this的时候需要使用函数形式,调用时this指向当前组件
provide() {
return {
todoLength: this.todos.length
}
},
}
// 子组件
<template>
<div>
<div>provide inject传递过来的数据: {{ name }}</div>
<div>provide inject传递过来的方法<button @click="say">say</button></div>
</div>
</template>
<script>
export default {
inject: ['name', 'say'],
},
}
</script>
vue3中的使用
provide()
方法,第一个参数需要是一个独一无二的标识(不允许和组件内部的变量重名),第二个参数为祖先组件提供的数据(值/方法)inject()
方法,第一个数据为标识,通过标识获取祖先组件提供的数据,第二个参数为默认值,如果没有从祖先组件获取到标识数据就使用默认值。
js
//祖先组件
<script setup lang="ts">
import {provide] from 'vue'
const age = ref(18);
provide("keyName",age )
</script>
//孙子组件
<script setup lang="ts">
import {inject} from 'vue';
let car = inject('keyName'); // car为ref引用对象
console.log(car.value)
const updateCar = ()=>{
car.value = '自行车';
}
</script>
说明
1.上述案例中,孙子组件中接受的数据和祖先组件注入的数据指向同一个ref引用对象。所以在孙子组件中修改car ,祖先组件中同名数据也会被修改。在祖先组件中修改值,孙祖组件中也会响应。
2.在模板中使用,可以直接使用参数标识
js
<script setup lang="ts">
import {inject} from 'vue';
</script>
<template>
<span>{{keyName}}</span>
</template>