React 有Provider-Consumer+Hook模式,Vue 3.0 也要!

1,343 阅读6分钟

React 有Provider-Constmer+Hook模式,Vue 3.0 也要!

前言

熟悉React的小伙伴都知道,随着React hook的推出,React出现了一种新型的状态管理方式 Provider-Consumer模式,可以说是真香警告。

随着Vue3.0的发布,提供了我们属于Vue的 Hook+provider-inject的新型状态管理工具。 本篇文章将以概念+案例结合的形式为大家讲解该模式的用法和优点。

文章目录:

  1. 引出问题,这种模式解决了什么问题?(优点)
  2. 如何使用,代码解构,项目案例。(以代码为主)

1. 引出问题

首先我以自问自答的方式来引出一些问题和以往的解决方案。带着问题学习是一种很好的学习方法吧。(我要我觉得)

  1. 在vue中,兄弟组件、祖孙组件如何通信?

看到第一个问你,你是不是嘴角微微上扬,45度角仰望天空,微撩刘海(如果你不是秃头的话😄),觉得这很easy

  • Vuex;不管多复杂的组件通信,Vuex仿佛一键搞定。然而,Vuex作为一个状态管理的重型利器,在面对少量的需要多组件共享的数据,显然是杀鸡用🐂🔪啊。况且在一个小型的Vue项目中,基本都不用Vuex,太重了不是么?

  • EventBus;这种方法常用于兄弟级别的组件通信。弊端也是非常明显;

  1. EventBus不会随着组件的销毁而自动销毁,需要我们手销毁;
  2. 挂在到全局;
  3. 注意:一定要记得销毁,否则会出现bug;
  4. 事件发生者只能单向广播,无法获得接收者对事件的处理结果;
  • 其他(attrs,provide-inject(vue2.x的,本地缓存,url传参)。这些方法都不太常用,虽然能勉强解决跨组件通信,但都是有种舍本逐末的感觉。(我要我觉得😄);
  1. 在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. 新增待办事件
  2. 编辑待办事件
  3. 删除待办事件
  4. 查看待办事件

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 排版