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>

 

posted @ 2019-10-11 09:07  申继林  阅读(158)  评论(0编辑  收藏  举报