vue3玩法摸索!一种全新的组件封装思路:量子纠缠式
「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」
如果您不喜欢看作者吹牛哔,可以跳过第0章😆😆😆😆,直接进入第1章看干货。
另:末尾已附上本文例子demo的github地址,供参考。
0. 也许并不存在的故事
还有这种好事?
省/市/区联动组件嘛,用Element-Plus的多级联动组件
,我5分钟就能整出来。
虽然我为人素来本分,是绝对不可能
和女同事
有任何工作外的交集的。
但是架不住红包太香
,而且正好我的steam游戏库里缺一部《只狼》
,而且又适逢steam促销...
于是我就很爽快接下了这个活儿。
但是,很快啊,不讲武德的事情就发生了:
当时我都整个人....都如同被一万只猛犸拱到了天上一样崩溃。
好家伙,我就说,天上绝对不可能掉馅饼
的。
这个组件应该怎么封装呢
?
很快,一招我刚刚领悟不久的绝招
就涌上了心头,我给这招取的名称就叫:
量子纠缠组件!
大招就是大招,不到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>
效果如下:
基本功能是有了,联动也实现
了,使用上也非常便捷
,所有的联动都被隐藏到了组件内部,无需使用者再在页面上写任何维护它们联动
的多余代码了。
那么能不能满足产品经理小美
那近乎无理的布局要求
呢?
当然!
布局灵活
可是“量子纠缠组件”最最最牛哔
的地方了~~
<div>省:</div>
<ProvinceSelect v-model="formData.province" clearable/>
<div>市:</div>
<CitySelect v-model="formData.city"/>
<div>区:</div>
<AreaSelect v-model="formData.area"/>
因为每个组件都是一个单独的标签,因此你完全可以按心意调整布局和样式
!
再逆天的布局
,我都不怕不怕啦~
接下来,让我们来看看,这种组件到底要怎么开发,思路又是什么~
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的思路来构建本文的基本思路呢?
原谅我...手残草图画的太丑。 基本过程如下:
-
useLinkage()
方法因为是在vue页面的setup周期内执行的,因此可以在它内部通过import的方式,使用vue内的ref,reactive, onMounted, watch ...
等一系列组合式API
。
-
- 通过这些组合式API,我们可以在
useLinkage
内部创建响应式对象
,可以监听
,可以利用vue的生命周期
,可以做的事情可太多了。
- 通过这些组合式API,我们可以在
-
- 我们通过return的方式,将要暴露给vue页面的内容,在本例中是
三个函数式组件
,但它可以范围更广:响应式对象、方法、组件
等等。
- 我们通过return的方式,将要暴露给vue页面的内容,在本例中是
-
- vue页面通过
useLinkage()
返回的内容完成页面渲染,享受细节被封装的快乐。
- vue页面通过
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
6. 有可能的故事结局
不喜欢看作者吹哔的朋友可以不用往下看了😆😆😆😆
就在我洋洋自得
发布完npm,通知完产品经理小美
后,终于迎来了故事大结局。
转载自:https://juejin.cn/post/7034673232573792263