前端可视化框架是怎样炼成的?
从一个“栗子”说起随着移动互联网的迅猛发展和 5G 技术的普及,前端页面需求量爆炸增长,用户交互也变得越来越复杂,页面从零开发的成本也水涨船高。如何快速、高效地构建前端组件乃至页面是解放前端生产力的重要标志,掌握抽象组件和页面模型,理解前端可视化搭建思路,摆脱固有的开发模式,提高前端开发效率,是每位前端应该了解的。
在我们前端开发过程中可能经常这样会遇到这样的场景:
(某天,产品经理找到前端。)
产品经理:简单开发一个欢迎页面,就展示下“欢迎访问”。
(这没什么难度啊,代码很好写了,于是前端不假思索就把代码秀出来。)
前端:
```
<template>
<div>
欢迎访问
</div>
</template>
```
(一天之后,产品经理觉得这个欢迎词不够具体,还想加个主语,于是……)
产品经理:那个欢迎词简单改一下,改成“欢迎访问我们的网站”。
前端:
```
<template>
<div>
欢迎访问我们的网站
</div>
</template>
```
(经过代码提交、代码审核、代码合并、部署到测试环境、测试验证、灰度发布、产品验证、
发布上线等一系列流程,欢迎词终于更新了……
又一天后,产品经理觉得欢迎词没有展现出我们的优势,于是加了个形容词来描述我们的网站。)
产品经理:那个欢迎词还是不行,改成“欢迎访问我们帅气的网站”。
(迫于产品经理手中40米长大刀的威慑,尽管心中满是问号,前端还是修改了代码。)
前端:
```
<template>
<div>
欢迎访问我们帅气的网站
</div>
</template>
```
(又又一天后,产品经理想到一个“狂拽酷炫”的形容词,于是……)
产品经理:欢迎词再改下,改成“欢迎访问我们狂拽酷炫的网站”。
(此时,前端坐不住了,想到了通过让产品经理自己维护JSON文件,在页面中获取JSON中title字段进行渲染显示)
前端:
```
<template>
<div>
{{title}}
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
title: ''
}
},
created() {
this.queryTitle()
},
methods: {
queryTitle() {
axios.get('JSON文件所在路径').then(res=>{
if(res && res.status === 200) {
this.title = res.data.title
}
})
}
}
}
</script>
```
(又又又一天后,产品经理有了另一个想法,这次不是改欢迎词了。)
产品经理:我想再加一个选择框,用来收集用户信息。
(此时,一阵风吹过,前端在风中凌乱了……)
当然,既然能想到通过 JSON 去配置,那意味着整个组件、页面都能配置,只需给到产品经理一个可视化的界面去配置,即可生成 TA 想要的页面,大大节省了沟通成本、提升了开发效率。这时,可视化框架应运而生。
可视化框架的构成和分工 可视化框架是怎样的?首先我们先了解一下可视化框架的构成和分工。开发作为可视化框架的维护者,可以提供一个可视化的配置平台;目标受众,也就是可视化平台的使用者,如上文例子中的产品经理;使用者通过可视化配置平台对页面进行配置,平台会产出一份配置文件用来描述页面并上传到存储空间;配置文件通过渲染引擎解析最终生成页面。在这个构成中,作为开发框架的维护者,我们只需要关注可视化配置平台和渲染引擎,当然可视化配置平台也只是将使用者的输入转化为渲染引擎能解析的配置文件并上传,是锦上添花的一环,如果还想应用在更复杂的场景,还可以加上权限管理,比如 A 只能编辑 A 负责的页面,B 只能编辑 B 复制的页面,还可以实现一个工作流的流程编排,比如审核……
可视化框架的核心在于渲染引擎,我们如何得出这个渲染引擎以及渲染引擎是如何运作并渲染最终的页面,是我们所关心的。如何得出渲染引擎就不得不先说说页面泛化模型。
为什么会有页面泛化模型?因为我们不同的页面其实是通过渲染引擎解析不同的配置文件渲染出来的,因此渲染引擎需要泛化的能力。比如页面 A 是表单填写,那么就会要求渲染引擎能够解析表单的配置文件;比如页面 B 包含卡片式列表,就会要求渲染引擎能够解析列表的配置文件。页面泛化模型的建立,主要根据开发者自身的经验和方法建立满足自身业务需求的模型即可。像上文案例中,如果产品经理一直只要求修改欢迎词,那案例中的那段 title 的渲染引擎就足够满足需求了。接下来,以华为云官网页面为例,抛砖引玉讲讲页面泛化模型如何建立。
从上面截图中我们可以看出这其实是一个从上到下的结构,可以理解为页面是由一个个楼层构成的,楼层可以理解为body下的一个div块,或者是一个功能块。比如图中从上到下是页头、banner、引导跳转楼层、推荐文章、技术领域楼层。当然我们经常遇到的页面也不全都是上下结构,还有左右结构。
其实,像这样左右结构的页面,您可以理解外层还有一个更大的容器组件包裹,整体形成一个楼层。容器组件只负责样式,比如上图中容器组件负责左右布局,如果您对布局还没有概念,您可按下F12键查看上图布局的代码,您会看到其实左边两个模块是被classname
为edu-index-version-left
的div
包裹,右边两个模块是被classname
为edu-index-version-right
的div
包裹,它们依旧是从上到下的楼层。因此,页面 = 楼层 + 容器组件
。
再来看看楼层解构,以上图为例,图中主体部分是一个tab功能组件,通过查看页面源码,我们发现两处红色框圈出来的文本并没有设计在tab
功能组件里,而是单独成为文本控件,这是因为这部分是属于定制化的功能,并不是每个tab
功能组件都有的,就抽离出来了遵循单一职责原则,蓝色框是一个容器组件。再来看看tab
功能组件中的卡片,卡片也是由一个一个的控件组成。因此,楼层=容器组件 +控件
。
可视化框架中的控件和常见的 UI 框架中的组件一样,都具有完善且单一的职责,比如表格组件,无论再怎么加功能都是在表格内部,表格里面的功能是无法拆分出去的。但可视化框架中的控件和 UI 框架中的组件还是有区别的,主要不同点在于控件需要用来被编辑,因此它具有统一的props
,所有的控件要遵循一样的props
,如视图配置和数据源,在可视化中我们不知道页面会用到哪些组件,因此需要统一去做循环渲染,不感知控件的具体属性。
可视化框架中的容器组件本质上也是组件,主要负责布局,分为基础容器组件和功能容器组件。基础容器组件中左右布局一般通过栅格或者 Flex 实现,上下布局通过正常的文本流或者定位以及控件自身间距等实现。功能容器组件如 tab 容器、轮播组件等。
当考虑用户交互时,事件在可视化模型中就不得不考虑。可视化模型中的事件需要关注交互的发起者、交互的作用者以及交互的影响方式,而不需要关注交互的种类和交互的具体内容。比如点击了某个按钮,步进器的最大最小值从 1、2 变成 3、4,其实是改变了input
控件的min
和max
属性;比如表格筛选加了一定条件之后,显示数据变少了,是因为触发了数据事件影响了表格的数据源;比如一个开关组件,点击之后控制控件的显示和隐藏……
最终,我们确定的可视化模型就是上图中总结的点。可视化页面由控件 + 容器组件 + 事件
组成,控件的粒度最小,是功能的最小单位;容器组件负责布局,是样式的集合;事件响应用户交互传递控件间依赖关系。那控件、容器组件、事件是怎么结合的呢?就不得不谈谈渲染引擎了。
渲染引擎本质上也是组件,主要功能是渲染当层组件、处理当层组件交互关系、对当层组件状态进行管理。它不关注子层组件,子层组件由子层容器的渲染引擎渲染,因为每一层组件的配置和数据源不一样,因此渲染结果也不相同。视图部分示例代码(基于Vue.js
)如下:
<template>
<div :class="clsPrefix">
<component
:is="component._type"
v-for="component in viewConfig.components"
v-show="showState[component._id]"
:ref="component._id"
:key="component._id"
:viewConfig="component"
:dataSource="dataSource[component._id]"
@valueChange="valueChangeHandler($event, component._id)"
/>
</div>
</template>
最外层是由div
包裹,使用自定义动态组件component
的方式定义渲染引擎,带下划线的属性意味着是自定义组件的内置属性,is
决定组件渲染的类型,如button
,做到可以不感知当层组件具体内容渲染当层组件;v-for
循环当前组件的属性;v-show
控制组件是否显示,通过showState
进行状态管理;ref
建立索引,可以用来做一些高级功能,比如父层容器调用子层容器的方法;viewConfig
和dataSource
就是上文中提到的控件中所必需的;@valueChange
是组件提交的统一事件。
父层容器和子层容器本质都是渲染引擎,只是样式不同。比如说一个左右布局的容器组件,相当于在视图中v-for
循环的地方绑定一个class
如栅格布局或者flex
布局的样式,如果是栅格布局的话,用户就要先定义type
是栅格组件,然后配置viewConfig
中grid
属性之类的,如果是flex
布局,定义的type
就是flex
组件,对齐方式如指定为space-between
之类的。上文中示例代码就是一个正常的从上到下的布局。
首先外层传递两个关键的参数--viewConfig
和dataSource
,当层容器就会进行配置解析和配置分发。配置解析主要包含属性映射和生成事件,属性映射比如控件本身需要title属性,而配置文件中可能是叫label
属性,这时我们要将label
转换成title
,只不过转换逻辑不包含在渲染引擎中,只是调用外部封装好的方法;生成事件则是根据配置生成预设事件并挂载。在配置分发之前,会先将解析好的配置进行初始化父层状态并存放在父层,主要考虑到viewConfi
g和dataSource
不是同步赋值,需要等待都就绪了才进行分发。
在介绍控件的时候,提到每个控件都要提交,因为每个控件都不能成为其他控件的一个origin
。比如Tip
组件,虽然本身可能没有交互,但可能成为别的控件的依赖项,子层可能需要获取内容,子层容器的提交会被收集在父层容器的valueWatch
中,在子层容器提交以后,如果状态变化的话,那父层容器就会根据functionList
去循环执行的事件;如果是展示类事件,就会更新父层的showState
控制显示或隐藏,如果是数据类事件,则会改变viewConfig
和dataSource
,对目标进行一个重新赋值。总得来说渲染引擎流程就包含以上四个步骤:配置解析、配置分发、收集提交、发起事件。
为了减少页面开发代码量,提升代码复用度,我们期望页面能进行可视化编辑;为了得出一个通过的可视化框架,我们对页面进行了可视化建模分析,得出几乎所有的页面都可以由控件和容器组件构成,通过泛化的事件处理用户交互和组件间的级联关系;根据建模的结果建立了渲染引擎,支撑起整个流程,最终实现可视化。
本文整理自华为云社区内容共创活动第二期之【线上直播】2.0倍起步?高效完成前端页面。
查看活动详情:https://bbs.huaweicloud.com/forum/thread-111494-1-1.html