vue中组合式 API-依赖注入之provide和inject的用法(二)——添加响应性 & 污染全局数据、破坏了单向数据流,vuex可追踪数据 & 三种通信方式之父子通信、兄弟通信、跨级通信

1、给 provide/inject 添加响应性

在vue3中给 provide/inject 添加响应性,使用 ref 或 reactive 。

//父组件代码
<template>
  <div>
    info:{{info}}
    <InjectCom ></InjectCom>
  </div>
</template>
<script>
import InjectCom from "./InjectCom"
import { provide,readonly,ref } from "vue"
export default {
  setup(){
    let info = ref("今天你学习了吗?")
    setTimeout(()=>{
      info.value = "不找借口,立马学习"
    },2000)
    provide('info',info)
    return{
      info
    }
  },
  components:{
    InjectCom
  }
}
</script>

// InjectCom 子/孙组件代码
<template>
 {{info}}
</template>
<script>
import { inject } from "vue"
export default {
  setup(){
    const info = inject('info')
    setTimeout(()=>{
      info.value = "更新"
    },2000)
    return{
      info
    }
  }
}
</script>

上述示例,在父组件或子组件都会修改 info 的值。
provide / inject 类似于消息的订阅和发布,遵循 vue 当中的单项数据流,什么意思呢?就是数据在哪,修改只能在哪,不能在数据传递处修改数据,容易造成状态不可预测。

在订阅组件内修改值的时候,可以被正常修改,如果其他组件也使用该值的时候,状态容易造成混乱,所以需要在源头上规避问题。

readonly 只读函数,使用之前需要引入,如果给变量加上 readonly 属性,则该数据只能读取,无法改变,被修改时会发出警告,但不会改变值。

使用方法:

import { readonly } from "vue"
let info = readonly('只读info值')
setTimout(()=>{
 info="更新info" //两秒后更新info的值
},2000)

运行两秒后,浏览器发出警告,提示 info 值不可修改。
所以我们就给provide发射出去的数据,添加一个只读属性,避免发射出去的数据被修改。在上面实例3的 provide 处添加 readonly 。

provide('info', readonly(info))

在子组件修改值的时候,会有一个只读提醒。
所以在修改值的时候,还是需要在 provide 发布数据的组件内修改数据,在父组件内添加修改方法,同时也发布出去,在子组件处调用就可以了。如:

//发布
let info = ref("今天你学习了吗?")
const changeInfo = (val)=>{
 info.value = val
}
provide('info',readonly(info))
provide('changeInfo',changeInfo)

//订阅
const chang = inject('changeInfo')
chang('冲向前端工程师')

完整示例:

// 父组件代码
<template>
  <div>
    info:{{info}}
    <InjectCom ></InjectCom>
  </div>
</template>

<script>
import InjectCom from "./InjectCom"
import { provide,readonly,ref } from "vue"
export default {
  setup(){
    let info = ref("今天你学习了吗?")
    const changeInfo = (val)=>{
      info.value = val
    }
    provide('info',readonly(info))
    provide('changeInfo',changeInfo)
    return{
      info
    }
  },
  components:{
    InjectCom
  }
}
</script>

//InjectCom 子组件代码
<template>
  <div>
    <button @click="chang('冲向前端工程师')">更新值</button>
  </div>
</template>
<script>
import { inject } from "vue"
export default {
  setup(){
    const info = inject('info')
    const chang = inject('changeInfo')
    return{
      info,
      chang
    }
  }
}
</script>
2、慎用 provide / inject

既然 provide/inject 如此好用,那么,为什么 Vue 官方还要推荐我们使用 Vuex,而不是用原生的 API 呢?

前面提到过,Vuex 和 provide/inject 最大的区别

Vuex 中的全局状态的每次修改是可以追踪回溯的,而 provide/inject 中变量的修改是无法控制的。换句话说,不知道是哪个组件修改了这个全局状态。

Vue 的设计理念借鉴了 React 中的单向数据流原则(虽然有 sync 这种破坏单向数据流的家伙),而 provide/inject 明显破坏了单向数据流原则。试想,如果有多个后代组件同时依赖于一个祖先组件提供的状态,那么只要有一个组件修改了该状态,那么所有组件都会受到影响。这一方面增加了耦合度,另一方面,使得数据变化不可控。如果在多人协作开发中,这将成为一个噩梦。

在这里,总结了使用 provide/inject 做全局状态管理的原则:

  • 多人协作时,做好作用域隔离;

  • 尽量使用一次性数据作为全局状态

看起来,使用 provide / inject 做全局状态管理好像很危险,那么有没有 provide / inject 更好的使用方式呢?
当然有,那就是使用 provide / inject 编写组件。

使用 provide / inject 编写组件

使用 provide/inject 做组件开发,是 Vue 官方文档中提倡的一种做法。

以我们比较熟悉的 elementUI 来举例:

在 elementUI 中有 Button(按钮)组件,当在 Form(表单)组件中使用时,它的尺寸会同时受到外层的 FormItem 组件以及更外层的 Form 组件中的 size 属性的影响。

如果是常规方案,我们可以通过 props 从 Form 开始,一层层往下传递属性值。看起来只需要传递传递两层即可,还可以接受。但是,Form 的下一层组件不一定是 FormItem,FormItem 的下一层组件不一定是 Button,它们之间还可以嵌套其他组件,也就是说,层级关系不确定。如果使用 props,我们写的组件会出现强耦合的情况。

provide/inject 可以完美的解决这个问题,只需要向后代注入组件本身(上下文),后代组件中可以无视层级任意访问祖先组件中的状态。

部分源码如下:

export default {
  name: 'ElButton',
  // 通过 inject 获取 elForm 以及 elFormItem 这两个组件
  inject: {
    elForm: {
      default: ''
    },
    elFormItem: {
      default: ''
    }
  },
  // ...
  computed: {
    _elFormItemSize() {
      return (this.elFormItem || {}).elFormItemSize;
    },
    buttonSize() {
      return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
    },
    //...
  },
  // ...
};
3、Vue中常见的组件通信方式可分为三类

1、父子通信

  • 父向子传递数据是通过 props,子向父是通过 events($emit);

  • 通过父链 / 子链也可以通信($parent / $children);

  • ref 也可以访问组件实例;

  • provide / inject;

  • a t t r s / attrs/ attrs/listeners;

2、兄弟通信

  • Bus;
  • Vuex;

3、跨级通信

  • Bus;
  • Vuex;
  • provide / inject
  • $attrs / $listeners
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐