likes
comments
collection
share

vue3玩法摸索!一种全新的组件封装思路:量子纠缠式

作者站长头像
站长
· 阅读数 29

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

如果您不喜欢看作者吹牛哔,可以跳过第0章😆😆😆😆,直接进入第1章看干货。

另:末尾已附上本文例子demo的github地址,供参考。

0. 也许并不存在的故事

vue3玩法摸索!一种全新的组件封装思路:量子纠缠式

还有这种好事? 省/市/区联动组件嘛,用Element-Plus的多级联动组件,我5分钟就能整出来。 虽然我为人素来本分,是绝对不可能女同事有任何工作外的交集的。 但是架不住红包太香,而且正好我的steam游戏库里缺一部《只狼》,而且又适逢steam促销... 于是我就很爽快接下了这个活儿。 但是,很快啊,不讲武德的事情就发生了:

vue3玩法摸索!一种全新的组件封装思路:量子纠缠式

当时我都整个人....都如同被一万只猛犸拱到了天上一样崩溃。 好家伙,我就说,天上绝对不可能掉馅饼的。 这个组件应该怎么封装呢? 很快,一招我刚刚领悟不久的绝招就涌上了心头,我给这招取的名称就叫: 量子纠缠组件! 大招就是大招,不到30分钟我就完成了工作。

1. “量子纠缠组件”超高的“布局”灵活性

先看看组件的引入和使用:

<template>
  <div>
    <ProvinceSelect v-model="formData.province" clearable/>
    <CitySelect v-model="formData.city"/>
    <AreaSelect v-model="formData.area"/>
  </div>
</template>
<script setup>
import { reactive } from '@vue/reactivity';
import { useLinkage } from './components/my-linkage';
const { ProvinceSelect, CitySelect, AreaSelect } = useLinkage()
const formData = reactive({
  province: null,
  city: null,
  area: null
})
</script>

效果如下:

vue3玩法摸索!一种全新的组件封装思路:量子纠缠式

基本功能是有了,联动也实现了,使用上也非常便捷,所有的联动都被隐藏到了组件内部,无需使用者再在页面上写任何维护它们联动的多余代码了。 那么能不能满足产品经理小美那近乎无理的布局要求呢? 当然! 布局灵活可是“量子纠缠组件”最最最牛哔的地方了~~

    <div>省:</div>
    <ProvinceSelect v-model="formData.province" clearable/>
    <div>市:</div>
    <CitySelect v-model="formData.city"/>
    <div>区:</div>
    <AreaSelect v-model="formData.area"/>

vue3玩法摸索!一种全新的组件封装思路:量子纠缠式

因为每个组件都是一个单独的标签,因此你完全可以按心意调整布局和样式! 再逆天的布局,我都不怕不怕啦~ 接下来,让我们来看看,这种组件到底要怎么开发,思路又是什么~

2. 什么是“量子纠缠组件”?

其实“量子纠缠组件”只是我为了好记,给它们取的“艺名”。 准确描述的话,应该是这样:

基于Vue组合式API,具备内置状态的,一组"函数式组件"。 几个关键词:

  • Vue组合式API: 这种写法有其依赖的环境,那就是Vue组合式API。(别害怕,vue2和vue3目前都可以用组合式API哦)
  • 有内置状态:这种组件的内置状态是存放在useLinkage()这个方法的闭包内的,因此它们很安全,不用担心在其他地方被误篡改。
  • 一组:省组件+市组件+区组件,三个独立可用的标签,当然算是一组啦;
  • 函数式组件:后面会讲到此组件的具体实现,依赖了Vue的函数式写法,在文章末尾放到DEMO源码里,也只列出了Vue3函数式写法的实现,关于Vue2的实现,可能需要你去尝试~

那么,写“量子纠缠组件”的思路又是什么呢?

3. 思路分析

3.1 基本结构

先看组件的使用方法:

import { useLinkage } from './components/my-linkage';
const { ProvinceSelect, CitySelect, AreaSelect } = useLinkage()

很明显,“量子纠缠组件”并不是像普通组件那样被直接引入,而是先引入了一个useLinkage方法,通过执行它,来返回三个组件,因此,我们很容易就能定义出组件的基本轮廓:

export const useLinkage = () => {
   // ...
    return {
        ProvinceSelect,
        CitySelect,
        AreaSelect
    }
}

而上面讲过,这几个组件都是函数式组件,打开Vue3官网-函数式组件一章地址点我,Vue3里的函数式组件长下面这样:

const FunctionalComponent = (props, context) => {
  // ...
}

省/市/区联动组件都是以Select形式的函数式组件,这里我们以Element-Plus为例,可以很快的把三个组件写出一个雏形。以省级选择组件为例:

import { createVNode } from 'vue'
// cityTree是省市区三级的树形json结构
import cityTree from './city-tree.json'

const ProvinceSelect = (props, context) => {
  // 这里是创建Array<ElOption>的VNodes数组,作为ElSelect的子VNodes
  const provinceOptions = createOptionVNodes(cityTree)
  return createVNode(ElSelect, context.attrs, provinceOptions)
}

按照上面这个思路,我们可以很容易的创建出三个函数式组件,并且按描述的用法,可以顺利渲染出三个ElSelect出来。

3.2 如何在函数内持有内置的状态

主体思路,还是vue Hooks的用法。 什么是vue Hooks?(可不是vue生命周期)可参考:# vueuse:vue hooks 方法集 那么我们怎么用vue Hooks的思路来构建本文的基本思路呢?

vue3玩法摸索!一种全新的组件封装思路:量子纠缠式

原谅我...手残草图画的太丑。 基本过程如下:

    1. useLinkage()方法因为是在vue页面的setup周期内执行的,因此可以在它内部通过import的方式,使用vue内的ref,reactive, onMounted, watch ...等一系列组合式API
    1. 通过这些组合式API,我们可以在useLinkage内部创建响应式对象,可以监听,可以利用vue的生命周期,可以做的事情可太多了。
    1. 我们通过return的方式,将要暴露给vue页面的内容,在本例中是三个函数式组件,但它可以范围更广:响应式对象、方法、组件 等等。
    1. vue页面通过useLinkage()返回的内容完成页面渲染,享受细节被封装的快乐。

3.3 如何实现组件之间的联动

为了实现在useLinkage()内部封装组件之间联动的细节,让使用者完全不用操心这部分细节,我做了以下几类工作:

  • 第一:拦截事件和属性省级选择组件为例:
  // 省级
  const currentProvince = ref(null)
  let onProvinceOriginChange
  // 用存放在闭包内的方法代替用户原本绑定在组件上的`onUpdate:modelValue`
  const onProvinceChange = (v) => {
    if (onProvinceOriginChange) {
      onProvinceOriginChange(v)
    }
    currentProvince.value = v
  }
  
  const ProvinceSelect = (props, context) => {
    const provinceOptions = createOptionVNodes(cityTree)
    onProvinceOriginChange = context.attrs['onUpdate:modelValue'] // ←这行
    currentProvince.value = context.attrs.modelValue // ←这行
    return createVNode(ElSelect, 
    { ...context.attrs, 'onUpdate:modelValue': onProvinceChange }, // ←这行
    provinceOptions)
  }

主要关注上面代码里,注释标注了 // ←这行的三行代码,这是拦截事件和属性的关键:

面对属性,直接将它存储到一个ref对象内。

面对事件,用一个变量将事件处理方法存储,然后用一个新方法将其包裹起来,并用新方法代替原有的事件处理方法递交给组件。

通过拦截事件和属性, 我们就可以完全控制组件的各种运转了。 比如: 当我们想清空省级组件v-model当前绑定的值时,只需要调用:

onProvinceChange(null)

就能轻松达成我们的目标。

  • 第二:监听和响应 通过ref、computed、watch监听、响应方式,完成组件之间数据的联动。 如市级选择组件
  const cities = computed(() => { // ← 这里
    return cityTree.find(t => t.id == currentProvince.value)?.children ?? [];
  })
  const CitySelect = (props, context) => {
    const cityOptions = createOptionVNodes(cities.value) // ←这里
    return createVNode(ElSelect, context.attrs, cityOptions)
  }

通过上面标记的这两行,就能完全达成市级列表跟随省级当前选中的省而联动的效果了。

  • 第三:结合上面第一和第二两点 接下来就是编程的乐趣所在地,在劫持的事件中修改响应式对象,在监听到的变化中,主动调用劫持的钩子以通知父组件更新参数... 如:
  const onProvinceChange = (v) => {
    if (onProvinceOriginChange) {
      onProvinceOriginChange(v)
    }
    currentProvince.value = v
    onCityChange(null) // ←这里
    onAreaChange(null) // ←这里
  }

以上就是监听到用户更改省级选中值之后,主动清空市、区两级选定值的操作。

4. 展望

Vue组合式API出现之后,Vue Hooks就开始有了更多的玩法。

所谓的“量子纠缠组件”当然不是什么特别新的东西,但足够给我们在组建封装业务复用上带来非常多的灵感,我们对组件的定义也可以从Component Class这个概念更多的延伸开。

我们完全可以想象,利用Vue组合式API,我们能够做到非常灵活非常细粒度的复用,这正是Vue组合式API的魅力所在,也是我认为它比混入(Mixin)更有潜力的内容。

5. Demo源码(附github仓库)

# github: zhangshichun/vue-demos

地址: github.com/zhangshichu…

6. 有可能的故事结局

不喜欢看作者吹哔的朋友可以不用往下看了😆😆😆😆

就在我洋洋自得发布完npm,通知完产品经理小美后,终于迎来了故事大结局。

vue3玩法摸索!一种全新的组件封装思路:量子纠缠式