Skip to content

Latest commit

 

History

History
659 lines (623 loc) · 17.6 KB

13_组件间通信(父传子 子传父).md

File metadata and controls

659 lines (623 loc) · 17.6 KB

组件间通信

  1. 上面的嵌套逻辑如下,他们存在如下的关系: APP组件是Header、Main、Footer组件的 父组件 Main组件是Banner、ProductList组件的 父组件
  2. 在开发过程中,我们会经常遇到需要 组件之间相互进行通信
    • 比如 App可能使用了多个Header ,每个地方的 Header展示的内容不同 ,那么我们需要使用者 传递给Header一些数据来让其进行展示
    • 又比如我们在Main中一次性 请求了Banner数据和ProductList数据 ,那么就需要传递给它们来进行展示
    • 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件
  3. 父子组件之间如何进行通信呢?
    • 父组件传递给子组件:通过props属性
    • 子组件传递给父组件:通过$emit触发事件

父组件传值给子组件

ShowInfo.vue

<template>
  <!--问题一:1. 但是如果只是这样写死的话,只能展示一个人的信息 -->
  <div class="infos">
    <div>身高:188</div>
    <div>体重:135</div>
    <div>姓名:zql</div>
  </div>
</template>

<script>
export default {
  // 问题一:2.另外一种想法就是写在data里面,但是data也是相当于给内容写死了
  // 所以我们希望的就是,App在用我们组件的时候,数据由App决定
  // 父组件传值给子组件
}
</script>

<style scoped>
</style>
什么是Props呢?
  • Props是你可以在组件上 注册一些自定义的 attribute
  • 父组件给这些attribute赋值,子组件通过attribute的名称来获取到对应的值
Props有两种常见的用法
  • 方式一:字符串数组,数组中的字符串就是attribute的名称
  • 方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等

如何实现父组件传值给子组件

第一种方式:数组语法 App.vue

<template>
  <show-info name="zql"
             age="18"
             height="1.88" />
  <show-info name="james"
             age="37"
             height="1.88" />
</template>

<script>
import ShowInfo from './ShowInfo.vue';
export default {
  components: {
    ShowInfo
  }
}
</script>

<style scoped>
</style>

ShowInfo.vue

<template>
  <!--问题二: 2.1 这里怎么拿到父组件传入的数据呢? -->
  <div class="infos">
    <div>姓名:{{name}}</div>
    <div>年龄:{{age}}</div>
    <div>身高:{{height}}</div>
  </div>
</template>

<script>
export default {
  /*
  问题2:2.2 这里如何接收到父组件传过来的值呢?
  以前我们这里定义数据是直接写在 data(){}函数中返回的对象,为什么data必须是个对象呢?
  因为在创建数据的过程中是相当于根据data构造器来new出来一个对象,数据存储在对象中
  这样才能保证没个data中的数据都存在一块独立的空间,如果是对象的话,那么如果两份数据都存在这个对象中
  相当于是直接赋值的地址,那么两块数据都存储在同一块空间中,就有可能会造成冲突
	
  我们这里是用一个 props来接收父组件传递过来的属性,这里用一个数组来接收,
  这样上面既可以访问 data中的数据,也可以访问 props 中的数据
  */
  props: ["name", "age", "height"]
}
</script>

<style scoped>
</style>

第二种方式:对象语法

  • 数组语法的弊端
    • 不能对数据类型进行验证,比如需要的是数字类型,但是传过来的值却是个字符串
    • 没有默认值的,比如我们希望当没有传入数据的时候来展示默认值

App.vue

<template>
  <show-info name="zql"
             :age="18"
             :height="1.88" />
  <!-- 传入Number数字类型的时候,只需要这里加上:即可,
	加上:就变成了JS代码,JS代码里面写30对应的就是数字类型,如果不写就是字符串 
	:age="18" 数字 :age="'18'" 字符串
	不加:的话就是我们平常写的 name="wc"字符串 加上了:就是变成了vue中的特定语法,后面的就变成数据了
	-->
  <show-info name="james"
             :age="37"
             :height="1.88" />
  <!-- 不传入值,就会使用默认值 -->
  <show-info />
</template>

<script>
import ShowInfo from './ShowInfo.vue';
export default {
  components: {
    ShowInfo
  }
}
</script>

<style scoped>
</style>

ShowInfo.vue

<template>
  <div class="infos">
    <div>姓名:{{name}}</div>
    <div>年龄:{{age}}</div>
    <div>身高:{{height}}</div>
  </div>
</template>

<script>
export default {
  /* 3.2第二种:对象语法 但是这种是没有默认值的
  props: {
    name: String,
    age: Number,
    height: Number
  }
  */
  props: {
    name: {
      type: String,
      default: "我是默认的name"
    },
    age: {
      type: Number,
      default: 0
    },
    height: {
      type: Number,
      default: 0
    }
  }
}
</script>

<style scoped>
</style>
那么父传子的时候type的类型都可以是哪些呢?
  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
注意细节
<script>
export default {
  /* 3.2第二种:对象语法 但是这种是没有默认值的
  props: {
    name: String,
    age: Number,
    height: Number
  }
  */
  props: {
    name: {
      type: String,
      default: "我是默认的name"
    },
    age: {
      type: Number,
      default: 0
    },
    height: {
      type: Number,
      default: 0
    },
    friend: {
      type: Object,
      // 这里需要注意的是,如果返回值的类型是对象,那么返回值就是函数
      default: () => ({ name: "james" })
    }
  }
}
</script>
对象类型的其他写法
props:{
	messageInfo:String,
	// 基础的类型检查('null'和'undefined'会通过任何类型验证)
	propA:Number,
	// 多个可能的类型
	propB:[String,Number],
	// 必填的字符串
	propC:{
		type:String,
		required:true
	},
	// 拥有默认值的数字
	propD:{
		type:Number,
		default:100
	}
}

// 带有默认值的对象
propE:{
	type:Object,
	default(){
		return {message:'hello'}
	}
}
// 箭头函数形式
propF:{
	type:Object,
	default()=>({name:'james'})
}
// 自定义验证函数
propG:{
	validator(value){
	// 这个值必须是匹配下列字符串中的一个
	return ['success','warning','danger'].includes(value)
	}
}
// 具有默认值的函数
propH:{
	type:Function,
	// 与对象或数组默认值不同,这不是一个工厂函数,这是一个用作默认值的函数
	default(){
		return 'default function'
	}
}

ShowInfo.vue

<script>
export default {
  props: {
    name: {
      type: String,
      default: "我是默认的name"
    },
    age: {
      type: Number,
      default: 0
    },
    height: {
      type: Number,
      default: 0
    },
    // 重要的原则,对象类型写默认值的时候,需要编写default的函数,函数返回默认值
    friend: {
      type: Object,
      // 这里需要注意的是,如果返回值的类型是对象,那么返回值就是函数
      default: () => ({ name: "james" })
    },
    hobbies: {
      // 数组类型也是对象 
      type: Array,
      default: () => ["篮球", "rap", "唱跳"]
    }
  }
}
</script>
Prop的大小写命名
  • HTML中的attribute名是大小写不敏感的,所以浏览器会把所有大写字符解释成小写字符
  • 这意味着当你使用 DOM 中的模板时,camelCase(驼峰命名法)的 prop 名需要使用其等价的kebab-case(短横线分隔命名)命名

非Prop的Attribute

image-20221206141629976

  • 什么是非Prop的Attribute呢?
    • 当我们传递给一个组件的某个属性,但是该属性并没有定义对应的props或者emits时就称之为非Prop的Attribute
    • 常见的包括 class、style、id属性等
  • attribute继承
    • 当组件有单个根节点时,非Prop的Attribute将会自动添加到根节点的Attribute中
  • 禁用非Prop的Attribute
  • 如果我们不希望组件的根元素继承attribute可以在组件中设置 inheritAttrs:false
    • 禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素
    • 我们可以通过$attrs来访问所有的非props的attribute

App.vue

<template>
  <show-info name="zql"
             :age="18"
             :height="1.88"
             ShowHobbies="['唱歌']"
             class="active"
             address="郑州市" />
  <show-info />
</template>

<script>
import ShowInfo from './ShowInfo.vue';
export default {
  components: {
    ShowInfo
  }
}
</script>

<style scoped>
</style>

ShowInfo.vue

<template>
  <div class="infos">
    <div>姓名:{{name}}</div>
    <div>年龄:{{age}}</div>
    <div>身高:{{height}}</div>
    <div>爱好:{{ShowHobbies}}</div>
  </div>
</template>

<script>
export default {
  props: {
    name: {
      type: String,
      default: "我是默认的name"
    },
    age: {
      type: Number,
      default: 0
    },
    height: {
      type: Number,
      default: 0
    },
    friend: {
      type: Object,
      default: () => ({ name: "james" })
    },
    ShowHobbies: {
      type: Array,
      default: () => ["篮球", "rap", "唱跳"]
    }
  }
}
</script>

<style scoped>
</style>
主要体现
// 比如上面的address class="active"属性是一个非prop的attribute,那么这个属性就会被默认添加到子组件的根元素上,F12打开之后在元素上的显示情况
<div class="infos active" address="郑州市">
  1. 那么我们就可以设置 inheritAttrs:false
export default{
  // 设置禁用将,父组件传来的值挂载到子组件的根节点上面,比如这里就是 info 就是根节点
  inheritAttrs:false,
  props:{
    type:Number,
    default:10
  }
}
  1. 但是如果我们需要的时候,如何取到这些值?
<template>
  <div class="infos">
    <div :class="$attrs.class">姓名:{{name}}</div>
    <div :class="$attrs.address">年龄:{{age}}</div>
    <div>身高:{{height}}</div>
    <div>爱好:{{ShowHobbies}}</div>
  </div>
</template>

<script>
export default {
  props: {
    name: {
      type: String,
      default: "我是默认的name"
    },
    age: {
      type: Number,
      default: 0
    },
    height: {
      type: Number,
      default: 0
    },
    friend: {
      type: Object,
      default: () => ({ name: "james" })
    },
    ShowHobbies: {
      type: Array,
      default: () => ["篮球", "rap", "唱跳"]
    }
  }
}
</script>

<style scoped>
</style>

2.1 F12上的元素显示

<div class="infos active" address="郑州市">
<div class="active">姓名:zql</div>
<div class="郑州市">年龄:18</div>
<div>身高:1.88</div>
<div>爱好:['唱歌']</div>
</div>
  1. 但是如果ShowInfo里面有两个根节点怎么办?设置v-bind="$attrs" 选择绑定到哪个身上
<template>
  <div class="infos">
    <div :class="$attrs.class">姓名:{{name}}</div>
    <div :class="$attrs.address">年龄:{{age}}</div>
    <div>身高:{{height}}</div>
    <div>爱好:{{ShowHobbies}}</div>
  </div>
  <div class="others"
       v-bind="$attrs">
  </div>
</template>

3.1 F12在元素上面的显示结果

<div class="infos">
<div class="active">姓名:zql</div>
<div class="郑州市">年龄:18</div>
<div>身高:1.88</div>
<div>爱好:['唱歌']</div>
</div>
// 设置了v-bind="$attrs"之后,那么没有被接收的参数(比如 设置class类)就会绑定到设定v-bind="$attrs"的根节点身上
<div class="others active" address="郑州市"></div>

子组件传递给父组件

  1. 什么情况下子组件需要传递内容到父组件呢?
  • 子传父主要就是触发了事件,然后做出的响应
  • 当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容
  • 子组件有一些内容想要传递给父组件的时候
  1. 我们如何完成上面的操作呢?
  • 首先,我们需要在子组件中定义好在某些情况下触发的事件内容名称
  • 其实,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中
  • 最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件
计数器例子

App.vue

<template>
  <div class="app">
    <h2>当前计数:{{counter}}</h2>
    <!-- 注意点:这里的自定义事件是不需要传参的 -->
    <add-counter @add="addBtnClick"></add-counter>
    <sub-counter @sub="subBtnClick"></sub-counter>
  </div>
</template>
<!-- 
1. 第一步:
当子组件中发生点击事件之后,那么父组件中的counter就要发生改变 
子组件需要先监听事件的发生 methods:{btnClick(){}}
-->
<!-- 
3. 第三步: 
普通浏览器发出的事件是@click="addEvent"
那么组件内部发生的事件是什么呢? 
v-on:add="addBtnClick" -> 语法糖:@add="addBtnClick"
-->
<script>
import AddCounter from './AddCounter.vue'
import SubCounter from './SubCounter.vue'
export default {
  components: {
    AddCounter,
    SubCounter
  },
  data () {
    return {
      counter: 0
    }
  },
  methods: {
    addBtnClick (count) {
      this.counter += count
    },
    subBtnClick (count) {
      this.counter -= count
    }
  }
}
</script>

<!-- scope样式只在当前组件上面有效 -->
<style scoped>
</style>

加操作按钮组件AddCounter.vue

<template>
  <div class="add">
    <button @click="btnClick(1)">+1</button>
    <button @click="btnClick(5)">+5</button>
    <button @click="btnClick(10)">+10</button>
  </div>
</template>
<!--
2. 第二步: 
既然子组件已经可以监听到数据发生改变了,
那么该怎么传递给父组件,修改父组件里面的counter呢? 
***自定义事件***
子组件可以发出一个自定义事件,第一个参数是我们自定义的名称,第二个是传递的参数 
this.$emit("add",count)
-->
<script>
export default {
  methods: {
    btnClick (count) {
      console.log("btnClick", count)
      // 子组件可以发出一个自定义事件,父组件接收
      this.$emit("add", count)
    }
  }
}
</script>

<!-- scope样式只在当前组件上面有效 -->
<style scoped>
</style>

减操作组件SubCounter.vue

<template>
  <div class="sub">
    <button @click="subBtnClick(1)">-1</button>
    <button @click="subBtnClick(5)">-5</button>
    <button @click="subBtnClick(10)">-10</button>
  </div>
</template>

<script>
export default {
  methods: {
    subBtnClick (count) {
      this.$emit("sub", count)
    }
  }
}
</script>

<!-- scope样式只在当前组件上面有效 -->
<style scoped>
</style>
子传父组件过程总结
1. 按钮发生点击 
2. 子组件通过点击事件,发出自定义事件 this.$emit("自定义事件名",参数) 
3. 父组件通过给引进来的子组件标签上面添加 
<AddCounter @自定义事件名="父组件事件"></AddCounter>触发父组件事件 
比如 <add-counter @add="addBtnClick"><add-counter>
4. 触发父组件事件addBtnClick对父组件里面的数据counter进行修改

Vu3新增emits:[" "]

  1. 第一种方式:写成数组语法 最多人用
<template>
  <div class="add">
    <button @click="btnClick(1)">+1</button>
    <button @click="btnClick(5)">+5</button>
    <button @click="btnClick(10)">+10</button>
  </div>
</template>
<script>
export default {
  // 这样来扫描的时候,只需要来最外层来扫描一下即可,并且这样写之后App.vue引用会有提示
  emits: ["add"],
  methods: {
    btnClick (count) {
      console.log("btnClick", count)
      // 子组件可以发出一个自定义事件,父组件接收
      this.$emit("add", count)
    }
  }
}
</script>
// scope样式只在当前组件上面有效
<style scoped>
</style>
  1. 写成对象语法 了解
<template>
  <div class="add">
    <button @click="btnClick(1)">+1</button>
    <button @click="btnClick(5)">+5</button>
    <button @click="btnClick(10)">+10</button>
  </div>
</template>
<script>
export default {
  // 写成对象形式可以进行一些验证的功能
  emits: {
    add: function (count) {
      if (count <= 10) {
        return true
      }
      return false
    }
  },
  methods: {
    btnClick (count) {
      console.log("btnClick", count)
      // 子组件可以发出一个自定义事件,父组件接收
      this.$emit("add", count)
    }
  }
}
</script>
// scope样式只在当前组件上面有效
<style scoped>
</style>