React 有Provider-Constmer+Hook模式,Vue 3.0 也要!
前言
熟悉React的小伙伴都知道,随着React hook的推出,React出现了一种新型的状态管理方式 Provider-Consumer模式,可以说是真香警告。
随着Vue3.0的发布,提供了我们属于Vue的 Hook+provider-inject的新型状态管理工具。 本篇文章将以概念+案例结合的形式为大家讲解该模式的用法和优点。
文章目录:
- 引出问题,这种模式解决了什么问题?(优点)
- 如何使用,代码解构,项目案例。(以代码为主)
1. 引出问题
首先我以自问自答的方式来引出一些问题和以往的解决方案。带着问题学习是一种很好的学习方法吧。(我要我觉得)
- 在vue中,兄弟组件、祖孙组件如何通信?
看到第一个问你,你是不是嘴角微微上扬,45度角仰望天空,微撩刘海(如果你不是秃头的话😄),觉得这很easy
Vuex;不管多复杂的组件通信,Vuex仿佛一键搞定。然而,Vuex作为一个状态管理的重型利器,在面对少量的需要多组件共享的数据,显然是杀鸡用🐂🔪啊。况且在一个小型的Vue项目中,基本都不用Vuex,太重了不是么?
EventBus;这种方法常用于兄弟级别的组件通信。弊端也是非常明显;
- EventBus不会随着组件的销毁而自动销毁,需要我们手销毁;
- 挂在到全局;
- 注意:一定要记得销毁,否则会出现bug;
- 事件发生者只能单向广播,无法获得接收者对事件的处理结果;
- 其他(attrs,provide-inject(vue2.x的,本地缓存,url传参)。这些方法都不太常用,虽然能勉强解决跨组件通信,但都是有种舍本逐末的感觉。(我要我觉得😄);
- 在Vue2.x中,如何进行逻辑复用?
举个栗子:在后台管理项目中,多个菜单(组件)同时用到一堆数据,进行花样呈现,有列表展示、有饼图占比、有折线图趋势、有热力图说明频次等等,这些组件使用的是相同的一些数据和数据处理逻辑(增删改查);如果在每个组件只都写一套这类的处理逻辑,显然是不符合程序员的终极目标(偷懒😂); 以往的解决方案:
- Mixins
- 高阶组件 (Higher-order Components, aka HOCs)
- Renderless Components (基于 scoped slots / 作用域插槽封装逻辑的组件)
其实上面在写业务代码的时候,也就mixin常用。高阶组件和Renderless Components更多的是封装组件的时候用到的。它们存在的缺点:
- 模版中的数据来源不清晰
- 命名空间冲突。
- 需要额外的组件实例嵌套来封装逻辑(性能问题);
那么Vue将如何解决以上这些问题呢。
概念
context
我们要利用 provide+inject+hook 创建一个上下文对象,而且对外暴露 供应者(通常在组件树中上层的位置和消费者(inject)),只要在上层组件使用provide,在上下文以内的一切子组件,通过inject都能够接见这个上下文环境以内的数据,而且不必经由过程props。也就是提供了一个集合治理并具备响应式的对象,并限制了这个对象可接见的局限,在局限内的子组件都能猎取到她内部的值。
provide+inject
在上层组件通过 provide 提供一些变量,在子组件中可以通过 inject 来拿到
provide用法:
provide接受两个参数,第一个参数是provide唯一名称,最好用Symbol,避免重复。第二个参数是你要暴露的数据
provide(ThemeSymbol, data)
inject 用法:
inject 接收两个参数,第一个参数是provide名称,也就是ThemeSymbol,第二个参数是provide暴露出来的数据,也就是data。
Hook
Vue3.0的composition api和setup提供了Vue具备Hook的能力。而Hook的存在可以让你在组件之外封装公共方法,达到逻辑复用。所以,我们可以在.vue组件外自定义Hook,创建context;
文件目录解构
根据上面的观点和功用,剖析一下要完成的步骤:
- 建立一个公共context,封装自定义hook
- provide供应数据
- inject注入数据
这里的文件构造:
context
├─context // context的文件夹
├─index.js // context根组件,统一引入各个模块的context,并统一暴露
│─home // 以模块名称命名,一个模块一个context
│─index.js
View
├─View
├─Home
├─index.vue // 根组件,在这层注入inject
├─xxx.vue // 其他组件 消费者地点的层级
用法
为了方便解读,我这里以Todolist项目为例;
源码: https://github.com/961998264/todolist-vue-3.0
项目介绍:
- 新增待办事件
- 编辑待办事件
- 删除待办事件
- 查看待办事件
1. 建立一个上下文context
// 引入 composition api
import { provide, ref, Ref, inject, computed, } from 'vue'
// provide命名
export const listymbol = Symbol()
// 自定义hook,借用React hook命名标准,以use开头
export const useProvide = () => {
const list = ref([])
// 获取list
const getList = async function () { ... }
// 新增list
const addList = (item) => { ... }
// 删除事件
const delList = (id) => { ... },
...
// provide 暴露数据
provide(listymbol, {
list,
getList,
addList,
delList
})
}
2. inject注入并使用
在根组件 index.vue中注入
import { inject } from 'vue'
import { listymbol } from '@/context/home/index'
setup () {
const listContext = inject(listymbol)
console.log("setup -> listContext", listContext)
}
如果你这样使用未尝不可,但是前面我们说过了,在大的项目中,每个模块都可能有自己的context,为了方便管理,我们最好是建立一个统一管理这类‘useProvide’的中心。
我们再编辑新增一些逻辑
// 引入 composition api
import { provide, ref, Ref, inject, computed, } from 'vue'
// provide命名
export const listymbol = Symbol()
// 自定义hook,借用React hook命名标准,以use开头
export const useProvide = () => {
const list = ref([])
// 获取list
const getList = async function () { ... }
// 新增list
const addList = (item) => { ... }
// 删除事件
const delList = (id) => { ... },
...
// provide 暴露数据
provide(listymbol, {
list,
getList,
addList,
delList
})
// 新增的逻辑
export const useInject = () => {
const listContext = inject(listymbol);
if (!listContext) {
throw new Error(`useListInject must be used after useListProvide`);
}
return listContext
};
}
index.js
统一引入并到处
export { useProvide, useInject } from './home/index'
index.vue
通过useProvide提供数据
import {useProvide} from '@/context/index.js'
setup(){
// 在顶层组件提供数据
useProvide()
}
inject使用
通过inject注入数据
import {useInject} from '@/context/index.js'
setup(){
// 在顶层组件提供数据
const { list, getList ...} = useInject()
return {
list,getList
}
}
最终,home模块的context就完成了。可以看到,在home模块下任何组件,都可以共享数据(list),以及复用处理list的公共方法。而这样做不仅避开了Vue2.x跨组件通信的坑,而且在后期的维护当中,逻辑聚合将使我们更容易找到该数据的所有相关逻辑,而不是按照组件划分。
最后
如果有不对的地方,欢迎留言指正。
如果该文章对你有帮助,欢迎点赞。 扫码_搜索联合传播样式-白色版.png
本文使用 mdnice 排版