前言
虽然Vue3
出来很久并且非常成熟了,但市面上的Vue
项目中Vue2
依然还是占半壁江山的。小弟当年刚入行时接触就是Vue2
项目,后来Vue3
流行之后慢慢开始在Vue3
和Vue2
项目中反复穿插。因此想通过整合最简单易懂的事例全面总结一下Vue2
的传参方式。
先直入主题列出有哪些传参方式,下面再通过事例一一讲解。
props
(父传子)$emit
与v-on
(子传父)EventBus
(兄弟传参).sync
与update:
(父子双向)v-model
(父子双向)ref
$children
与$parent
$attrs
与$listeners
(爷孙双向)provide
与inject
(多层传参)Vuex
(全局)Vue.prototype
(全局)- 路由
- 浏览器缓存 (全局)
window
(全局)$root
(顶层)slot
(父传子)
一、props(父传子)
思路简述:父组件直接用冒号
:
绑定变量,然后子组件通过props
接收父组件传过来的内容。
父组件代码: 核心代码在第3
行,直接用:message="message"
传参。
<template>
<div>
<child :message="message" />
</div>
</template>
<script>
import child from './child .vue';
export default {
components: {
child
},
data() {
return {
message: '这是父组件传过去的'
};
}
};
</script>
子组件代码: 用props
接收消息后可以直接使用,如下第3
行和第16
行中直接使用
注意: props
有两种接收方法,如下第9
行注释的就是简写用法,此用法不能设置默认值。
<template>
<div>
<p>接收到的消息: {{ message }}</p>
</div>
</template>
<script>
export default {
//props:['message'],
props: {
message: {
type: String,
default: '', // 这里能设置默认值,如果父组件没有传参,默认值会生效
},
},
mounted() {
console.log(this.message);
},
};
</script>
注意: 此传参方式是单向的,即子组件接收到父组件的数据后,是不能直接修改props
接收的数据,否则会直接报错。
二、$emit与v-on (子传父)
思路简述: 子组件通过
$emit
触发父组件的指定方法并且在此方法中携带任意参数,父组件通过在被触发的方法中拿到携带的参数完成子传父。语法:
$emit(方法名,参数)
子组件代码: 核心代码在第11
行,触发方法并且携带参数。
<template>
<div>
<button @click="sendParent">点击发送数据给父组件</button>
</div>
</template>
<script>
export default {
methods: {
sendParent() {
this.$emit('my-event', '这是传递的参数');
},
},
};
</script>
父组件代码: 核心代码在第3
行触发事件,获取到子组件的传参。
<template>
<div>
<child v-on:my-event="childEvent" />
// 或者用@简写代替v-on
<child @my-event="childEvent" />
</div>
</template>
<script>
import child from './child.vue';
export default {
components: {
child,
},
methods: {
childEvent(item) {
console.log('接收到子组件的传参:', item);
},
},
};
</script>
三、EventBus (兄弟传参)
思路简述: 先创建一个全局的事件总线
Bus
(可以随意命名),并挂载在Vue.prototype
上。然后兄弟组件
A
通过$emit
发送参数,兄弟组件B
通过$on
接收参数。
- 有两种使用方法,下面分别讲解。
方法一: 直接挂载全局事件总线,全局直接使用不需要额外引入。
- 先在项目的入口文件中(
main.js
或main.ts
)创建全局事件Bus
并且挂载在Vue.prototype
*
import Vue from 'vue';
const Bus = new Vue();
Vue.prototype.$Bus = Bus;
兄弟组件A
代码: 通过this.$Bus.$emit(方法名,参数)
发送参数。
<template>
<div>
<button @click="sendSibling">点击发送内容给兄弟组件</button>
</div>
</template>
<script>
export default {
methods: {
sendSibling() {
this.$Bus.$emit('my-event', '参数');
},
},
};
</script>
兄弟组件B
代码: 通过this.$Bus.$on(对应$emit的方法,本地方法)
触发本地的方法,从而接收参数。
<template>
<div> 我是兄弟组件B </div>
</template>
<script>
export default {
created() {
this.$Bus.$on('my-event', this.handleMessage);
},
beforeDestroy() {
this.$Bus.$off('my-event', this.handleMessage);
},
methods: {
handleMessage(message) {
console.log('来自兄弟的参数:', message);
},
},
};
</script>
注意: 如上第10-12
行所示,在组件销毁前要在 beforeDestroy
生命周期中使用$off
移除移除$on
的事件监听器,防止避免内存泄漏影响性能。如下所示
方法二: 不创建全局事件总线,单独开一个文件,哪里需要就哪里引用。
- 创建一个单独文件命名为
Bus.js
(可以自由命名)
import Vue from "vue"
export default new Vue()
兄弟组件A
代码: 先引入Bus.js
文件,然后通过Bus.$emit(方法名,参数)
发送参数。
<template>
<div>
<button @click="sendSibling">点击发送内容给兄弟组件</button>
</div>
</template>
<script>
import Bus from './Bus.js';
export default {
methods: {
sendSibling() {
Bus.$emit('my-event', '参数');
},
},
};
</script>
兄弟组件B
代码: 先引入Bus.js
文件,然后通过Bus.$on(对应$emit的方法,本地方法)
触发本地的方法,从而接收参数。同样也需要使用$off
销毁事件监听。
<template>
<div> 我是兄弟组件B </div>
</template>
<script>
import Bus from './Bus.js';
export default {
created() {
Bus.$on('my-event', this.handleMessage);
},
beforeDestroy() {
Bus.$off('my-event', this.handleMessage);
},
methods: {
handleMessage(message) {
console.log('来自兄弟的参数:', message);
},
},
};
</script>
四、.sync与update: (父子双向)
思路简述:
.sync
其实是一个语法糖, 配合子组件用this.$emit('update:绑定的属性名', 方法)
修改父组件属性, 能解决props
只能单向传递的问题。
父组件代码: 核心代码在第3行,比普通的父传子多使用了.sync
修饰符。
<template>
<div>
<chile :myprop.sync="myData" />
</div>
</template>
<script>
import chile from './chile.vue';
export default {
components: {
chile
},
data() {
return {
myData: '父组件数据'
};
}
};
</script>
子组件代码: 核心代码是第14
行,通过this.$emit
同步修改父组件内容。
<template>
<div>
<button @click="updateData">点击子修改父传过来的数据</button>
</div>
</template>
<script>
export default {
props: {
myprop: String,
},
methods: {
updateData() {
this.$emit('update:myprop', 新内容);
},
},
};
</script>
注意: 使用.sync
修饰符时,this.$emit
里面总是以 update:
开头,后面接要修改的属性名称。
五、v-model (父子双向)
思路简述:v-model
最常用于表单,它其实是一个语法糖,并且和上面.sync
有点类似。v-model
本质上是v-bind:value
和@input
组件效果。通过v-bind:value
绑定数据父传子,通过@input
触发对应事件子传父从而实现双向绑定。
父组件代码: 直接用v-model
绑定要传给子组件的参数,当子组件触发 input
事件时父组件myData
会同步更新。
<template>
<div>
<child v-model="myData" />
</div>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
data() {
return {
myData: '天天鸭'
};
}
};
</script>
子组件代码: 当input
输入框的内容发生变化时,就会触发 @input
事件,然后this.$emit
同步修改父组件的值。
<template>
<div>
<input :value="childData" @input="handleChange" />
</div>
</template>
<script>
export default {
model: {
prop: 'myProp',
event: 'input'
},
props: {
myProp: String
},
data() {
return {
childData: this.myProp
};
},
methods: {
handleChange(event) {
this.childData = event.target.value;
this.$emit('input', this.childData);
}
}
};
</script>
注意: 在子组件当中,必须要定义model
来指定 props
和事件名称(名称默认为 input
)。
六、ref
思路讲解: ref
主要用来访问子组件的方法和属性,是直接操纵DOM
的方式。主要用法是在子组件上绑定一个ref
,然后父组件用this.$refs
直接访问子组件的方法。
父组件代码: 子组件上用ref="refChild"
绑定一个ref
,然后用this.$refs.refChild
获取到子组件实例,能获取到子组件属性和操纵子组件方法。
<template>
<div>
<child ref="refChild" />
</div>
</template>
<script>
import child from './child.vue';
export default {
components: {
child,
},
mounted() {
let childObj = this.$refs.refChild;
console.log(childObj.name); // 直接获取到子组件的属性内容 打印出来:天天鸭
childObj.childMethod('参数'); // 触发子组件的方法
},
};
</script>
子组件代码:
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
name: '天天鸭',
};
},
methods: {
childMethod(val) {
console.log(val);
},
},
};
</script>
七、$children与$parent
简述: $children
和 $parent
是Vue
用于访问子组件实例和父组件实例的特殊属性。其中$children
能获取所有子组件实例但不能获取孙子的,而$parent
获取当前组件的父组件实例。
父组件代码: 直接使用this.$children
即可获取。
注意: 获取到的实例可能为空,因此需要判空。并且如果有多个子组件时返回的是一个数组,所以需要通过下标确认对应的子组件数据。
<template>
<div>
<child />
<button @click="getChildMethod">调用子组件方法</button>
</div>
</template>
<script>
import child from './child.vue';
export default {
components: {
child,
},
methods: {
getChildMethod() {
// 判空,然后用下标获取
if (this.$children.length > 0) {
this.$children[0].childMethod(); // 使用子组件方法
this.$children[0].name; // 使用子组件属性
}
},
},
};
</script>
子组件代码: 类似地,父组件也是同样用法,但区别是返回的不是数组而且一个对象,能直接使用。
<template>
<div></div>
</template>
<script>
export default {
mounted(){
this.$parent.parMethod()
this.$parent.name
}
};
</script>
八、$attrs与$listeners (爷孙双向)
简述: $attrs
和 $listeners
相当于是使用在父亲组件上的一个中转站。 $attrs
用于将props
外的数据从爷组件传递给孙组件的,而$listeners
用于从孙组件中触发爷组件中事件达到传参效果。
下面把$attrs
与$listeners
分开讲解更易于理解。
$attrs使用流程代码:
(1)爷组件代码: 类似父传子,正常用冒号:
绑定属性传参。
<GrandParent :name=name></GrandParent>
(2)父组件代码: $attrs
作用在父组件,意思是把props
之外属性全部传递给到孙子。
注意:如果这里父组件用
props
接收了name
属性,那么用$attrs
无法传递到孙子组件,因为只能传递props
之外属性。
<Parent v-bind="$attrs"></Parent>
(3)孙组件代码:类似父传子,正常用popos
接收爷组件传过来的参数。
<template>
<div> 爷组件传来的:{{ name }} </div>
</template>
<script>
export default {
props: {
name: {
default: String,
},
},
};
</script>
$listeners使用流程代码:
(1)孙组件代码 直接this.$emit
类似子传父
<template>
<div>
<el-button @click="update">点击孙传爷</el-button>
</div>
</template>
<script>
export default {
methods: {
update() {
this.$emit('my-event', '孙传给爷的数据');
},
},
};
</script>
(2)父组件代码: $listeners
作用在父组件。
<Parent v-bind="$listeners"></Parent>
(3)爷组件代码: 类似子传父中的父组件,触发对应孙子组件this.$emit
中的my-event
事件接收到参数。
<template>
<div>
<Parent @my-event="getMyEvent" />
</div>
</template>
<script>
import Parent from './Parent.vue';
export default {
components: {
Parent,
},
methods: {
getMyEvent(val) {
console.log('爷组件接收到的数据:', val);
},
},
};
</script>
这里感觉算两种(爷传孙与孙传爷)传参方式了,但由于都是类似中转站效果,所以放一起说比较好理解。
九、provide与inject (多层传参)
简述: provide
与inject
无论多少层组件都能传参。顶层组件通过provide
传参,下面所有组件都能用inject
接收,而且子组件也能通过方法给顶层组件传参。
顶层组件代码: 核心代码在第8
行的provide()
中,可以传递常量、变量和方法。
<template>
<div> </div>
</template>
<script>
export default {
provide() {
return {
name: '天天鸭',
age: this.age,
myMethod: this.myMethod,
};
},
data() {
return {
age: '18',
};
},
methods: {
myMethod(data) {
console.log('收到来自某个孙子的数据:', data);
},
},
};
</script>
子孙组件代码: 核心代码在第10
行接收参数, 除了能接收顶层参数外,还能通过参考第13
行的用法,通过顶层给到的方法传参给顶层组件。
<template>
<div>
<el-button @click="myMethod('我是孙子组件')">我是孙子组件</el-button>
这是顶层传下来的参数: {{ name }}
</div>
</template>
<script>
export default {
inject: ['name', 'age', 'myMethod'],
methods: {
myMethod(data) {
this.myMethod(data); // 传参给顶层祖先组件
},
},
};
</script>
十、Vuex (全局)
有针对性写过对应的文章,可以直接跳转细看:对比学习vuex和pinia用法
十一、Vue.prototype (全局)
简述:能用Vue.prototype
把任何属性和方法挂载在Vue
实例了,让所有Vue
实例共用。
(1)挂载属性 直接往Vue.prototype
挂载即可
Vue.prototype.$testName = '天天鸭';
(2)挂载方法 直接往Vue.prototype
挂载即可
Vue.prototype.$testMethod = function(val) {
console.log(val);
};
调用: 直接在任何页面用this
调用
this.$appName;
this.$testMethod('参数');
十二、浏览器缓存
简述: localStorage
和sessionStorage
:主要是浏览器用来持久化存储的,这算是用的不多,但也是必用的一种通信方式。两者区别如下
sessionStorage
(临时存储):最大空间5M
,为每一个数据源维持一个存储区域,但只在浏览器打开期间存在,关闭后数据会不会消失,包括页面重新加载。
localStorage
(长期存储):最大空间5M
,与 sessionStorage 一样,但是哪怕浏览器关闭后,数据依然会一直存在,除非手动删除。
具体用法如下所示:
// 存储
sessionStorage.setItem('key', 'value');
localStorage.setItem('key', 'value');
// 获取
let valueFromSessionStorage = sessionStorage.getItem('key');
let valueFromLocalStorage = localStorage.getItem('key');
// 删除
sessionStorage.removeItem('key');
localStorage.removeItem('key');
// 清空所有
sessionStorage.clear();
localStorage.clear();
注意: 存储的数据只能是字符串形式,因此如果要存储对象或者数组,则需要使用 JSON.stringify
来转换后再存储,读取后用 JSON.parse
还原。
十三、window (全局)
简述: 直接用语法 window.age = '18'
定义然后全局
通用即可。(此方式是存放在内存刷新会清空)
注意:在 Vue
应用中,虽然可以直接将属性挂载到 window
对象上实现全局通用,但并推荐,因为这可能会出现命名冲突、导致代码难以维护
。
添加属性和方法:直接定义,可以是属性也可以是对象
window.Obj = { test: '挂载对象' }
window.name = '天天鸭'
使用:
console.log( window.Obj);
console.log( window.name);
十四、路由
简述: Vue
在路由跳转时携带参数其实也很常用的方式,下面汇总一下三种路由传参。
(1)通过 params
传参
跳转页面用法:
this.$router.push({name:"index", params:{id}})
目标页面接收参数:
this.$route.params.id
(2)通过 query
传参
跳转页面用法:有几种方式
this.$router.push({ name:"index", query:{id}})
this.$router.push({ path:"/index", query:{id}})
this.$router.push('/index?name='+obj.name+'&age='+obj.age)
目标页面接收参数:
this.$route.query.id
(3)通过动态路由传参
注意: 如果用动态路由传参需要对路由进行配置;并且参数会在 url
中显示。 如下所示,在path
后面要配置:name/:age
.
{
path: '/about/:name/:age' ,
name: 'About',
component() => import('@/views/About.vue')
}
跳转页面用法:
this.$router.push('/about/'+obj.name+'/'+obj.age)
目标页面接收参数:
this.$route.params
十五、$root (顶层)
简述: 可以通过 $root
访问到整个 Vue
树的根实例,也就是可以使用 $root
来访问全局的属性或者修改全局的属性。
示例:在main.js
文件中定义一个globalName
属性,可以全局使用。
import App from './App.vue';
import Vue from 'vue';
new Vue({
el: '#app',
render: h => h(App),
data: {
globalName: '天天鸭'
}
});
在下层任意组件使用或者修改内容
<template>
<div>
{{ globalName }}
</div>
</template>
<script>
export default {
mounted() {
this.$root.globalName = '修改数据';
},
};
</script>
十六、slot(父传子)
简述: 通过插槽也是可以传递参数,这也是很多人忽略的一种方式。父组件可以通过插槽向子组件传递参数,然后子组件拿到参数进行渲染。
下面主要讲解具名插槽和默认插槽两种使用方式。
(1)具名插槽用法
子组件代码: 用slot
定义一个名叫header
的插槽。
<template>
<div>
<slot name="header"></slot>
</div>
</template>
<script>
export default {
};
</script>
父组件代码: 用v-slot:header
向子组件的header
插槽传递内容。这边传递什么那边就会在对应区域显示什么。
<template>
<div>
<child>
<template v-slot:header>
<h1>这是插槽的内容</h1>
</template>
</child>
</div>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
}
};
</script>
(2)无参数传递的默认插槽
子组件代码: 用slot
定义插槽时不需要指定name
名称。
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
};
</script>
父组件代码: 不需要指定插槽名称,只要在组件中间填写内容就会渲染在默认插槽中。
<template>
<div>
<child>
<p>默认插槽中的内容。</p>
</child>
</div>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
}
};
</script>