『可组合的Vue』别样的“小组件”设计
Posted 恪愚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了『可组合的Vue』别样的“小组件”设计相关的知识,希望对你有一定的参考价值。
最近在设计笔者所在组自己的组件库。从设计上看,一个组件库是否“成功”取决于前期的设计 —— 我决定用上ITCSS模型。为这个组件的团队维护、扩展打下坚实的基础。但这还不够,组件库最重要的组件往往是非大型库开发中最容易被忽视的。
我将组件从使用场景上分为两种类型:
- 大组件:也叫“场景组件”。功能完善,自成一片天地。取之即用,通过传参在内部做不同处理 —— 或渲染不同UI、或返回不同值。最重要的是:不侵入页面逻辑。也就是说,无论谁在什么场景下调用,都是相同的方式。拿到值以后这个组件在当前操作流中就算完成自身‘职责’了。显而易见,它有很大的缺陷:一般是针对某个场景/业务线定制的。
- 小组件:可以处理专门的逻辑。它的使用必须和页面逻辑上下文联系起来。甚至于说组件内部和外部的数据是有一定耦合的、需要N个“小组件”嵌套/组合共同完成某个功能。并且针对不同场景有不同程度的改造需要。但优势也是明显的:一般我们说的通用组件/第三方组件基本就指它。
忘了哪一本书中说过一句我非常认可的话:“使用组合而不是继承”。用在这里似乎也很巧妙。
拿笔者遇到的场景来说。结合上面我个人的定义。这个“选择商品”就可以说是“大组件”:
而像弹窗、提示层、按钮这种就是“小组件”了:
只是粗略的划分一下。上面“小组件”的示例其实并不直观。不过可以这么认为:大组件示例中跳转过去的商品选择route里每一条数据的结构(是一样的,可以拆分出来)就是一个小组件。
一般我们的组件都是这么设计的:通过props动态传参、或通过调用子组件内部暴露的方法传参1;再监听子组件传出的自定义事件并接收值。
比如这样:
//子组件
<script>
export default
props:
area:
type: Array,
default: []
,
methods:
$_setData(list=[])
this.list = xxx(list);
,
//第一种方法
$_getData()
return this.xxx;
// 第二种方法
handleData()
//一些处理
this.$emit("harea-change", xxx);
</script>
// 父组件
<area-map
:area="xxx" //第一种传参
@harea-change="handleAreaChange"
ref="mapAreaRef"
></area-map>
//js
this.$refs.mapAreaRef.$_setData(xxx); //在某个方法中调用,第二种传参
this.$refs.mapAreaRef.$_getData(); //在某个方法中调用,第一种接收参数方法
handleAreaChange(val)
//第二种接收参数方法
这两种方式的好处是写起来比较直观,尤其是在父组件/页面拿到数据后需要再进行复杂处理的情况下。
但是对于一些通用组件来说,组件内定义的数据、组件暴露出来的方法名等等都需要口口相传或者文档规定清楚。
对此,我们还有另一种方式:这里笔者以组内实际使用的「底部弹层」组件为例看一下“小组件”的嵌套使用
- 真实弹层组件的js文件
import Creater from "../utils/popup.js";
import Module from "./index.vue"; //真实弹层组件的本体
export default Creater(Module);
那第一行是什么?其实是组内规定的「底部弹层通用动画规范」。我们将其封装为一个单独的“小组件”。真实的组件时通过Vue.extend
挂载上去的(这是在js中,如果按一般写法就是父子组件)!
这样在有其它样子的弹层时也可以直接挂载而不需要再去CV动画css:
- 真实弹层组件的vue文件
<template>
<section class="mkt-popup-range">
<div class="popup-range-line" @click="setRange(1)">全店商品</div>
<div class="popup-range-line popup-range-tb" @click="setRange(2)">部分商品</div>
<div class="popup-range-line pr--cancel" @click="hideLayer">取消</div>
</section>
</template>
<style lang="less">
// css略
</style>
<script>
export default
props: //调用方传的一些参数,通过父组件传进来
options:
type: Object,
default()
return ;
,
close:
type: Function
,
data()
return
showBg: false
;
,
methods:
hideLayer()
this.close && this.close();
,
setRange(type)
this.options.callback && this.options.callback(type); //回调函数,直接在内部处理,这个在使用用时能看到效果
this.hideLayer();
;
</script>
- 动画组件的js文件
// 弹层组件样式
import "./popup.less";
import Vue from "vue";
import Popup from "./popup.vue"; //动画组件本体
const doc = document;
class ModalMaker
constructor(modalComp)
this.instance = null;
this.modalComp = modalComp; //参数挂载(这个参数是下面函数处理过后的父子组件嵌套:父Popup-子Module)
create(options)
this.instance = new this.modalComp(
el: doc.createElement("div"),
propsData:
afterLeave: () =>
doc.body.removeChild(this.instance.$el);
this.instance = null;
,
options,
);
// 添加DOM 到body
doc.body.appendChild(this.instance.$el);
// 页面元素展示
this.instance.show();
delete()
if (!this.instance) return false;
this.instance.hide();
export default function createModal(innerComponent) //这个就是在上面真实组件js中拿到 & 使用的函数
let modalComp = Vue.extend(Object.assign(, Popup,
components: innerComponent
));
return new ModalMaker(modalComp);
- 动画组件的vue文件
<template>
<section class="mkt__modal">
<transition name="modal-fade" @after-leave="afterLeave">
<div v-if="showBg" class="mkt__modal-bg"></div>
</transition>
<transition name="modal-popup">
<div v-if="showBg" class="mkt__popup-container" @click="onBGClick">
<!-- 这里被挂载的地方就是上面js中extend里面的components部分 -->
<inner-component :options="options" :close="hide"></inner-component>
</div>
</transition>
</section>
</template>
<style lang="less"></style>
<script>
export default
props: //这里的传值是上面js文件的create函数中这个父子组件实例的propsData属性,通过调用方调用create函数传进来
afterLeave:
type: Function
,
options:
type: Object,
default()
return ;
,
data()
return
showBg: false
;
,
methods:
// 上面js中“显示元素”的this.instance.show();代码就是调用的这个函数
// 因为上面说了,modalComp已经是这个组件成型后的结构了,new的时候取实例可以调用其中的方法
// 这里也说明一件事:vue文件(编译后)实际上就是一段js模块化代码、是一个闭包、是一个构造函数!!!
show()
this.showBg = true;
,
hide()
this.showBg = false;
,
onBGClick()
if (this.options.autoHide)
this.hide();
;
</script>
这样以后,我们使用时就可以直接一个函数中完成:
//引入
import PopupGoodsRange from "真实弹层组件的js文件位置";
PopupGoodsRange.create(
//其他参数
callback: type =>
if (type !== this.form.range)
this.form.range = type;
);
这样“小组件”之间的组合,使得其它组件使用动画也是如此方便:
不怎么正的例子
前两天看到CSDN的活动页面有三个弹窗。虽然他们的功能定位不同,但是两个都是很突兀的展示而另一个是有一个过渡缓动动画,这就很厚此薄彼了吧😉:
当然,CSDN的产品或许有不同的考量,但是按照本文的思路来看,也就寥寥几行代码的事!
技术圈不怎么新鲜事
- 微软由于“邮件过滤管理系统”使用的存储日期的格式问题导致了2022开年大bug
- Vue3 和 vite 双加持 uni-app,期待表现/等待出丑
- 从Chrome86开始对用户隐私表现出极大“兴趣”的Chrome从97版本开始支持
Keyboard MAP API
;Mozilla 则是将此 API 添加到了有害 API 列表中 - Chrome98新增两个Header:
Access-Control-Request-Private-Network
和Access-Control-Allow-Private-Network
,意在保护用户免受针对私有网络上的路由器和其它设备的CSRF攻击。
关于这一点,笔者在前面有介绍过。不同的方式在不同场景中可能会有意想不到的效果:
我对vue中组件通信的思考
Vue生命周期和dom操作时机 ↩︎
以上是关于『可组合的Vue』别样的“小组件”设计的主要内容,如果未能解决你的问题,请参考以下文章