vue组件间传递参数`$attrs/$listeners`和`provide/inject`

315 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

vue组件中参数传递有很多种:

  1. props/$emit
  2. EventBus
  3. vuex
  4. $attrs/$listeners
  5. provide/inject

其实我们对前3种传递方式已经很熟悉了,这里就不做赘述,但是$attrs/$listenersprovide/inject这两对还是需要重点熟悉一下。

$attrs/$listeners

用于当祖先组件想传递属性或者方法到孙子组件时,路过子组件,在子组件中用v-bind="$attrs"(传递属性)或v-on="$listeners"(传递事件),不需要在路过的子组件中多写一遍emit和props来接收和传递

例子如下:

index.vue

<template>
  <div class="box">
    <h3>祖先页面</h3>
    <son :name="name" :color="color" @getData="getData" @setData="setData"></son>
  </div>
</template>

<script>
import Son from './son.vue'
export default {
  components: { Son },
  data () {
    return {
      name: 'myTest',
      color: '#cdcdcd'
    }
  },
  methods: {
    getData () {
      console.log('getData', this.name)
    },
    setData (v) {
      this.name = v
    }
  }
}
</script>

son.vue

<template>
  <div class="box">
    <h3>父页面</h3>
    <grand-son v-bind="$attrs" v-on="$listeners"></grand-son>
  </div>
</template>

<script>
import GrandSon from './grandson.vue'
export default {
  components: { GrandSon }
}
</script>

grandson.vue

<template>
  <div class="box">
    <h3>子页面</h3>
    color: {{color}}
    <input v-model="data"/>
    <button @click="setData">设置</button>
    <button @click="getData">{{name}}</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      data: '888'
    }
  },
  props: {
    name: String,
    color: String
  },
  methods: {
    getData () {
      this.$emit('getData')
    },
    setData () {
      this.$emit('setData', this.data)
    }
  }
}
</script>

这样就可以在grandson.vue中使用祖先里的属性和方法了。

image.png

image.png

provide/inject

这一对的用法是,祖先组件向后代注入依赖,注入后可以在后代组件中任意使用祖先组件中的方法或事件,中间不需要传递

注意到这个用法是因为做项目时,遇到了vue单页面需要刷新页面的场景,这种场景下,为了不让整个页面白屏刷新,其实直接在app.vue中<router-view>的地方用v-if渲染,此时如果在组件里需要刷新页面,就要调用祖先组件里的reload方法,就需要用到provide和inject

app.vue

<template>
    <router-view v-if="isRenderRouterView"></router-view>
</template>
<script>
    export default {
        name: 'app',
        data () {
          return {
            isRenderRouterView: true
           }
        },
        methods: {
          reload () {
            this.isRenderRouterView = false
            this.$nextTick(() => {
              this.isRenderRouterView = true
            })
          }
        },
        provide () {
          return {
            reload: this.reload
          }
        },
</script>

component.vue

<template>
  <div class="box">
    <h3>组件</h3>
    <button @click="refresh">刷新</button>
  </div>
</template>

<script>
export default {
  inject: ['reload'],
  methods: {
      refresh () {
        this.reload()
      }
  }
}
</script>

需要注意的是:provide/inject 破坏了vue单向数据流原则。如果多个组件同时依赖一个祖先组件中的数据状态,只要一个组件修改这个状态,所有组件都会受到影响,而且无法追溯到底是哪个组件修改了。因此,用provide/inject来做和vuex一样的状态管理是很危险的。他更适合用在组件的开发中。例如element-ui。