组件加name属性_Vue 2.X组件通信总结

能工摹形,巧匠窃意。必三省吾身,万不可怠惰因循。

foreword

最近一直在学Vue3.0 的东西,当然Vue2.x其实对于现在前端来说也非常重要,所以这里附上之前博客上写的Vue2.x组件通信总结,之后会写个万字长文Vue3.0的学习。

这篇容纳了我个人所知道的一些Vue 2.x组件通信的总结,之后3.x官网公布后会增加3.x的部分。(篇幅长,细节有那么一些些,熟知部分可以一眼略过).

start

一. props $emit $attrs  $listeners  $props

  • 之所以把$attrs/$listeners/$props 和props $emit 放在一起 是因为个人感觉,更加方便记忆。老项目使用$attrs  $listeners  $props这几个API需要看当时的vue版本是不是已经支持;

1.props 父组件向子组件传值

  _ parent.vue

<template>  <div>    PARENT    <children :stars="stars">children>  div>template><script>import children from './children/children';export default {  components:{children},  data(){    return {      stars:[        {name:"周杰伦",id:1},        {name:"刘亦菲",id:2},        {name:"胡歌",id:3},        {name:"古天乐",id:4},      ]    }  },}script>

_ children.vue

<template>   <div>     CHILDREN     <ul>       <li v-for="star in stars" :key="star.id">{{star.name}}li>     ul>   div>template><script>export default {  name:"children",   props:{    stars:{      type:Array,      default(){        return []      },      // required:true // 是否必须属性      // type:Symbol, // 传入类型 type String Number Boolean Function Object Array Symbol       // type:CustormFn,// 可以是自定义构造函数,用instanceof 检测      validator(V){  // 自定义验证函数        return V.length > 2      }    }  },  created(){    console.log(this.stars) //[{…}, {…}, {…}, {…}, __ob__: Observer]  }}script>

summarize: 父组件通过props传入到子组件. 子组件可以设定传入值的校验,等属性.组件中的数据方式共有 data,computed,props以及provide和inject(这个待商榷).

2. 子组件通过事件的形式向父组件传值

_ parent

<template>  <div>    <p>{{bestHandsome}}p>    <children @handleBs='handleBs'>children>  div>template><script>import children from './children';export default {  name:'parent2',  components:{children},  data(){    return {      bestHandsome:'刘德华'    }  },  methods:{    handleBs(name){      this.bestHandsome = name;    }  }}script>

_children

<template>  <button @click="setBestHandsome('吴彦祖')">BUTTONbutton>template><script>export default {  name:'children2',  methods:{    setBestHandsome(name){      this.$emit('handleBs',name);    }  }}script>

summarize:子组件通过events的形式改变父组件的值,实际上是调用传入参数父组件的方法,来改变父组件的值.  有部分程序员喜欢将 .sync 和v-model这两个语法糖也归为组件通信方式,这里不做归纳,详细请看Vue官方文档。

3. $attrs/$listeners/$props 

官方解释:

  • $props:当前组件接收到的 props 对象。Vue 实例代理了对其 props 对象属性的访问。类型(Object)

  • $attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。类型:{ [key: string]: string }(只读)

  • $listeners: 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件——在创建更高层次的组件时非常有用。类型: { [key: string]: Function | Array

     }(只读)

 $props

// parent.vue<template>  <div>    <children      name='input'      type='nmber'      disabled      autofocus      placeholder='这是一个输入框'    >children>  div>template>// children.vue<template>   <div>     <input v-bind="$props">   div>template><script>export default {  name:"children",   props:['name','type','disabled','autofocus','placeholder'],  mounted(){    console.log(this.$props.name)// input  }}script>

6d63fd268d4ab5524c834aacde94edf7.png

10cc159897c0fd659d6d5ece59195a50.png

  • 注意这里使用v-bind=”$props”就会使得子组件中的input标签绑定上父组件中定义的props属性.

$attrs

_ code

// parent.vue<template>  <div>    <children      name='input'      type='nmber'      disabled      autofocus      placeholder='这是一个输入框'    >children>  div>template>// children.vue<template>   <div>     <input v-bind="$attrs">   div>template><script>export default {  inheritAttrs:false, // 将默认绑定根元素属性去掉  name:"children",   props:['handsome'],  mounted(){    console.log(this.$attrs.name)// input    console.log(this.$attrs.handsome)// undefined    console.log(this.$props.handsome)// 1  }}script>

inheritAttrs:

默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例属性 $attrs 可以让这些特性生效,且可以通过 v-bind 显性的绑定到非根元素上。

不设置inheritAttrs:false效果 7f91c290d4d2d5a0bbb60c9f3691fa6e.png 设置inheritAttrs效果 741aae98397427fd96782797fd49dc3f.png $listeners: _ code
// parent.vue<template>  <div>    <p>{{ handsome }}p>    <children      @changeHandsome="changeHandsome"      @clearHandsome="clearHandsome"      @resetHandsome="resetHandsome"    >children>  div>template><script>import children from './children/children';export default {  components:{children},  data(){    return {      handsome:'lin'    }  },  methods:{    changeHandsome(name){      this.handsome = name;    },    clearHandsome(){      this.handsome = '';    },    resetHandsome(){      this.handsome = 'lin';    },  }}script>// children.vue<template>   <div>     <g-children v-on="$listeners">g-children>   div>template><script>import gChildren from './grandchildren'export default {  name:"children",   components:{gChildren},  mounted(){    console.log(this.$listeners)  }}script>// grandchildren.vue<template>  <div>    <button @click="$emit('changeHandsome','zhou')">set Zhoubutton>    <button @click="$emit('clearHandsome')">clearbutton>    <button @click="$emit('resetHandsome')">resetbutton>  div>template>

以上这些实际上是父子组件直接直接或者间接通过vue提供的通信方式通信.

二. $refs $parent $children $root

官方解释

$refs:一个对象,持有注册过 [ref 特性] 的所有 DOM 元素和组件实例。
$parent:父实例,如果当前实例有的话。(类型:Vue instance)
$children:当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。(类型:Array)
$root:当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。

$refs _ code 
//children.vue<script>export default {  name: "children",  data() {    return {      name: "xiaoerlang",      age: 18    };  }};script>// parent.vue<template>  <div>    <children ref="children">children>    <button @click="setChildrenData">buttonbutton>  div>template><script>import children from "./children/children";export default {  components: { children },  methods: {    setChildrenData() {      console.log(this.$refs.children.name); //第一次点击按钮的时候打印 xiaolang      this.$refs.children.name = "xiaoming";      console.log(this.$refs.children.name); //第一次点击按钮的时候打印 xiaoming    }  }};script>
$parent
// parent.vue<template>  <div>    {{name}}    <children>children>  div>template><script>import children from "./children/children";export default {  components: { children },  data(){    return {      name:'liu'    }  },};script>// children.vue<template>  <div>    <button @click="setParentName('fei')">buttonbutton>  div>template><script>export default {  name: "children",  methods:{    setParentName(name){      this.$parent.name = name;    }  }};script>
$children
// parent.vue<template>  <div>    <children>children>    <button @click="setChildrenName('yi')">buttonbutton>  div>template><script>import children from "./children/children";export default {  components: { children },  methods:{    setChildrenName(name){      this.$children[0].name = name;    }  }};script>// children.vue<template>  <div>    {{name}}  div>template><script>export default {  name: "children",  data() {    return {      name: "xiaoerlang",    };  },};script>
$root:这里与$parent类似,是当前组件树的根实例. 附加:使用$parent或者$root配合$on和$emit可以 进行兄弟组件之间通信
// parent.vue<template>  <div>    <bother1>bother1>    <bother2>bother2>  div>template><script>import bother1 from './children/brother1';import bother2 from './children/brother2';export default {  components: { bother1,bother2 },};script>// bother2.vue<template>  <div>{{name}}div>template><script>export default {  name:'brother2',  data(){    return{      name:'zhouxiaolun'    }  },  created(){    this.$parent.$on('setB2',this.setName)  },  methods:{    setName(name){      this.name = name;    }  }}script>// bother1.vue<template>  <button @click="setB2Name('zhoujielun')">buttonbutton>template><script>export default {  name:'brother1',  methods:{    setB2Name(name){      this.$parent.$emit('setB2',name)    }  }}script>
summarize:
  • 注意这里$children 格式为数组,如果没有就是空数组,但是这里的数组顺序与页面顺序是不对应的,这里涉及到了虚拟dom挂载.

  • 上面的部分情况其实是拿到对应的组件的实例,相当于在对应vue组件中调用this.xx = ‘xxxx’;

  • 实际开发中,非自定义组件,或者真实需要,不建议使用$parent和$children $root进行组件之间的通信.

三. provide/inject provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。provide/inject能够实现祖先和后代之间传值.、
// 祖先组件export default {  provide() {    const that = this;    return {      foo: "foo",      forefathersThis: that    };  },  name: "parent",  components: { children }};// 后代组件export default {  name: "children",  inject: ["foo", "forefathersThis"],  created() {    console.log(this.foo);    console.log(this.forefathersThis); // 祖先组件的实例  }};
提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。这里也可以传入this到后代组件中,但实际开发中不推荐使用,可以用于开发高阶组件或者组件库. 四.事件总线eventBus方式(自定义Bus类,或者使用Vue代替);
// Bus 类class Bus {  constructor() {    this.CB = {};  }  // 监听  $on(name, fn) {    this.CB[name] = this.CB[name] || [];    this.CB[name].push(fn)  }  // 派发  $emit(name, args) {    this.CB[name] && this.CB[name].forEach(cb => cb(args))  }}export default Bus;// main.jsimport Bus from './eventBus';Vue.prototype.$bus = new Bus();// 组件1 methods: {    setBH2Name() {      this.$bus.$emit("setB2", "zhoujielun");    }  }// 组件2   created() {    this.$bus.$on("setB2", this.setName);  },  methods: {    setName(name) {      this.name = name;    }  }
summarize:
  • 如果不使用自定义方式,也可以Vue.prototype.$bus = new Vue(); vue内部已经做了具体处理.并且提供$once只监听一次这个事件,$off(name)移除name事件监听,$off() 移除所有事件监听.

  • 这里主要说Vue通信方式,所以关于上部分需要在destroy生命周期需要注销监听等操作都未列出,实际开发实际需求.

五.Vuex

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,实际上是把一些需要多处用到的状态放在同一个对象中.

小demo

// store  state: {    infoName: "handsome"  },  mutations: {    setInfoName(state, payload) {      state.infoName = payload;    }  }// 组件1import { mapMutations } from "vuex";import bother2 from "./children/brother2";export default {  name: "parent",  components: { bother2 },  methods: {    ...mapMutations(["setInfoName"]),    setInfo() {      const name = "ugly";      this.setInfoName(name);    }  }};script>// 组件2import { mapState } from "vuex";export default {  name: "brother2",  computed: {    ...mapState({      infoName: s => s.infoName    })  }};script>
summarize: Vuex相对来说比redux简单一些,详细可以参考中文官网 六. 自定义broadcast/dispatch
  • vue 1.x 版本中有两个API $dipatch,$broadcast,$broadcast和$dispatch 这两个API在2.x版本中去除. 实际上我们经常写一些自定义组件库,或者高阶组件的时候可能会用到.
    vue 1.x解释

    $dispatch:向上级派发事件,祖辈组件中$on监听到
    $broadcast:与$dispatch相反,向下级广播事件.

  • 自定义代码实现功能.

/** * @param {*} componentName  // 组件名 * @param {*} eName  // 自定义事件名称 * @param {*} params // 传递参数数据 */export function broadcast(componentName, eName, params) {  this.$children.forEach(child => {    const name = child.$options.name;    if (name === componentName) {      // 调用子组件emit      child.$emit.bind(child)(eName, params)    } else {      // 递归调用      broadcast.bind(child)(componentName, eName, params)    }  })};/** * @param {*} componentName  // 组件名 * @param {*} eName  // 自定义事件名称 * @param {*} params // 传递参数数据 */export function dispatch(componentName, eName, params) {  let parent = this.$parent || this.$root;  let name = parent.$options.name;  // 往上寻找 直到找到  while (parent && (!name || name !== componentName)) {    parent = parent.$parent;    if (parent) name = parent.$options.name;  }  if (parent) parent.$emit.bind(parent)(eName, params)}
解析
  1. this.$options.xx 可以取到vue组件中export default暴露的对象的对应xx属性值.我们一帮用来取一些静态属性.例如 组件的name值,判断是哪个组件.

  2. 我们找到对应的子组件或者父组件,然后用$emit调用,实际上就相当于我们在对应的组件A中用this.$emit(xxx)调用其在当前组件A中created生命周期中$on监听的事件.

  3. 实际的逻辑就是找到对应组件实例, 组件实例$emit 自己本身$on监听的事件.

引入 main.js

import { broadcast, dispatch } from './dispatch-broadcast';Vue.prototype.$dispatch = dispatch;Vue.prototype.$broadcast = broadcast;
  • 实例引用.

  1. $dispatch 派发

//  后代<template>  <div><button @click="setParentDay('Sat')">buttonbutton>div>template><script>export default {  name: "children",  methods: {    setParentDay(day) {      this.$dispatch("parent", "setDay", day);    }  }};script>//  祖先<div>    <children>children>    <p>{{ day }}p>  div>template><script>import children from "./children/children";export default {  name: "parent",  components: { children },  data() {    return { day: "Fir" };  },  created() {    this.$on("setDay", this.setDay);  },  methods: {    setDay(day) {      this.day = day;    }  }};script>
2.$broadcast  广播
// 祖先<template>  <div>    <children>children>    <button @click="setChildrenDay('Fir')">buttonbutton>  div>template><script>import children from "./children/children";export default {  name: "parent",  components: { children },  methods: {    setChildrenDay(day) {      this.$broadcast("children", "setDay", day);    }  },};script>// --------- 后代 --------------<template>  <div>{{ day }}div>template><script>export default {  name: "children",  data() {    return {      day: "Sat"    };  },  created() {    this.$on("setDay", this.setDay);  },  methods: {    setDay(day) {      this.day = day;    }  }};script>

七. 自定义findComponents多个方法

  • 就像上面说的,其实我们寻找到了对应组件的实例,就可以用这个实例进行操作,就可以说进行了组件的通信.那么这里就存在几个问题. (注意这里的前提是组件中name的属性设置严格按照规范),这些方法一般在我们自定义组件库,或者定义一些高阶组件用来使用.

    提出问题.
  1. 如何由一个组件向上找到第一个最近的指定组件?

  2. 如何由一个组件向上找到所有的指定组件?

  3. 如何由一个组件向下找到最近的指定组件?

  4. 如何由一个组件向下找到所有的指定组件?

  5. 如何由一个组件找到指定的兄弟组件?

分析:
  • 利用$options.name $children $parent , 参数包含当前组件的this,要找到的组件名name. 通过$options.name确定寻找的组件.

    1. 由一个组件向上找到第一个最近的指定组件.
/** * @param {*} context  执行上下文,这里一般传 this * @param {*} componentName 要找到的组件名 name * @returns */function findComponentUpwrad(context, componentName) {  let parent = context.$parent;  let { name } = parent.$options;  while (parent && (!name || [componentName].indexOf(name) < 0)) {    parent = parent.$parent;    if (parent) name = parent.$options.name;  }  return parent;}
2. 由一个组件向上找到所有的指定组件
/** * @param {*} context  执行上下文,这里一般传 this * @param {*} componentName 要找到的组件名 name */function findComponentsUpward(context, componentName) {  const parents = [];  const parent = context.$parent;  if (parent) {    if (parent.$options.name === componentName) parents.push(parent);    return parents.concat(findComponentUpwrad(parent, componentName));  }  return [];}
3. 由一个组件向下找到最近的指定组件
/** *@description 向下找到最近的指定组件 * * @context {*} context 执行上下文,这里一般传 this * @componentName {*} componentName 要找到的组件名 name */function findComponentDownward(context, componentName) {  const childrens = context.$children;  let children = null;  if (childrens.length) {    for (const child of childrens) {      const { name } = child.$options;      if (name === componentName) {        children = child;        break;      } else {        children = findComponentDownward(child, componentName);        if (children) break;      }    }  }  return children;}
4. 由一个组件向下找到所有的指定组件
/** * @context {*} context 执行上下文,这里一般传 this * @componentName {*} componentName 要找到的组件名 name */function findComponentsDownward(context, componentName) {  return context.$children.reduce((components, child) => {    if (child.$options.name === componentName) components.push(child);    const foundChilds = findComponentsDownward(child, componentName);    return components.concat(foundChilds);  }, []);}
5. 由一个组件找到指定的兄弟组件
/** * @context {*} context 执行上下文,这里一般传 this * @componentName {*} componentName 要找到的组件名 name * @exceptMe  {Boolean}  是否包含本身 * @description2 Vue.js 在渲染组件时,都会给每个组件加一个内置的属性 _uid,这个 * * *_uid 是不会重复的, */function findBrothersComponents(context, componentName, exceptMe) {  const res = context.$parent.$children.filter(item => item.$options.name === componentName);  const index = res.findIndex(item => item._uid === context._uid);  if (exceptMe) res.splice(index, 1);  return res;}
找到组件后就等于找到组件中的this,之后通信的方式就可以很随意了,当然这里使用方式一般是存在特殊情况下,正常我们组件之间的通信使用Vuex 或者 props $emit 就可以了.
代码参考 iview源码 具体位置在 iview assets.js,有兴趣的朋友可以查看源码.

总结:Vue组件之间的通信,当然可能还有更多,这里容纳了大部分,当然可能还有其他一些.

好学而不勤问非真好学者. 如果有帮助请点上一个赞,如果由疑问,请评论留言.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值