手搓低代码框架(2)- 项目结构、高可扩展的方式

225 阅读3分钟

注:文章所写内容在星云中得到了实践

项目结构

通过 monorepo 架构来组织,Monorepo 架构是一种将多个项目(通常是相互关联的项目)存放在一个代码库(Repository)中的方法,我们可以借助 monorepo 实现

  • 共享代码和依赖:所有项目在同一个仓库中,因此可以轻松共享和复用代码,例如公共的工具库、组件等,避免了重复开发。
  • 一致性和版本控制:所有项目的代码版本一致,可以统一管理依赖的版本并快速查看整个项目的版本变动。 我们考虑使用 pnpm 的 workspace 来实现,当然也可以使用 Lerna 等其他
关注点分离
  • 将逻辑层和视图层分离
    考虑到视图层期望支持不同的UI框架(例如react/vue),以及视图层有较强的定制性(每个公司都希望自己的产品不要一眼就看出是使用了某某框架)
    故而,我们将逻辑层单独抽成npm 包,而可复用的UI层则抽成npm包里相对独立的小组件,大的面板则交由客户自己开发。
  • 通过不同的业务类型分离
    我们将,表单/大屏/门户/流程等等不同的业务的核心逻辑,抽取到不同的npm包中,这样用户用到哪个模块,只需要安装对应的包即可
核心对象

核心的对象,我们放在了 core 包中,我们抽取了 project 、document、node、props, children 等各个业务都需要的通用对象,如果不同业务需要对其扩展,我们可以通过继承这个类并注册新的类来实现

如何实现较高的扩展性

常见的扩展方式:
1.插件的扩展思想

function middleware1() {}
function middleware2() {}
function middleware3() {}
const plugins = [middleware1, middleware2, middleware3]
run() // (run函数会依次执行所以注册的插件), 通过添加自定义的插件,来实现对默认逻辑的扩展

2.面向对象的继承思想

class Animal {
  eat() {}
}

class Person extend Animal {
    work() {}
}
// 通过继承来实现对 Animal 的扩展

这两种基础的方式,一类是函数式的思想,一类是面向对象的思想
我们采取了两者的结合,在面向对象的基础上,增加许多hook,hook本身则是插件的容器,可以插入自定义的逻辑。

// Project 本身是一个对象,提供基础的能力,可以继承它来添加自定义的方法或属性
export class Project<T extends Services = Services> {
  // 这个 hooks 是一个插件容器,我们可以通过加入不同的插件来对某些流程插入自定义的工作
  hooks = projectHooks as ProjectHooks<T>
  
  constructor() {
    makeObservable(this)
  }

  @action
  createDocument(schema?: DocumentSchema, index = -1): T['Document'] {
    const doc = this.documentFactory(this, newSchema)
    if (index === -1) {
      this.documents.push(doc)
    } else {
      this.documents.splice(index, 0, doc)
    }

    // 执行 onDocumentsChange 这个hooks里所有注册的插件
    // 意味着可以在 创建doc 之后加入自定义的逻辑
    this.hooks.onDocumentsChange.call({ documents: this.documents })

    return doc
  }
}

状态驱动UI

我们使用mobx来作为状态库,mobx是围绕响应式编程创建的库,更加关注状态的自动同步和响应式特性。mobx和vue很像,而且是框架无关的,这意味着在任意框架中都可以接入。
MobX 会自动追踪 observable 数据和它们的依赖关系,确保当数据变化时,UI 自动更新,而无需手动设置监听或通知。


export class Project<T extends Services = Services> {
  // 通过 mobx 的语法 监听documents的数组变化
  @obx.shallow documents: T['Document'][] = []

  constructor(
  ) {
    makeObservable(this)
  }

  @action
  removeDocument(index: number) {
    // 直接移除指定的document
    this.documents.splice(index, 1)
    // a after hook
    this.hooks.onDocumentsChange.call({ documents: this.documents })
  }

}

import React from "react"; 
import { observer } from "mobx-react"; 
import { project } from "mobx-react";

// 通过包裹 observer 监听所有组件内部用到的依赖
const Counter = observer(() => (
  <div>
    // 此处可以实时渲染 doc 的数量
    {project.documents.length}
  </div>
))