# vue组件应用和数据通信
<!--父组件-->
<template>
<div id="index">
<personinfo
:isdisplay="isdisplay"
@eswitch="doswitch"
>
</personinfo>
</div>
</template>
<script>
import Personinfo from "./Personinfo.vue"
export default {
data() {
return {
isdisplay:false,
}
},
components:{
Personinfo,
},
methods:{
doswitch(value){
this.isdisplay=value;
},
},
}
</script>
<!--子组件-->
<template>
<div v-show="isdisplay">
</div>
</template>
<script>
export default {
props:['isdisplay'],
methods:{
closemes(){
this.$emit('eswitch',false)
},
}
}
</script>
# vue中组件通信的方式
- 父传子
props;子组件通知父组件$emit vuex- 父组件通过获取子节点的引用
this.refs.xx.childmethods,子组件可以this.$parent.fathermethods,注意时间,父组件如果调用子组件的方法,不要不要选择在created的时候,而是应该放在mouted时候,而子组件调用父组件的方法可以写在created时候,类似koa的洋葱模型。 - let
bus= new Vue() - 父组件调用
children,但是不保证加载顺序,有问题,可以适用一个子组件,同时vue3移除该操作 provide/inject隔代传参兄弟组件通信:依赖this.$parent.$on 和 this.$parent.$emit$attrs 和 $listeners:当传给子组件的内容没有被props接受,可以使用$attrs进行处理,如果还要传给孙子组件的话,如果值比较多,可以使用v-bind='$attrs'进行类似解构传给孙子组件- slot插槽
- vue1版本有$dispatch / $broadcast;虽然已经被移除,但是仍然可以自定义编写这个功能,很多第三方UI库如 Mint UI、Element UI 和 iView 等,可以解决父子组件、嵌套父子组件的通信。
核心是向上寻找
# 父子组件
- 属性props
- 引用refs(注意如果要使用子元素中的data,要切记使用时间)
- 子组件chidren
// child
props: { msg: String }
// parent
<HelloWorld msg="Welcome to Your Vue.js App"/>
// parent
<HelloWorld ref="hw"/>
this.$refs.hw.xx = 'xxx'
// parent
created(){
console.log(this.$refs.t)//undefined 还没绑定el
console.log(this.$children[0])//undefined 子组件还未加载到父组件中
// this.$refs.t.we=100000
},
mounted(){
console.log(this.$children[0].we=30//可以修改,但是children如果多个加载顺序是不固定的
// this.$refs.t.we=100000
},
//子组件的data数据在父组件的created的时候获取不到,在mounted时候才可以获取,也类似koa2中的洋葱圈模型
created(){
this.init()
console.log(this.$refs.newTenant,0) // undefined 0
console.log(this.$refs.newTenant.visible,1)//Error in created hook: "TypeError: Cannot read property 'visible' of undefined"
},
mounted(){
console.log(this.$refs.newTenant.visible,2)//可以成功获取
},
父组件可以调用子组件方法,但是同时传递数据给子组件时,会获取不及时,使用setTimeout处理调用的子组件方法可以解决
在Vue3中已经移除了$children的属性,所以不可以使用了。
- 子组件首先要引入父组件,并且要注册,然后展示
1. 引入子组件
2. 注册子组件
3. 在template中使用子组件
4. 父组件给子组件传值
5. 子组件接受并应用
6. 通过事件等方式传递信息给父组件
7. 父组件接受并且处理
须知
父组件甚至还可以把自己的方法传给子组件。
<Recivephone
v-if="isclosephone"
:iscloseson='closephone'
>
</Recivephone>
......
closephone(){
this.isclosephone=false;
},
---------------------------
props:['iscloseson'],
......
close(){
this.iscloseson()
}
# ref $parent和$root
在组件中想要直接获取到元素对象或者子组件实例,这个时候,我们可以给元素或者组件绑定一个ref的attribute属性;
组件实例有一个$refs属性:它一个对象Object,持有注册过 ref attribute 的所有 DOM 元素和组件实例
<template>
<div>
<!-- 绑定到一个元素上 -->
<h2 ref="title">哈哈哈</h2>
<!-- 绑定到一个组件实例上 -->
<nav-bar ref="navBar"></nav-bar>
<button @click="btnClick">获取元素</button>
</div>
</template>
<script>
import NavBar from './NavBar.vue';
export default {
components: {
NavBar
},
methods: {
btnClick() {
console.log(this.$refs.title);
console.log(this.$refs.navBar.message);
this.$refs.navBar.sayHello();
// $el
console.log(this.$refs.navBar.$el);
}
}
}
</script>
- 在子组件中可以通过$parent来访问父元素/$root根元素。【耦合性问题】
console.log(this.$parent);
console.log(this.$root);
# emits vue3验证抛出的事件
与 prop 类型验证类似,如果使用对象语法而不是数组语法定义发出的事件,则可以对它进行验证。
要添加验证,请为事件分配一个函数,该函数接收传递给 $emit 调用的参数,并返回一个布尔值以指示事件是否有效。emit事件依然会触发,只不过控制台会有warning
app.component('custom-form', {
emits: {
// 没有验证
click: null,
// 验证 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm(email, password) {
this.$emit('submit', { email, password })
}
}
})
如果不需要验证的,也可以写成emits:['a','b']
# props
props除了最简单的数组写法,也可以换成对象写法,对象写法还可以升级成多种校验和默认值是否为true等!
由于vue-loader处理,在子组件上写:aaBc这种依然不会报错,不过不推荐
- 如果是对象或数组,需要返回这个默认值而不是直接赋值(直接赋值的话多个子组件会出现对象的引用赋值问题,造成数据的错误)
props: {
userobj: {
type: Object,
default: function() {
return {
// 费用类型id
itemId: '',
// 小区id
orgId: '',
// 当前用户总表id
id: ''
}
}
}
},
- validator条件返回false时,会在控制台抛出warning
props: {
content: {
type: Number,
validator: function(value) {
return value < 1000;
},
default: function() {
return 456;
}
}
},
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 16</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
var a= Vue.component('counter', {
props: ['count'],
template: `<div @click="count += 1">{{count}}</div>`
});
const app = new Vue({
el:"#root",
data() {
return { num: 1 }
},
components:{
'counter':a
},
template: `
<div>
<counter :count="num" />
<counter :count="num" />
<counter :count="num" />
</div>
`
});
</script>
</html>
相对于vue2的props单向数据流设计,vue3则是更加严格,虽然vue2会在控制台报错,但是如果真的修改了props也可以在页面上发生变化,而vue3则会禁止更改,同时在控制台抛出错误.代码如下。
[Vue warn]: Attempting to mutate prop "count". Props are readonly.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 16</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// v-bind="params"
// :content="params.content" :a="params.a" :b="params.b" :c="params.c"
// 属性传的时候,使用 content-abc 这种命名,接的时候,使用 contentAbc 命名
const app = Vue.createApp({
data() {
return { num: 1 }
},
template: `
<div>
<counter :count="num" />
<counter :count="num" />
<counter :count="num" />
</div>
`
});
app.component('counter', {
props: ['count'],
mounted(){
setInterval(()=>{
this.count=this.count+4
},500)
},
template: `<div @click="count += 1">{{count}}</div>`
});
const vm = app.mount('#root');
</script>
</html>
警告
注意在 JavaScript 中对象和数组是通过 引用传入 的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态,且 Vue 无法为此向你发出警告。作为一个通用规则,应该 避免修改任何 prop,包括对象和数组 ,因为这种做法无视了单向数据绑定,且可能会导致意料之外的结果。
<template>
<div class="home">
{{myarr}}
{{t}}
--------------
<T1 :arr='myarr' :t='t'/>
</div>
</template>
<script>
import T1 from './com/Children1.vue'
export default {
name: 'Home',
components: {
T1
},
data(){
return {
myarr:[1],
t:1
}
}
}
</script>
<template>
<div>
{{arr}}
-{{t}}
<el-button @click='add'>add</el-button>
</div>
</template>
<script>
export default {
props:{
arr:Array,
t:Number
},
methods:{
add(){
this.arr.push(11)
this.t++
}
}
}
</script>
# 传入一个对象的所有 property
如果想要将一个对象的所有 property 都作为 prop 传入,可以使用不带参数的 v-bind='xxx' (用 v-bind 代替 :prop-name)。例如,对于一个给定的对象 post:
post: {
id: 1,
title: 'My Journey with Vue'
}
下面的模板:
<blog-post v-bind="post"></blog-post>
等价于:
<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>
const app = Vue.createApp({
data() {
return {
params1:{
a:1,
b:2,
c:3,
count:4
}
}
},
template: `
<div>
<counter v-bind="params1" />
</div>
`
});
app.component('counter', {
props: ['count','a'],
template: `<div @click="count += 1">{{count}}-{{a}}</div>`
});
# Non-prop 属性
Attribute 继承 当组件返回单个根节点时,非 prop 的 attribute 将自动添加到根节点的 attribute 中。例如,在 date-picker 组件的实例中:
app.component('date-picker', {
template: `
<div class="date-picker">
<input type="datetime-local" />
</div>
`
})
如果我们需要通过 data-status attribute 定义 <date-picker> 组件的状态,它将应用于根节点 (即 div.date-picker)。
<!-- 具有非 prop 的 attribute 的 date-picker 组件-->
<date-picker data-status="activated"></date-picker>
<!-- 渲染后的 date-picker 组件 -->
<div class="date-picker" data-status="activated">
<input type="datetime-local" />
</div>
同样的规则也适用于事件监听器:
<date-picker @change="submitChange"></date-picker>
app.component('date-picker', {
created() {
console.log(this.$attrs) // { onChange: () => {} }
}
})
当一个具有 change 事件的 HTML 元素作为 date-picker 的根元素时,这可能会有帮助。
app.component('date-picker', {
template: `
<select>
<option value="1">Yesterday</option>
<option value="2">Today</option>
<option value="3">Tomorrow</option>
</select>
`
})
在这种情况下,change 事件监听器将从父组件传递到子组件,它将在原生 <select> 的 change 事件上触发。我们不需要显式地从 date-picker 发出事件:
<div id="date-picker" class="demo">
<date-picker @change="showChange"></date-picker>
</div>
const app = Vue.createApp({
methods: {
showChange(event) {
console.log(event.target.value) // 将打印所选选项的值
}
}
})
禁用 Attribute 继承 如果你不希望组件的根元素继承 attribute,可以在组件的选项中设置 inheritAttrs: false。
禁用 attribute 继承的常见场景是需要将 attribute 应用于根节点之外的其他元素。
通过将 inheritAttrs 选项设置为 false,你可以使用组件的 $attrs property 将 attribute 应用到其它元素上,该 property 包括组件 props 和 emits property 中未包含的所有属性 (例如,class、style、v-on 监听器等)。
使用上一节中的 date-picker 组件示例,如果需要将所有非 prop 的 attribute 应用于 input 元素而不是根 div 元素,可以使用 v-bind 缩写来完成。
app.component('date-picker', {
inheritAttrs: false,
template: `
<div class="date-picker">
<input type="datetime-local" v-bind="$attrs" />
</div>
`
})
有了这个新配置,data-status attribute 将应用于 input 元素!
<!-- date-picker 组件使用非 prop 的 attribute -->
<date-picker data-status="activated"></date-picker>
<!-- 渲染后的 date-picker 组件 -->
<div class="date-picker">
<input type="datetime-local" data-status="activated" />
</div>
当父组件没给子组件传值,而是在父组件把写在子组件上的信息当做属性attr传递到子组件。子组件可以使用v-bind="$attrs"全部接受,也可以自定义名:xx='$attrs.yy'接收。
子组件可别出现props和绑定在父组件里子组件上名字一样的内容,如props:['msg'],那么msg则就不属于Non-prop属性了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 17</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// Non-prop 属性
const app = Vue.createApp({
template: `
<div>
<counter msg="hello" msg1="hello1" />
</div>
`
});
app.component('counter', {
// inheritAttrs: false,
mounted() {
console.log(this.$attrs.msg);
},
template: `
<div :ttt="$attrs.msg">Counter</div>
<div v-bind="$attrs">Counter</div>
<div :msg1="$attrs.msg1">Counter</div>
`
});
const vm = app.mount('#root');
</script>
</html>
<div>
<div ttt="hello">Counter</div>
<div msg="hello" msg1="hello1">Counter</div>
<div msg1="hello1">Counter</div>
</div>
<T1 :arr='myarr' :t='t' class="home" />
.home{
color:red
}
子元素
inheritAttrs: false//设置了不继承,就不会显示红色,不设置,默认会继承这个.home的信息
不管设不设置inheritAttrs,$attrs都能拿到对应的non-props属性
# Vue click.native
- 如果在组件上直接使用click,需要加和是哪个@click.native来区分
- 如果子组件设置inheritAttrs: false,点击子组件事件不生效
- 但是子组件通过this.$attrs.onClick()依然可以触发这个事件
<template>
<div >
{{myarr}}
{{t}}
--------------
<T1 :arr='myarr' :t='t' class="home" @click.native='xx()'/>
</div>
</template>
<script>
import T1 from './com/Children1.vue'
export default {
name: 'Home',
components: {
T1
},
data(){
return {
myarr:[1],
t:1
}
},
methods:{
xx(){
console.log('skt')
}
}
}
</script>
<style type="text/css">
.home{
color:red
}
</style>
<template>
<div>
{{arr}} - {{t}}
<el-button @click='add'>add</el-button>
</div>
</template>
<script>
import { ElMessage } from 'element-plus'
export default {
// inheritAttrs: false,
props:{
arr:Array,
t:{
validator(value){
console.log(value)
return value > 10
},
default:3
}
},
methods:{
add(){
this.arr.push(11)
console.log(this.$attrs)
this.$attrs.onClick()
}
}
}
</script>
<style>
</style>
# 兄弟组件交互
通过共同的祖辈组件搭桥,$parent或$root。
// brother1
this.$parent.$on('foo', handle)
// brother2
this.$parent.$emit('foo')
//组件1
<template>
<div @click="$parent.$emit('tell')">
</div>
</template>
//子组件2
<template>
<div id="ti1">
{{n}}
</div>
</template>
<script>
export default{
data(){
return {
n:0
}
},
mounted(){
this.$parent.$on("tell",()=>{
this.n++
})
}
}
</script>
# provide和inject
这种方式可以避免在使用props传值时,必须将每一个属性都传递给子组件的写法,当使用的公共组件不确定会被传递什么值的时候,使用这种写法非常方便。
provide:是一个对象,或者是一个返回对象的函数。里面就包含要给后代的属性和属性值。
inject:一个字符串数组,或者是一个对象。属性值可以是一个对象,包含from和default默认值。
//父组件示例
<template>
<div>
<childOne></childOne>
</div>
</template>
<script>
import childOne from '../components/test/ChildOne'
export default {
name: "Parent",
provide: { //重要一步,在父组件中注入一个变量
for: "demo"
},
components:{
childOne
}
}
</script>
//子组件示例,这个不管嵌套了多少层,都能请求到父组件中注册的变量
<template>
<div>
{{for}}
</div>
</template>
<script>
export default {
name: "childOne",
inject: ['for'], //子孙组件中使用inject接住变量即可
}
</script>
- 如果provide中的值需要取data里的值,则需要改写成函数显示传递,且provide传给后代组件的值,如果是简单的值,后代只接受最初的值,变动了监听不到变化!如果是对象,则可以监听变化而随之改变!!!
data() {
return { count: 1}
},
// provide:{
// count:this.count,
// }
provide() {
return {
count: this.count,
}
},
inject:['a']
---------------
inject:{
'ss':'a'//别名ss,解决重名问题
}
------------
inject:{
'ss':{
from:'a',
default:'xxx'//设置默认值
}
}
# vue3 provide 和inject
利用ref和reactive,可以实现响应式的驱动上层组件传递过来的数据。
<template>
<div >
<div id="app">
333
<T1></T1>
<el-button @click='add'>add</el-button>
{{todos}}
</div>
</div>
</template>
<script>
import T1 from './com/Children1.vue'
import { provide, reactive, ref } from 'vue'
export default {
name: 'Home',
components: {
T1
},
setup() {
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
provide('location', location)
provide('geolocation', geolocation)
function add(){
location.value =10000
}
return {add}
}
}
</script>
<template>
<div>
{{userLocation}}-{{userGeolocation}}
</div>
</template>
<script>
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>
<style>
</style>
- 如果要使用到data中数据,需要改成函数模式,而且,为了保证length也是可以响应的,借助import {computed} from 'vue'将length包装成ref对象

# @hook 父组件监听子组件的生命周期
比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
this.$emit("mounted");
}
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数 ...');
},
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
# @hook应用场景
不干扰子组件的逻辑,增加的逻辑也和组件本身的功能好不关联。最好的办法就是使用 v-on="hook:xxx" 的方式:
<v-chart
@hook:mounted="loading = false"
@hook:beforeUpdated="loading = true"
@hook:updated="loading = false"
:data="data"
/>
# vue3 之provide和inject
- 可以传递方法给后代去调用执行,同时如果不想后代自己通过获取到前辈的值后乱修改,可以加上readonly属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 41</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// provide, inject
// dom ref
const app = Vue.createApp({
setup() {
const { provide, ref, readonly } = Vue;
const name = ref('dell');
provide('name', readonly(name));
provide('changeName', (value) => {
name.value = value;
});
return { }
},
template: `
<div>
<child />
</div>
`,
});
app.component('child', {
setup() {
const { inject } = Vue;
const name = inject('name');
const changeName = inject('changeName');
const handleClick = () => {
changeName('lee');
}
return { name, handleClick }
},
template: '<div @click="handleClick">{{name}}</div>'
})
const vm = app.mount('#root');
</script>
</html>
# $attr and $listeners
# $props
子组件可以将所有props传递给自己的子组件,也可以传递props中的部分值
<User v-bind="$props"/>
<template>
<div>
<p>子代组件</p>
{{k1}} {{mes.g}}
<Childson :p="$props"></Childson>
</div>
</template>
<script>
import Childson from "@/components/Childson"
export default{
props:{
k1:Number,
mes:{
type:Object,
default:{t:10000}
}
}
}
</script>
<template>
<div>孙子组件:{{this.p.k1}}</div>
</template>
<script>
export default{
props:{
p:Object
},
mounted() {
console.log(this.p)
}
}
</script>
TIP
组件的本质 vue中的组件经历如下过程 组件配置 => VueComponent实例 => render() => Virtual DOM=> DOM 所以组件的本质是产生虚拟DOM
# vue3组件通信的修改
provide:
- 在vue3中
简单值也会响应式的变化(依赖ref/reactive或者利用computed[options API写法需要引入vue里的conputed包裹]) - 而vue2中,如果传递的是简单值,只会取最初值,后面进行修改,
是不会变化的,如果传递的是对象,才会变动。而且传递inject接受的值 - 在vue2中,如果是简单类型,自己修改,不会改变原始数据,当时对象数据的话,则是可以影响到上级元素的值的变化。vue3简单和对象是都会变化,当然如果为了保护原始数据不被后代随意修改,可以设置为readonly传递。当然祖辈还是可以去修改这个值的,后代也可以同步变化。[ 2022-08-18认证 ]
- 在vue3中
emit通知组件变更,需要emits: ['abcd'],vue2则可以直接使用this.$emit()
不再可以利用 new Vue()创建事件总线,如有需要,引入插件mitt或者 tiny-emitter
vue2的父子组件通信可以借助this.$parent.$emit/this.$parent.$on,但是$on,$off在vue3中已经被移除
vue3使用ref调用子元素方法也被修改
<template>
<old1 ref='old1v'/>
</template>
<script setup lang="ts">
import old1 from './old1.vue'
import {ref} from 'vue'
let old1v = ref()
//old1v.value.init() //注意setup执行时间,子元素还没加载,不能调用这个方法
let say1 =() =>{
old1v.value.init()
}
</script>
- listeners 现在作为 $attrs 的一部分传递,可以将其删除;而vue2中 v-on="$listeners": 将父组件标签上的自定义事件向下传递,其子组件可以直接通过this.$emit(eventName)的方式调用。
- 祖辈
<template>
<div>
<children test="123" :name="name" :age="age" v-on:start1="say1" @start2="say2" ></children>
</div>
</template>
<script setup lang="ts">
import children from './children.vue'
import {ref} from 'vue'
let name =ref('传给父组件的值')
let age = ref(20)
let say1 =() =>{
console.log('第一个。。。。。');
}
let say2=() =>{
console.log('第二个。。。。。');
}
</script>
- 父辈
<template>
<div>
<h3>父组件</h3>
<div>组件名上绑定的非props特性($attrs): {{ $attrs }}</div>
<!--vue2$listeners可以将父级的方法传递给孙子组件,vue3合并到attrs中-->
<app-child v-bind.$attrs="$attrs" v-bind="$props"></app-child>
</div>
</template>
<script setup lang="ts">
import AppChild from './third.vue';
import { defineProps,onMounted } from 'vue';
import type{PropType} from 'vue'
let emit =defineEmits(['start1'])
const props = defineProps({
name:String as PropType<string>,
age:Number as PropType<number>
})
onMounted(()=>{
emit('start1')
})
</script>
- 孙辈
<template>
<div>
<h3>子组件</h3>
<div>父组件传递过来的名称: {{name}}</div>
<div>父组件传递过来的年龄: {{age}}</div>
{{$attrs}}
</div>
</template>
<script setup lang="ts">
import { defineProps,toRefs,onMounted } from 'vue';
import type{PropType} from 'vue'
let emit =defineEmits(['start2'])
const props = defineProps({
name:String as PropType<string>,
age:Number as PropType<number>
})
let {name,age} =toRefs(props)
onMounted(()=>{
setTimeout(() => {
emit('start2')
}, 500);
})
</script>
# mitt
cnpm i mitt -S
import mitt from 'mitt';
const emitter = mitt();
export default emitter;
import emitter from './utils/eventbus';
emitter.emit("why", {name: "why", age: 18});
emitter.on("why", (info) => {
console.log("why:", info);
});
emitter.on("kobe", (info) => {
console.log("kobe:", info);
});
// 参数变成两个,第一个是类型
emitter.on("*", (type, info) => {
console.log("* listener:", type, info);
})
- 取消监听的两种方式
