vue 组件通讯
14 种组件通讯
1. props
这个就是父传子属性, props 值可以是一个数组或对象
// 数组:不建议使用 props:[] // 对象 props:{ inpVal:{ type:Number, //传入值限定类型 // type 值可为String,Number,Boolean,Array,Object,Date,Function,Symbol // type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认 required: true, //是否必传 default:200, //默认值,对象或数组默认值必须从一个工厂函数获取如 default:()=>[] validator:(value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } }
2. $emit
这个也非常常见, 触发父组件的自定义事件, 其实就是父传子的方法
1 // 父组件 2 <home @title="title"> 3 // 子组件 4 this.$emit('title',[{title:'这是title'}])
3. vuex
vuex 是一个状态管理器,
一个独立的插件, 适合数据共享多的项目里面, 因为如果只是简单的通讯, 使用起来会比较重
1 state:定义存贮数据的仓库 ,可通过this.$store.state 或mapState访问 2 getter:获取 store 值,可认为是 store 的计算属性,可通过this.$store.getter 或 mapGetters访问 4 mutation:同步改变 store 值,为什么会设计成同步,因为mutation是直接改变 store 值, 5 vue 对操作进行了记录,如果是异步无法追踪改变.可通过mapMutations调用 6 action:异步调用函数执行mutation,进而改变 store 值,可通过 this.$dispatch或mapActions 访问 8 modules:模块,如果状态过多,可以拆分成模块,最后在入口通过...解构引入
4. $attrs 和 $listeners
2.4.0 新增 这两个是不常用的属性, 但是高级用法很常见;
$attrs 场景: 如果父传子有很多值, 那么在子组件需要定义多个
props解决: attrs 获取子传父中未在props定义的值(在 $attrs里面只会有props没有注册的属性) -> (class 和 style 除外)
1 // 父组件 2 <home title="这是标题" width="80" height="80" imgUrl="imgUrl"/> 3 4 // 子组件 5 mounted() { 6 console.log(this.$attrs) //{title: "这是标题", width: "80", height: "80", imgUrl: "imgUrl"} 7 },
1 props: { 2 width: { // 父组件的width 在子组件props中注册后, 那么在$attrs上取不到 3 type: String, 4 default: '' 5 } 6 }, 7 mounted() { 8 console.log(this.$attrs) //{title: "这是标题", height: "80", imgUrl: "imgUrl"} 9 },
$listeners 场景: 子组件需要调用父组件的方法解决: 父组件的方法可以通过 v-on="listeners" 传入内部组件 ----- 在创建更高层次的组件时非常有用
1 // 父组件 2 <home @change="change"/> 3 4 // 子组件 5 mounted() { 6 console.log(this.$listeners) //即可拿到 change 事件 7 }
$inheritAttrs
组件内未被注册的属性将作为普通html元素属性被渲染
1 // 父组件 2 <home title="这是标题" width="80" height="80" imgUrl="imgUrl"/> 3 4 // 子组件 5 mounted() { 6 console.log(this.$attrs) //{title: "这是标题", width: "80", height: "80", imgUrl: "imgUrl"} 7 }, 8 9 inheritAttrs默认值为 true,也就是父组件上的属性会显示到根组件上 10 如果设置为 false 就会隐藏
5. provide 和 inject
2.2.0 版本新增
使用场景: 以允许一个祖先组件向其所有子孙后代注入一个依赖, 不论组件层次有多深, 并在起上下游关系成立的时间里始终生效。
provide :一个对象或返回一个对象的函数
inject : 一个字符串数组,或一个对象,对象的key是本地的绑定名
1 <template> // 父组件 2 <div id="app"> 3 </div> 4 </template> 5 <script> 6 export default { 7 data () { 8 return { 9 datas: [ 10 { 11 id: 1, 12 label: '产品一' 13 }, 14 { 15 id: 1, 16 label: '产品二' 17 }, 18 { 19 id: 1, 20 label: '产品三' 21 } 22 ] 23 } 24 }, 25 provide { 26 return { 27 datas: this.datas 28 } 29 } 30 } 31 </script>
<template> // 后代组件 <div> <ul> <li v-for="(item, index) in datas" :key="index"> {{ item.label }} </li> </ul> </div> </template> <script> export default { inject: ['datas'] } </script>
注意: provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个监听的对象,那么其对象的属性还是可响应的。
响应式示例:
父组件中提供 provide() { return { map_nodeObj: { map_node: this.obj } // 提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。 } }, 子组件中引入 inject: { map_nodeObj: { default: () => { return {map_node: '0'} } } }, 使用: this.map_nodeObj.map_node // 运行顺序 data provide created // 在这个阶段$el还未生成,在这先处理privide的逻辑,子孙组件才可以取到inject的值 mounted ...
6. $parent 和 $children
$parent:指代的父组件, 返回的是一个组件集合
用法:this.$parent (如果当前组件没有父组件,那么返回当前组件)
$children:指代的子组件,返回的是一个组件集合
用法: this.$children (如果你能清楚的知道子组件的顺序,可以使用下标来表示)
注意:(1) 组件只能有一个根节点
(2) 可以在子组件中使用this.$parent.属性值, 或者函数
(3) 在父组件中可以使用this.$children[i].属性
(4) 需要注意this的指向
7. ref 和 $refs
ref 有三种用法:
(1) ref加在普通的元素上,用this.$refs.name 获取到的是dom元素
(2) ref 加在子组件上, 用this.$refs.name 获取到的是组件实例,可以使用组件的所有方法
ref 和 v-for 在一起的情况
8. $root
$root 设置全局属性
let app = new Vue({ el: '#app', // 全局数据,在其他页面或者组建可改变 data: function () { return { s: '' } }, router, store, template: '<router-view></router-view>' }) // a.vue this.$root.s = '设置了s属性' // b.vue console.log(this.$root.s) //设置了s属性
9. .sync
从2.3.0起重新引入的 .sync 修饰符,.sync被作为一个编译时的语法糖,它会被扩展为一个自动更新父组件属性的 v-on 监听器
// 父组件 <comp :foo.sync="bar"></comp> // 编译时会被扩展为: <comp :foo="bar" @update:foo="val => bar = val"></comp> //子组件 //当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件: // 所以子组件可以通过$emit 触发 update 方法改变 this.$emit('update:foo', newValue)
10. v-slot 插槽
2.6.0新增
(1) 匿名插槽
默认插槽, 没有命名, 有且只有一个
1 // 父组件 2 <todo-list> 3 <template v-slot:default> 4 任意内容 5 <p>我是匿名插槽 </p> 6 </template> 7 </todo-list> 8 9 // 子组件 10 <slot>我是默认值</slot> 11 //v-slot:default写上感觉和具名写法比较统一,容易理解,也可以不用写
(2) 具名插槽
相对匿名插槽组件slot标签带有name命名的
1 // 父组件 2 <todo-list> 3 <template v-slot:todo> 4 任意内容 5 <p>我是匿名插槽 </p> 6 </template> 7 </todo-list> 8 9 //子组件 10 <slot name="todo">我是默认值</slot>
(3) 作用域插槽
子组件内数据可以被父页面拿到(解决了数据只能从父页面传递给子组件)
1 // 父组件 2 <todo-list> 3 <template v-slot:todo="slotProps" > 4 {{slotProps.user.firstName}} 5 </template> 6 </todo-list> 7 //slotProps 可以随意命名 8 //slotProps 接取的是子组件标签slot上属性数据的集合所有v-bind:user="user" 9 10 // 子组件 11 <slot name="todo" :user="user" :test="test"> 12 {{ user.lastName }} 13 </slot> 14 data() { 15 return { 16 user:{ 17 lastName:"Zhang", 18 firstName:"yue" 19 }, 20 test:[1,2,3,4] 21 } 22 }, 23 // {{ user.lastName }}是默认数据 v-slot:todo 当父页面没有(="slotProps")
注意: 1. 父组件可以利用v-slot:header="slotProps" 接收组件中的消息, 组件中只需要在 <slot name="header" :header="header"><slot/>
2. 如果被提供的内容只有一个默认插槽时, 组件的标签可以直接被当做插槽的模板来使用<template v-slot="slotProps">
3. 动态参数也可是使用到插槽中 <template v-slot="{ user : person}">
4. v-slot 缩写是#, 但是使用#的话,必须始终始终使用具名插槽来代替<template #default="slotProps">
11. EventBus
1. 就是声明一个全局Vue实例变量 EventBus, 把所有的通信数据, 事件监听都存粗到这个变量上;
2. 类似于Vuex, 但这种方式只使用与极小的项目
3. 原理就是利用 $on 和 $emit 并实例化一个全局vue 实现数据共享
4. 可以实现平级, 嵌套组件传值, 但是对应的事件名 eventTarget 必须是全局唯一的
1 // 在 main.js 2 Vue.prototype.$eventBus=new Vue() 3 4 // 传值组件 5 this.$eventBus.$emit('eventTarget','这是eventTarget传过来的值') 6 7 // 接收组件 8 this.$eventBus.$on("eventTarget",v=>{ 9 console.log('eventTarget',v);//这是eventTarget传过来的值 10 })
12. broadcast 和 dispatch
Vue 1.x 有这两个方法, 事件广播和派发, 但是 vue 2.x 删除了 下面是对两个方法进行的封装
1 /* 2 broadcast 事件广播 3 @param {componentName} 组件名称 4 @param {eventName} 事件名 5 @param {params} 参数 6 遍历寻找所有子孙组件,假如子孙组件和componentName组件名称相同的话,则触发$emit的事件方法,数据为 params. 7 如果没有找到 则使用递归的方式 继续查找孙组件,直到找到为止,否则继续递归查找,直到找到最后一个都没有找到为止。 8 */ 9 function broadcast(componentName, eventName, params) { 10 this.$children.forEach(child => { 11 const name = child.$options.name; 12 if (name === componentName) { 13 child.$emit.apply(child, [eventName].concat(params)); 14 } else { 15 broadcast.apply(child, [componentName, eventName].concat([params])); 16 } 17 }) 18 } 19 /* 20 * dispatch 查找所有父级,直到找到要找到的父组件,并在身上触发指定的事件。 21 @param { componentName } 组件名称 22 @param { eventName } 事件名 23 @param { params } 参数 24 */ 25 export default { 26 methods: { 27 dispatch(componentName, eventName, params) { 28 let parent = this.$parent || this.$root; 29 let name = parent.$options.name; 30 31 while (parent && (!name || name !== componentName)) { 32 parent = parent.$parent; 33 34 if (parent) { 35 name = parent.$options.name; 36 } 37 } 38 if (parent) { 39 parent.$emit.apply(parent, [eventName].concat(params)); 40 } 41 }, 42 broadcast(componentName, eventName, params) { 43 broadcast.call(this, componentName, eventName, params); 44 } 45 } 46 };
13. 路由传参
方案一
1 // 路由定义 2 { 3 path: '/describe/:id', 4 name: 'Describe', 5 component: Describe 6 } 7 // 页面传参 8 this.$router.push({ 9 path: `/describe/${id}`, 10 }) 11 // 页面获取 12 this.$route.params.id
方案二
1 // 路由定义 2 { 3 path: '/describe', 4 name: 'Describe', 5 omponent: Describe 6 } 7 // 页面传参 8 this.$router.push({ 9 name: 'Describe', 10 params: { 11 id: id 12 } 13 }) 14 // 页面获取 15 this.$route.params.id
方案三
// 路由定义
{
path: '/describe',
name: 'Describe',
component: Describe
}
// 页面传参
this.$router.push({
path: '/describe',
query: {
id: id
`}
)
<router-link :to="{ path: '/describe', query: { id: 1111}}">click to page</router-link>
// 页面获取
this.$route.query.id
注意: 三种方案对比, 方案二参数不会拼接在路由后面, 页面刷新参数会丢失, 方案一和三参数拼接在后面容易暴露信息
14. Vue.observable
2.6.0新增
用法: 让一个对象可响应。Vue内部会用它来处理data函数返回的对象;返回的对象可以直接用渲染函数和计算属性内, 并且会在发生改变是触发响应的更新;也可以作为最小化的跨组件状态存储器,用于简单的场景。通讯原理实质上是利用Vue.observable 实现一个简易的vuex
1 // 文件路径 - /store/store.js 2 import Vue from 'vue' 3 4 export const store = Vue.observable({ count: 0 }) 5 export const mutations = { 6 setCount (count) { 7 store.count = count 8 } 9 } 10 11 //使用 12 <template> 13 <div> 14 <label for="bookNum">数 量</label> 15 <button @click="setCount(count+1)">+</button> 16 <span>{{count}}</span> 17 <button @click="setCount(count-1)">-</button> 18 </div> 19 </template> 20 21 <script> 22 import { store, mutations } from '../store/store' // Vue2.6新增API Observable 23 24 export default { 25 name: 'Add', 26 computed: { 27 count () { 28 return store.count 29 } 30 }, 31 methods: { 32 setCount: mutations.setCount 33 } 34 } 35 </script>