关于软件工程:我来聊聊配置驱动的视图开发

6次阅读

共计 4499 个字符,预计需要花费 12 分钟才能阅读完成。

我在平时上下班开车时,全凭身材记忆与条件反射,根本不必脑子,所以脑子就空进去痴心妄想了,东想想西想想。

某天早上突然想到:最近几年,业界在开发时都考究以「数据驱动」的形式更新视图,回忆过来这几个月的工作内容,发现咱们的视图层开发并不是单纯的数据驱动,而是「配置驱动」。

视图更新

让咱们先来回顾一下以往以及当初,在视图层开发时个别是如何更新视图的吧——

在 React、Vue 等前端库 / 框架风行之前,根本以手动操作 DOM 的形式进行:

<form>
  <div>
    <span> 是否已婚 </span>
    <div>
      <label><input type="radio" name="married" value="true"> 是 </label>
      <label><input type="radio" name="married" value="false"> 否 </label>
    </div>
  </div>
  <div id="childrenCountField" style="display: none;">
    <label> 孩子数量 </label>
    <input type="text" name="childrenCount" value="">
  </div>
</form>

<script>
$('[name="married"]').on('change', function() {const $children = $('#childrenCountField');

  if ($(this).val() === 'true') {$children.show();
  }
  else {$children.hide();
  }
});
</script>

在 Vue 中应用的是数据绑定:

<template>
  <el-form>
    <el-form-item label="是否已婚">
      <el-radio-group v-model="married">
        <el-radio :label="true"> 是 </el-radio>
        <el-radio :label="false"> 否 </el-radio>
      </el-radio-group>
    </el-form-item>
    <el-form-item label="孩子数量" v-show="married">
      <el-input />
    </el-form-item>
  </el-form>
</template>

<script>
export default {data() {
    return {married: false};
  }
}
</script>

通过配置的形式来实现同样的事件:

<view widget="form">
  <field name="married" label="是否已婚" widget="radio" />
  <field name="childrenCount" label="孩子数量" widget="input" invisible="record.married !== true" />
</view>

有没有感觉最初一种很不便,并且可读性很强?

相较于手动操作 DOM,数据绑定绝对更「智能」,这是一种数据驱动的开发方式。可单纯的数据驱动只解决了根本的数据显示问题,并没有任何视图层可扩展性上的撑持,比方同一个表格组件:

  • 在 A 模型下想显示 a、b、c 字段,在 B 模型下想显示 d、e、f 字段;
  • 在页面主体中时想显示列偏好设置、单元格文本密度调节,但在对话框中时不想要这些性能;
  • 在 A 利用中表头的边框是尖角且背景色是浅蓝色,在 B 利用中则是圆角的边框与淡紫的背景色。

在复杂多变的中后盾业务场景中,要想使一个组件可能最大限度地复用,要想用一些组件疾速搭建出一个中后盾利用,就须要一套足够灵便、足够弱小的可扩大体系。

视图配置

一个页面,或者说一个视图,能够进行配置的点次要有:模板、模型、逻辑、主题。

模板

在 web 开发中所应用的「模板」,大多是与 HTML 相符合且面向开发的,如:Vue 的模板、Pug(Jade)、Thymeleaf、FreeMarker、Velocity 等等。

然而,这里的「模板」与 HTML 没有间接关系,是对某个畛域的视图构造、数据结构或逻辑构造的形容,是一种内部 DSL:

  • 形容数据容器的视图模板;
  • 形容搜寻过滤器及操作符的搜寻模板;
  • 形容整体布局的布局模板;
  • 形容纸张打印的打印模板;
  • 形容调研问卷的问卷模板。

这些模板听从雷同的设计准则,应用同一套解析器,解决不同畛域问题。它们别离是一套标签集,只有有新的畛域的问题要解决,就能够新增一套标签集。

模板不仅能让人一眼就看懂它所形容的信息,还能管制最终所呈现出的状态,详见我之前写的《我来聊聊面向模板的前端开发》。

模型

这里所说的「模型」次要是指元数据。什么是「元数据」?简略了解,就是「用来形容数据的数据」。

如果有一张个人信息表,须要填写如下信息:

  • 姓名
  • 出生日期
  • 年龄
  • 性别
  • 是否已婚
  • 孩子数量
  • 月支出
  • 兴趣爱好

试想一下,这些信息别离是什么数据类型?不要想当然地认为姓名就是字符串而不是长文本,年龄就是数字而不是字符串,性别就是布尔型而不是枚举……

为了使在进行数据处理时可能模式化,须要对要解决的数据进行形容,即应用元数据。

要形容的信息次要是数据类型及其要显示的文本标签,如果不是布尔型、数字、字符串等根本类型,最好形容其数据起源,比方枚举;依据须要还能够形容是否必填、是否只读等:

[
  {
    "name": "name",
    "label": "姓名",
    "type": "string",
    "required": true
  },
  {
    "name": "birthday",
    "label": "出生日期",
    "type": "date",
    "required": true
  },
  {
    "name": "age",
    "label": "年龄",
    "type": "integer",
    "required": true
  },
  {
    "name": "gender",
    "label": "性别",
    "type": "enum",
    "options": [],
    "required": true
  },
  {
    "name": "married",
    "label": "是否已婚",
    "type": "boolean",
    "required": true
  },
  {
    "name": "childrenCount",
    "label": "孩子数量",
    "type": "integer",
    "required": true
  },
  {
    "name": "monthlySalary",
    "label": "月支出",
    "type": "currency"
  },
  {
    "name": "hobbies",
    "label": "兴趣爱好",
    "type": "m2m",
    "options": "","chosen": []}
]

元数据对视图的影响,次要是数据相干的,对视图状态没什么影响,如:要显示哪些字段(依据元数据生成视图模板)、字段的校验规定、字段的编辑状态、申请的参数等。

根据上述元数据所生成的视图模板大略长这样儿:

<view widget="form">
  <field name="name" label="姓名" required="true" />
  <field name="birthday" label="出生日期" required="true" />
  <field name="age" label="年龄" required="true" />
  <field name="gender" label="性别" required="true" />
  <field name="married" label="是否已婚" required="true" />
  <field name="childrenCount" label="孩子数量" required="true" />
  <field name="monthlySalary" label="月支出" />
  <field name="hobbies" label="兴趣爱好" />
</view>

在应用元数据时,最好后端能陪着一起玩儿,这么一来就省去了不少接口的设计、评审、联调等工夫,取而代之的是后端定模型。如果只能前端本人玩儿,能够利用 JSON Schema 等工具。

逻辑

如果框架设计得正当,应该可能在不更改组件的外部实现的状况下与内部可配置的逻辑进行组合联动。依据逻辑的轻重与组合联动形式,能够大抵分为动作与表达式这两种。

「动作」是一段残缺逻辑的形象,与函数相当,用来形容且只形容「做什么事」,不形容「长什么样」。一个可复用的动作应该是原子化的。

依据逻辑的定义、执行所在位置,能够分为客户端动作(狭义)与服务端动作:客户端动作(狭义)是定义并且执行在前端;服务端动作是定义并且执行在后端。

客户端动作(狭义)依据具体场景的用处及个性,又可分为以下几种动作:

  • 路由动作
  • CRUD 动作
  • 客户端动作(广义)
  • 组合动作

其中,路由动作的作用是进行页面跳转;CRUD 动作是对数据进行操作;客户端动作(广义)是单纯的一段逻辑,能够简略了解为是一个 JS 函数;组合动作用于将其余类型的动作「打包」解决,就像一个调用了其余函数的函数。

服务端动作能够简略粗犷地了解为是非常规 CRUD 的后端接口。

表达式是一种轻逻辑,次要用于字段的值计算、备选项筛选、状态联动等使用简略逻辑的场景:

<view widget="form">
  ...
  <field name="married" label="是否已婚" required="true" />
  <!--「是否已婚」的值为 `true` 时才显示「孩子数量」,并且必填 -->
  <field name="childrenCount" label="孩子数量" required="record.married === true" invisible="record.married !== true" />
  ...
</view>

主题

置信看到「主题」这两个字,第一反馈是扭转字体、字色、背景色等个性的「皮肤」,然而在本文的语境中,不完全正确。

下面所提到的模板、模型、逻辑等都是较为底层的配置,从内部去影响组件的出现;而主题则是更为下层的配置,从外部或者其自身去影响组件——款式、行为、组件及其所依赖的运行时。

「款式」不难理解,就是扭转字体、字色、背景色等个性的「皮肤」,但「行为」是什么呢?看以下几种需要:

  • 布尔型字段在某个利用中想用 Switch 组件,在其余利用中想用 Checkbox、Radio 或 Select 等组件;
  • 表格在页面主体中时想显示列偏好设置、单元格文本密度调节,但在对话框中时不想要这些性能。

用来解决这类需要的配置,就是「行为」。

至于为什么组件及其所依赖的运行时也是配置,这是因为在这种体系下,次要业务逻辑被底层所接管了,组件内根本只剩属于自身的交互逻辑。所以,无论是用 Vue、React 还是其余的,又或者是几种混用,对理论业务的停顿不会造成影响。

思维总结

在文章题目中应用的是「视图开发」而不是「前端开发」是因为全文的侧重点在视图层,根本没有提到其余层的事件,但不代表仅视图层是能够配置驱动的。

实践上,在一个可能疾速响应业务变动的前端架构中,应该是整体可配置,各层都可被替换,但无奈替换的是设计指标、设计思维与接口协议,这些是灵魂,只有它们在,架构就没变。


本文其余浏览地址:集体网站|微信公众号

正文完
 0