Vue组件通信全记录

711 阅读2分钟

Vue Component 相比React受控组件的概念,Vue组件之间的“通信”特征则表现的更强。

这种“通信”特性表现在,组件内部拥有一套自己的状态管理,对外只表现为信息的传递,而不需要外部组件特意做出什么行为(例如受控组件的状态完全是由控制者决定,受控组件状态的改变必须要求控制者做出响应的行为)

除了常见的父子级别通信,还有兄弟级、跨级,到总线和Vuex。不得不说Vue组件通信的方式要更丰富一些。

父组件给子组件传消息

1. 属性props

 最基础的父传子手段。

// child指定props
export default {
  props: {
    msg: String
  }
}

// parent传递消息
<child msg='msg' />

2. 引用refchildren

直接获取子组件引用来设置子组件的内部值。children是一个数组,装着所有自定义子组件,但多个子组件情况下排列不能保证顺序。所以更可靠的还是子组件制定ref,父组件使用$refs获取子组件引用。

// child
export default {
  data() {
    return {
      // parent msg by $refs
      // or
      // parent msg by $children
      current: ''
    }
  }
}

// parent
<child ref="child">
export default {
  methods: {
    handleChange1() {
      this.$refs.child.current = 'parent msg by $refs'
    },
    handleChange2() {
      this.$children[0].current = 'parent msg by $children'
    }
  },
  mounted() {
    // this.handleChange1()
    // or
    // this.handleChange2()    
  }
}

子组件给父组件传消息

1. 自定义事件

最核心的向上传递消息的手段

// child
<button @click="sendMsg" />
export default {
  methods: {
    sendMsg() {
      this.$emit('sendMsg', { msg: 'msg' })
    }
  }
}

// parent
<child @send-msg="receiveMsg" />
export default {
  methods: {
    receiveMsg(param) {
      console.log(param) // { msg: 'msg' }      
    }
  }
}

v-model.sync的本质也是props➕自定义事件的组合

祖先组件给后代组件传消息

1. 特性$attr

传递那些子组件没有设定在props里面的值,通常在子组件中使用inheritAttrs: false来取消非props值在根组件上的继承。不过$attrs特殊的是,可以在“族谱”中一直向后传递信息:每个成员使用v-bind="$attrs"都可以将祖先的非props属性传递下去。

// grandson
export default {
 mounted() {
   console.log(this.$attrs.attr) // attr
 }
}

// child
<grandson v-bing="$attrs" />
export default {
  props: {
    msg: String
  },
  mounted() {
    console.log(this.msg, this.$attrs.attr) // props, attr
  }
}

// parent传递props消息和attrs消息
<child msg="prop" attr="attr" />

2. 提供和注入provide/inject

一个祖先向所有后代提供消息的手段,不必由每一代传递消息

// grandson
export default {
  inject: ['msg'],
  mounted() {
    console.log(this.msg) // 'msg'
  }
}

// child
<grandson />

// parent
<child />
export default {
  provide() {
    return {
      msg: 'msg'
    }
  }
}

后代组件给祖先组件传消息

1. 监听器$listeners

$listeners可以一直向后传递事件(普通事件和自定义事件),由某一后代触发事件,实现向祖先传递消息

// grandson
<button @click="sendMsg">触发祖先的自定义事件</button>
export default {
  methods: {
    sendMsg() {
      this.$listeners['on-send']({ grandson: 'grandson' })
    }
  }
}

// child
<grandson v-on="$listeners"/>

// parent
<child @on-send="receiveMsg" />
export default {
  methods: {
    receiveMsg(param) {
      console.log(param) // { grandson: 'grandson' }
    }
  }
}

兄弟组件传消息

1. 借助共同父代或祖先

// brother-send
export default {
  mounted() {
    this.$parent.$emit('parent-msg', { msg: 'msg by parent' })
    this.$root.$emit('root-msg', { msg: 'msg by root' })
  }
}

// brother-receive
export default {
  created() {
    this.$parent.$on('parent-msg', (param) => {
      console.log(param) // { msg: 'msg by parent' }
    })
    this.$root.$on('root-msg', (param) => {
      console.log(param) // { msg: 'msg by root' }
    })
  },
  destroyed() {
    this.$parent.$off('parent-msg')
    this.$root.$off('root-msg')
  }
}

// parent
<div>
  <brother-send />
  <brother-receive />
</div>

无规律跨级通信

1. 事件总线Bus

可以直接借助一个空Vue对象,自带$on $emit $off的API

// bus.js
export default new Vue()

也可以自己构造

// bus.js
class Bus {
  constructor() {
    this.callbacks = {}
  }
  $on(name, fn) {
    this.callbacks[name] = this.callbacks[name] || []
    this.callbacks[name].push(fn)
  }
  $emit(name, ...args) {
    if (this.callbacks[name]) {
      this.callbacks[name].forEach(cb => cb(...args))
    }
  }
  $off(name) {
    this.callbacks[name] = null
    Reflect.deleteProperty(this.callbacks, name)
  }
}
export default new Bus()

状态管理

Vuex

Vuex能解决所有组件之间的通信问题,但实际上Vuex更像是一个状态管理的“库”。Vuex相对比较重,但是能保存所有需要的数据结构,并且所有组件都可以访问到。是否使用Vuex还是取决于项目规模,或者说是数据规模。