1. 父子组件间通信
- props / $emit
父组件通过props的方式向子组件传递数据,而通过$emit 子组件可以向父组件通信。
(1)父组件向子组件传值
<!-- 父组件 -->
<template>
<div class="section">
<child :msg="articleList"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
name: 'HelloWorld',
components: { comArticle },
data() { return { msg: '阿离王'
}
}
}
</script>
<!-- 子组件 child.vue -->
<template>
<div> {{ msg }} </div>
</template>
<script>
export default {
props: { msg: String }
}
</script>
注意:
-
第一,不应该在一个子组件内部改变 prop,这样会破坏单向的数据绑定,导致数据流难以理解。如果有这样的需要,可以通过 data 属性接收或使用 computed 属性进行转换
-
第二,如果 props 传递的是引用类型(对象或者数组),在子组件中改变这个对象或数组,父组件的状态会也会做相应的更新,利用这一点就能够实现父子组件数据的“双向绑定”,虽然这样实现能够节省代码,但会牺牲数据流向的简洁性,令人难以理解,最好不要这样去做。
-
想要实现父子组件的数据“双向绑定”,可以使用 v-model 或 .sync。
(2)子组件向父组件传值
<!-- 父组件 -->
<template>
<div class="section">
<child :msg="articleList" @changMsg="changMsg"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
name: 'HelloWorld',
components: { comArticle },
data() {
return {
msg: '高渐离'
}
},
methods:{
changMsg(msg) {
this.msg = msg
}
}
}
</script>
<!-- 子组件 child.vue -->
<template>
<div> {{ msg }}
<button @click="change">改变字符串</button>
</div>
</template>
<script>
export default {
props: { msg: String },
methods: {
change(){
this.$emit('changMsg', '高渐离带你学习前端')//changMsg保持一致
}
}
}
</script>
- provide/ inject
provide/ inject 是vue2.2.0新增的api, 简单来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。
注意: 这里不论子组件嵌套有多深, 只要调用了inject 那么就可以注入provide中的数据,而不局限于只能从当前父组件的 props属性中回去数据
// A.vue
//三个组件: A.vue、B.vue、C.vue 其中 C是B的子组件,B是A的子组件
<template>
<div>
<comB></comB>
</div>
</template>
<script>
import comB from '../components/test/comB.vue'
export default {
name: "A",
provide: { for: "demo" },
components:{ comB }
}
</script>
// B.vue
<template>
<div>
{{demo}}
<comC></comC>
</div>
</template>
<script>
import comC from '../components/test/comC.vue'
export default {
name: "B",
inject: ['for'],
data() {
return {
demo: this.for
}
},
components: { comC } }
</script>
// C.vue
<template>
<div>
{{demo}}
</div>
</template>
<script>
export default {
name: "C",
inject: ['for'],
data() {
return {
demo: this.for
}
}
}
</script>
- listeners
多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法----listeners
- attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用
- listeners" 传入内部组件
// index.vue
<template>
<div>
<h2>浪里行舟</h2>
<child-com1
:foo="foo"
:boo="boo"
:coo="coo"
:doo="doo"
title="前端工匠"
></child-com1>
</div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
components: { childCom1 },
data() {
return {
foo: "Javascript",
boo: "Html",
coo: "CSS",
doo: "Vue"
};
}
};
</script>
// childCom1.vue
<template class="border">
<div>
<p>foo: {{ foo }}</p>
<p>childCom1的$attrs: {{ $attrs }}</p>
<child-com2 v-bind="$attrs"></child-com2>
</div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
components: { childCom2 },
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
props: { foo: String // foo作为props属性绑定 },
created() {
console.log(this.$attrs); //
{
"boo": "Html",
"coo": "CSS",
"doo": "Vue",
"title": "前端工匠"
}
}
};
</script>
// childCom2.vue
<template>
<div class="border">
<p>boo: {{ boo }}</p>
<p>childCom2: {{ $attrs }}</p>
<child-com3 v-bind="$attrs"></child-com3>
</div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
components: { childCom3 },
inheritAttrs: false,
props: { boo: String },
created() {
console.log(this.$attrs); // { "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
}
};
</script>
// childCom3.vue
<template>
<div class="border">
<p>childCom3: {{ $attrs }}</p>
</div>
</template>
<script>
export default {
props: {
coo: String,
title: String
}
};
</script>
2. 事件总线(EventBus)
eventBus呢,其实原理就是 事件订阅发布,eventBus 又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。
var Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {});
这里我们可以直接使用 vue 自带的事件监听,也就是 emitemit emiton,我们来简单封装下:
- 首先需要创建一个事件总线并将其导出, 以便其他模块可以使用或者监听它
新建一个 event-bus.js 文件
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
2.发生事件
假设你有两个组件: additionNum 和 showNum, 这两个组件可以是兄弟组件也可以是父子组件;这里我们以兄弟组件为例:
<template>
<div>
<show-num-com></show-num-com>
<addition-num-com></addition-num-com>
</div>
</template>
<script>
import showNumCom from './showNum.vue'
import additionNumCom from './additionNum.vue'
export default {
components: {
showNumCom, additionNumCom
}
}
</script>
// addtionNum.vue 中发送事件
<template>
<div>
<button @click="additionHandle">+加法器</button>
</div>
</template>
<script>
import { EventBus } from './event-bus.js'
console.log(EventBus)
export default {
data() {
return {
num: 1
}
},
methods: {
additionHandle() {
EventBus.$emit('addition', {
num: this.num++
})
}
}
}
</script>
3.接收事件
// showNum.vue 中接收事件
<template>
<div>计算和: {{count}}</div>
</template>
<script>
import { EventBus } from './event-bus.js'
export default {
data() {
return {
count: 0
}
},
mounted() {
EventBus.$on('addition', param => {
this.count = this.count + param.num;
})
}
}
</script>
这样就实现了在组件addtionNum.vue中点击相加按钮, 在showNum.vue中利用传递来的 num 展示求和的结果.
3. Vuex
- 简要介绍各模块在流程中的功能:
- Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
- dispatch:操作行为触发方法,是唯一能执行action的方法。
- actions:操作行为处理模块,由组件中的$store.dispatch('action 名称', data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发
- commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
- mutations:状态改变操作方法,由actions中的commit('mutation 名称')来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。
- state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
- getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。
- Vuex与localStorage
- vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
- 注意的是:由于vuex里,我们保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换:
JSON.stringify(state.subscribeList); // array -> string JSON.parse(window.localStorage.getItem("subscribeList")); // string -> array
4. 作用域插槽
就是把子组件的数据通过插槽的方式传给父组件使用,然后再插回来
// Child.vue
<template>
<div>
<slot :user="user"></slot>
</div>
</template>
export default{
data(){
return {
user:{ name:"xxx" }
}
}
}
// Parent.vue
<template>
<div>
<child v-slot="slotProps"> {{ slotProps.user.name }} </child>
</div>
</template>
5. refs
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据, 我们看一个ref 来访问组件的例子:
// 子组件 A.vue
export default {
data () {
return {
name: 'Vue.js'
}
},
methods: {
sayHello () {
console.log('hello')
}
}
}
// 父组件 app.vue
<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.name); // Vue.js
comA.sayHello(); // hello
}
}
</script>
ref 这种方式,就是获取子组件的实例,然后可以直接子组件的方法和访问操作data的数据,就是父组件控制子组件的一种方式,子组件想向父组件传参或操作,只能通过其他的方式了
6. 路由组件传值
路由的query传参,路由的params参数,通过hash传参,通过props传参
-
路由的query参数传参(类似于axios通过query参数发送请求)
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="/home/school/?id=1&name=王城二小"></router-link>
<!-- 跳转并携带query参数,to的对象写法 -->
<router-link :to="{
path:'/home/school',
query:{
id:1,
name:'王城二小'
}
}" ></router-link>
route.query.title
-
路由的params参数传参(类似于axios通过params参数发送请求),这里不再传数组,传单个数据
在router的配置文件中,声明接收params参数
export default new VueRouter({
routes: [
{
name:"aboutus",
path: '/aboutus',
component: Aboutus
},
{
name: "home",
path: '/home',
component: Home,
children:[
{
name: "school",
path: '/school/:id/:name',//使用占位符声明接收params参数
component: School
},
{
name: "student",
path: '/student',
component: Student
},
]
}
]
})
传递参数
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="/home/school/id/name">跳转</router-link>
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link :to="{
name:'school',//必须使用name
params:{
id:1,
name:'王城二小'
}
}" >跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置! 接收参数:
$route.params.id $route.params.name
- 通过props传参
-
- 布尔模式
//组件
const User = {
props: ['id'], // 组件中通过 props 获取 id
template: '<div>User {{ id }}</div>'
}
// 路由配置中,增加 props 字段,并将值 设置为 true,把路由收到params所有的参数通过props传递给组件
const routes = [{ path: '/user/:id', component: User, props: true }]
-
- 对象模式
//路由配置,对象中的key-value都会通过props传递给组件
const routes = [
{
path: '/hello',
component: Hello,
props: {
name: 'World'
}
}
]
//组件中获取数据
const Hello = {
props: {
name: {
type: String,
default: 'Vue'
}
},
template: '<div> Hello {{ name }}</div>'
}
-
- 函数模式
// 创建一个返回 props 的函数
const dynamicPropsFn = (route) => {
return { name: route.query.say + "!" }
}
const routes = [
{
path: '/hello',
component: Hello,
props: dynamicPropsFn //
props(route)
{
return{
id:route.query.id,
title:route.query.title
}
} //
}
]
const Hello = {
props: {
name: {
type: String,
default: 'Vue'
}
},
template: '<div> Hello {{ name }}</div>'
}
-
通过hash传参
通过此方式,url 路径中带有 hash,例如:/details/001#car。
- 路由配置
使用 hash 时,以下三种方式都是可行的(同 query):
this.$router.push('/details/001#car')
this.$router.push({ path: '/details/001', hash: '#car'})
this.$router.push({ name: 'details', params: { id: '001' }, hash: 'car'})
-
- 组件获取数据
组件通过 $route.hash.slice(1) 获取:
const Details = {
template: '<div>Details {{ $route.hash.slice(1) }} </div>',
}
7. 总结
常见使用场景可以分为三类:
- 父子组件通信: props/parent/refs 、listeners、slot
- 兄弟组件通信: eventBus 、 vuex
- 跨级通信: eventBus、 Vuex、 provide / inject 、 listeners