导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

业务技能

针对性攻坚

AI


介绍

通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。

官网的解释很让人疑惑,那我翻译下这几句话:

provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。

Untitled

使用场景

当父组件有很多数据需要分发给其子代组件的时候, 就可以使用 provide 和 inject 。

基本用法

App.vue

<template>
  <inject-child></inject-child>
</template>

<script>
import injectChild from '@/components/injectChild';
import { provide } from 'vue';
export default {
  name: 'App',
  components: {
    injectChild
  },
  // provide: { // Vue2写法
  //   name: 'Lance',
  //   age: 28
  // },
  setup() {
    // Vue3写法
    provide('name', 'Lance');
    provide('age', 28);
  }
}
</script>

injectChild.vue

<template>
  <div>
    <div>{{ name }}</div>
    <div>{{ age }}</div>
  </div>
</template>
<script>
import { inject } from 'vue';
export default {
  name: 'InjectChild',
  // inject: ['name', 'age'] // Vue2写法
  setup() {
    // Vue3写法
    const name = inject('name');
    const age = inject('age', 'loading...');
    return {
      name,
      age
    }
  }
}
</script>

Untitled

provide 响应式

父组件更新 provide

App.vue

<template>
  <div>
    <inject-child></inject-child>
    <button @click="changeName">改变名字</button>
  </div>
</template>

<script>
import injectChild from '@/components/injectChild';
import { provide, ref } from 'vue';
export default {
  name: 'App',
  components: {
    injectChild
  },
  setup() {
    const name = ref('Lance');
    provide('name', name);
    provide('age', 28);

    const changeName = () => {
      name.value = 'GC'; // 子组件能监听到父组件对name的更改
    }

    return {
      changeName
    }
  }
}
</script>

injectChild.vue

<template>
  <div>
    <div>{{ name }}</div>
    <div>{{ age }}</div>
  </div>
</template>
<script>
import { inject } from 'vue';
export default {
  name: 'InjectChild',
  setup() {
    const name = inject('name');
    const age = inject('age', 'loading...');
    return {
      name,
      age
    }
  }
}
</script>

子组件更新 provide

App.vue

<template>
  <div>
    <inject-child></inject-child>
  </div>
</template>

<script>
import injectChild from '@/components/injectChild.vue';
import { provide, ref } from 'vue';
export default {
  name: 'App',
  components: {
    injectChild
  },
  setup() {
    const name = ref('Lance');
    const changeName = () => {
      name.value = 'GC';
    }

    provide('name', name);
    // 父组件把更改函数通过 provide 传递给 子组件
    provide('changeName', changeName);
  }
}
</script>

injectChild.vue

<template>
  <div>
    <div>{{ name }}</div>
    <button @click="changeName">改变名字</button>
  </div>
</template>
<script>
import { inject } from 'vue';
export default {
  name: 'InjectChild',
  setup() {
    const name = inject('name');
    const changeName = inject('changeName');
    return {
      name,
      changeName
    }
  }
}
</script>

如果不希望子组件更改 provide,可以用 readonly 包裹 provide

App.vue

<template>
  <div>
    <inject-child></inject-child>
  </div>
</template>

<script>
import injectChild from '@/components/injectChild_子组件改变provide.vue';
import { provide, ref, readonly } from 'vue';
export default {
  name: 'App',
  components: {
    injectChild
  },
  setup() {
    const name = ref('Lance');
    const changeName = () => {
      name.value = 'GC';
    }
    // 这样子组件就无法更改name了(如果不用readonly包裹,子组件可以直接更新 name.value)
    provide('name', readonly(name));
    // 父组件把更改函数通过 provide 传递给 子组件
    provide('changeName', changeName);
  }
}
</script>

injectChild.vue

<template>
  <div>
    <div>{{ name }}</div>
    <button @click="changeName">改变名字</button>
    <button @click="injectChildChangeName">子组件改变name</button>
  </div>
</template>
<script>
import { inject } from 'vue';
export default {
  name: 'InjectChild',
  setup() {
    const name = inject('name');
    const changeName = inject('changeName');
    const injectChildChangeName = () => {
      name.value = 'Sherry'; // 无法更改父组件name
    }
    return {
      name,
      changeName,
      injectChildChangeName
    }
  }
}
</script>

Untitled

Untitled

setup script 语法糖

父组件

<template>
    <div>祖先组件{{obj.lv1.lv2.num}}</div>
    <button @click="obj.lv1.lv2.num++">按钮++</button>
    <Child />
</template>

<script setup>
import Child from './Child.vue'
import { ref,provide,readonly } from 'vue';

// 为保证依赖注入是响应式,使用ref
let obj = ref({
    name:'paul',
    age:18,
    lv1:{
        lv2:{
            num:666
        }
    }
})
// 看源码可知setup语法糖只支持一次provide一个变量
// 子孙组件可以直接逆向修改祖先组件的传递过来的值,这是不允许的。所以可以增加一个readonly,防止子组件修改祖先组件造成混乱。
provide('obj',readonly(obj))
</script>

子组件

<template>
    <div>子组件{{obj?.lv1.lv2.num}}</div>
    <GrandChild />
</template>
<script setup lang='ts'>
import GrandChild from './GrandChild.vue'
import { inject } from 'vue';

let obj = inject('obj')
</script>

总结

  1. provide在vue3里的setup语法糖里只支持一次provide一个变量,这点是不如vue2的,vue2(配置项写法)可以批量使用。
  2. 直接使用provide和inject为非响应式,我们知道在vue2中使用provide想达到响应式可以在provide的时候用函数包裹住并在,inject的时候用computed调用这个函数拿到数据。在vue3中想做到响应式比较方便,直接使用ref或者reactive包裹住数据,即可做到依赖注入的响应式。
  3. provide只支持在setup语法糖里调用,
  4. 禁止子孙组件修改祖先组件的注入,可以在祖先组件使用readonly将变量设置为只读。