全栈项目实践三:领域模型架构建设

49 阅读4分钟

本文内容学习自 哲玄前端 《大前端全栈实践》课程

引言

以后台管理系统为例,很多页面其实都是由菜单(头部菜单,侧边菜单)、搜索栏、数据展示部分(表格)组成。日常的开发中这些相同内容甚至能占到80%。

最普通的开发场景:每次一个新页面,把以前写过的,能用的代码直接cv,然后针对不同的属性来修修改改。每个页面不同的属性就具有不同的代码。

更进阶一点的开发场景:既然每个页面相同块的部分都大差不差,那直接对一个块中的属性进行配置。例如搜索栏的属性就配置一个搜索栏的解析器,表格中的属性就配置一个表格的解析器,不同块的属性就配置一个相应块的解析器。但这样又有一个新的问题:看似封装了一些组件能进行复用,但是当一个新的产品来的时候,又要针对新的产品重新进行类似的封装,即每个产品都需要各自的封装。

由此,我们应该基于数据驱动的方式从数据维度出发,而不是从组件维度看待问题。对于同一份源数据,对数据配置不同的属性来渲染搜索栏,表格等等。而不是划分不同数据来渲染不同的内容。

什么是DSL

DSL全称为Domain-Specific Language (领域特定语言)

DSL 的主要价值在于简化问题的描述和解决方式。设计 DSL 的核心是要体现出意图,而不仅仅是实现功能。通过 DSL,程序员可以将复杂的实现细节隐藏在简洁的语法之下,让使用者专注于表达需求,而非实现细节。

dashboard

以dashboard为例

image.png

  • DSL都只需要描述数据以及数据相关的配置项,而不用去在意这些配置应该如何的实现。

image.png

  • 一份DSL模板-> 不同模型(model) -> 不同项目(project)配合解析引擎落地为具体的一个产品
    • 不同项目属于同一个模型(继承实现)
    • 模型配置描述80%的共同点,项目配置20%的定制化需求
      • 图中绿色部分均为可拓展部分(可定制化部分)

上图中的配置项可以通过以下的描述来进行实现

docs = {
  mode: 'dashboard', // 模板类型描述, 不同模板类型对应不一样的数据结构

  name: '', // 模板名称
  desc: '', // 模板描述
  icon: '', // icon
  homePage: '', // 首页(项目配置)

  menu: [
    // menuItem[]
    {
      key: '', // 菜单唯一描述,
      name: '', // 菜单名称
      menuType: '', // 菜单类型, 枚举值: 'group' | 'module'

      // 1. 当 menuType == group 时, 可填
      subMenu: [], // menuItem[],

      // 2. 当 menuType == module 时
      moduleType: '', // 模块类型, 枚举值: 'schema' | 'iframe' | 'custom' | 'sider'

      // 2.1 当 moduleType == schema 时
      schemaConfig: {
        api: '', // 数据源 API (遵循 RESTFUL 规范)
        schema: {
          // 模块数据结构
          type: 'object',
          properties: {
            prop: {
              ...schema, // 标准 schema 结构
              type: '', // 字段类型
              label: '', // 字段中文名
              // 字段在 table 中的相关配置
              tableOption: {},
              // 字段在 search-bar 中的相关配置
              searchOption: {},
            },
          },
        },
        // table 相关配置
        tableConfig: {
          headerButtons: [
            {
              ...elButtonConfig, // 标准 el-button 配置
              label: '', // 按钮名称
              eventKey: '', // 按钮事件名称
              eventOption: {}, // 按钮具体配置
            },
          ],
          rowButtons: [
            {
              ...elButtonConfig, // 标准 el-button 配置
              label: '', // 按钮名称
              eventKey: '', // 按钮事件名称
              eventOption: {}, // 按钮具体配置
            },
          ],
        },
        // search-bar 相关配置
        searchConfig: {},
        // 模块组件
        components: {},
      },

      // 2.2 当 moduleType == iframe 时
      iframeConfig: {
        path: '', // iframe 路径
      },

      // 2.3 当 moduleType == custom 时
      customConfig: {
        path: '', // 自定义路由路径
      },

      // 2.4 当 moduleType == sider 时
      siderConfig: {
        menu: [], // menuItem[], 这里 menuItem 的 moduleType 只能是 'schema' | 'iframe' | 'custom'
      },
    },
  ],
}

  • 通过解析 menu,即可渲染出对应的菜单,而每个 menuItem 的数据对应其菜单具体要渲染的页面内容。

以 menuItem 里面的 moduleType == schema 为例(即schema-view)

  • schemConfig.schema.properties 里面包括我们要渲染的每个数据。
  • 通过 tableOption 来控制这个数据在表格中的表现,通过 schemaConfig.tableConfig 来控制整个表格在页面的表现。
  • 通过 searchOption 来控制这个数据在表格中的表现,通过 schemaConfig.searchConfig 来控制整个搜索栏在页面的表现。

通过这样的方式完成了用一份源数据在不同的块进行渲染的目标。

  • 并不局限于给出的 table 和 search,还可以进行额外的拓展(form, dialog, drawer等等)