父子组件通信
背景
=======================
父传子 Props
attribute:标签属性,如class、id、style这些都是属性。
作用
多用于,父组件统一从服务器获取数据,然后把部分数据传给子组件使用。而不是子组件再发一次网络请求。
写法
1、字符串数组
简单,但并不能对其进行任何形式的限制
<template>
<div>
<show-message id="abc" class="why" title="哈哈哈" content="我是哈哈哈哈" message-info=""> 这是传字符串数据 </show-message>
<show-message title="呵呵呵" content="我是呵呵呵呵">这是传字符串数据</show-message>
<show-message :title="title" :content="content">这是传变量数据</show-message>
<show-message :title="message.title" :content="message.content">可以传对象中的属性</show-message>
<show-message v-bind="message"> 可以传整个对象 </show-message>
<multi-root-element id="aaaa"></multi-root-element>
</div>
</template>
<script>
import ShowMessage from './ShowMessage.vue';
import MultiRootElement from './MultiRootElement.vue';
export default {
components: { // 父组件内注册子组件
ShowMessage,
MultiRootElement
},
data() {
return {
title: "嘻嘻嘻",
content: "我是嘻嘻嘻嘻",
message: {
title: "嘿嘿嘿",
content: "我是嘿嘿嘿"
}
}
}
}
</script>
<style scoped>
</style>
2、对象类型
可以对传入的数据增加验证和限制,否则会报警告。
子组件
<template>
<div>
<h2 v-bind="$attrs">{{title}}</h2>
<p>{{content}}</p>
</div>
</template>
<script>
export default {
// props: ['title', 'content'] 字符串数组写法
props: {
// 1、字符串
title: String, // 指定类型,父数据传入子组件时,检查数据是否是props给定的类型,否则抛出警告。
// 2、对象
content: {
type: String, // 指定类型,父数据传入子组件时,检查数据是否是props给定的类型,否则抛出警告。
required: true, // 父组件是否必须传给我
default: "123" // 默认值,一般和required 二选一就可以了
},
counter: {
type: Number,
validator: function (value) { // 自定义校验规则,父数据传入子组件时,检查数据是否符合我的规则
return value >= 0
}
},
info: {
type: Object,
default() { // 注意,传对象带有默认值,默认值必须是一个函数,否则组件复用时,多个这个组件会指向同一个对象!
return {name: "why"}
}
/*
default:{name: "why"} // 这种情况,本组件多次复用会指向同一个对象{name: "why"},任意一个修改会影响到其他的
*/
},
}
}
</script>
<style scoped>
</style>
验证数据类型 type
如下子组件里的msg:String 就是验证数据类型,可以验证原生的String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定义构造函数、或上述内容组成的数组。
注意!
要验证,父组件传入的属性,如果要验证数值(Number)、布尔值(Boolean)、数组(Array)、对象(Object),一定得用v-bind绑定,才能传入,直接写会报错的
//age验证Number,一定得用v-bind 绑定传入
<Title1 :msgSend="word" :age="321"></Title1>
//不绑定变量,会报错
<Title1 :msgSend="word" age=321></Title1>
在ts中使用
// 1、需要引入 PropType 类型
import { PropType } from "vue"
// 2、props 属性中,要按下面 as PropType<你自己定义的类型> 的方式使用
props:{
formProps: {
type: Object as PropType<你自己定义的类型>,
default() {
return {}
}
},
}
其他特性
1、传入对象所有的Key:value
父组件
<blog-post v-bind="post"></blog-post>
等价于:
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
2、非Props的属性 与 继承(重要)
禁用属性Attribute继承,如下代码,这样就不会把属性添加到下面根节点的 div 中
<template>
<div>
<h2 v-bind="$attrs">{{title}}</h2>
<p>{{content}}</p>
</div>
</template>
<script>
export default {
inheritAttrs: false, // 设置禁用继承
props: {
// 众多props...........
}
}
</script>
<style scoped>
</style>
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。 这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。 额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。 这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。 注意:在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组(Array)或对对象(Object)的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态 如果非要子组件非要改变父组件传入的数据,可以有2种方法: 2、如果只是对父组件传入的数据进行过滤转换,可以用计算属性computed,只调用转换的方法 一般发送请求给服务器的,都是最外层的那个组件。 现在点击左边的导航栏,右边的同级的组件就会发送变化。过程是点击导航栏后,发送一个事件给最外层的父组件,父组件向服务器请求数据,拿到后父传子给右边显示的组件 子组件通过提交一个this.$emit(“ 事件名 “, 参数 ) 的方法,向父组件提交一个@事件名 的事件,父组件只要捕获这个事件,并关联到自己组件内的一个方法,就可以通过方法获取提交的参数。 注意!子组件传给父组件的参数,如果是数值,会自动变成字符串String,我们自己可以用*1 或者parseInt( ) parseFloat( ) 自己转换 子组件 父组件 子组件 父组件 可以和Vue 3.x普通写法混用,比如父组件普通,子组件组合式API,或者反过来。 子组件 父组件 父组件传过来的 子组件(Vue3 普通写法)
Vue3允许一个.vue文件里面的多个根节点,Vue2不行
3、大小写不敏感
4、单向数据流
1、把父组件传入的数据,赋值给子组件自己的data里的变量,然后再操作<script>
export default{
name: 'Title1',
props: {
msgSend: String,
age:{
type: Number,
default:123,
required:false,
validator: function (value) {
return value >=0
}
}
},
data() {
return {
//把父组件传入的数据,赋值给子组件自己的data里的变量,然后再操作
ageSon:this.age
}
}
};
</script>
<script>
export default{
name: 'Title1',
props: {
msgSend: String,
age:{
type: Number,
default:123,
required:false,
validator: function (value) {
return value >=0
}
}
},
data() {
return {
}
},
//
computed:{
ageSon:function(){
//对父组件传入的数据进行过滤转换,可以用计算属性computed,只调用转换的方法
return this.age.toString()
}
}
};
</script>
=======================
子传父 $emit( )
作用
如何使用
Vue 2.x
<template>
<div>
<el-button v-for="(item,index) in food" @click="clickFood(item)">{{index}}.{{item}}</el-button>
</div>
</template>
<script>
export default{
name: 'Title1',
props: {
},
data() {
return {
food:[
"苹果","香蕉","芒果","桃子"
]
}
},
methods:{
clickFood:function(value){
// 向父组件发送一个事件,事件名为showFood,并且带一个参数
this.$emit("showFood",value);
}
}
};
</script>
<template>
<div>
<div >我是父组件,我点击了{{word}}</div>
<br>
<!--父组件捕获子组件提交的showFood事件,并且绑定给了自己的clickButton 方法-->
<Title1 @showFood="clickButton"></Title1>
</div>
</template>
<script>
import Title1 from '../../components/Title1.vue'
export default {
data() {
return {
word:"默认水果"
};
},
components:{
Title1
},
methods:{
//通过clickButton 方法,获取子组件提交过来的参数,并使用
clickButton:function(x){
// console.log(x,this);
this.word = x;
},
}
};
</script>
Vue 3.x 普通写法
<template>
<div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<input type="text" v-model.number="num">
<button @click="incrementN">+n</button>
</div>
</template>
<script>
export default {
emits: ["add","sub","addN"],
data() {
return {
num: 0
}
},
methods: {
increment() {
console.log("+1");
this.$emit("add");
},
decrement() {
console.log("-1");
this.$emit("sub");
},
incrementN() {
this.$emit('addN', this.num, "why", 18);
}
}
}
</script>
<style scoped>
</style>
<template>
<div>
<h2>当前计数: {{counter}}</h2>
<counter-operation @add="addOne"
@sub="subOne"
@addN="addNNum">
</counter-operation>
</div>
</template>
<script>
import CounterOperation from './CounterOperation.vue';
export default {
components: {
CounterOperation
},
data() {
return {
counter: 0
}
},
methods: {
addOne() {
this.counter++
},
subOne() {
this.counter--
},
addNNum(num, name, age) {
console.log(name, age);
this.counter += num;
}
}
}
</script>
<style scoped>
</style>
Vue 3.x 组合式API写法
<template>
<div>
<button @click="increment"></button>
<button @click="decrement"></button>
</div>
<template>
<script>
export default {
emits:["add","sub","addN"],// 1、注册提交事件
setup(props,ctx){ // setup的第一个参数就是props,第二个参数是上下文对象
const increment = ()=>{
ctx.emit("add") // 2、使用
}
const decrement = ()=>{
ctx.emit("sub") // 2、使用
}
const incrementN() {
ctx.emit('addN', 10, "why", 18); // 2、可以传递参数使用,ctx.emit('父组件事件', 参数)
}
return {
increment,
decrement,
incrementN
}
}
}
</script>
<template>
<div>
<h2>当前计数: {{counter}}</h2>
<counter-operation @add="addOne" @sub="subOne" @addN="addNNum">给子组件传递事件</counter-operation>
</div>
</template>
<script>
import CounterOperation from './CounterOperation.vue';
import {ref} from 'vue';
export default {
components: {
CounterOperation
},
setup(){
let counter = ref(0)
const addOne() {
counter.value++
},
const subOne() {
counter.value--
}
const addNNum(num, name, age) {
console.log(name, age);
counter.value += num;
}
return {
counter,
addOne,
subOne,
addNNum
}
},
}
</script>
<style scoped>
</style>
验证参数
<template>
<div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<input type="text" v-model.number="num">
<button @click="incrementN">+n</button>
</div>
</template>
<script>
export default {
// emits: ["add", "sub", "addN"],
// 对象写法的目的是为了进行参数的验证
emits: {
add: null, // null表示不需要验证
sub: null,
addN: (num, name, age) => { // 传给父组件时需要验证
console.log(num, name, age);
if (num > 10) { // 不管是否验证通过,都会传给父组件
return true
}
return false; // 只是验证失败时,会报警告
}
},
data() {
return {
num: 0
}
},
methods: {
increment() {
console.log("+1");
this.$emit("add");
},
decrement() {
console.log("-1");
this.$emit("sub");
},
incrementN() {
this.$emit('addN', this.num, "why", 18);
}
}
}
</script>
<style scoped>
</style>
=======================
TS 类型