2

精卫 · lowcode 年前进展

 2 years ago
source link: https://zhuanlan.zhihu.com/p/442675795
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

精卫 · lowcode 年前进展

呼呼呼,年终啦,好久没有发作文了,午休时间编一个叭

本来打算发小程序的来着,但是小程序搞完远程调试基本就完工了,实在没什么好编的了

今天还是来谈谈精卫 lowcode

精卫是年底开的新坑,是小程序 lowcode 平台,通过拖拖拽拽就可以生成小程序,或导出组件,它在特定场景下可以节约一些编码成本……

lowcode 是局限场景的局限手段

比如,你还有一天就要交工了,实在是写够了代码了,就可以试试 lowcode

随着 figma/notion 获得亿元的融资,我们渐渐发现,其实不用和 ps/word 一样功能强大,小而美的,精致的产品也可以备受好评

这也奠定了我做精卫的基调——<基础功能简单,但提供精致的扩展机制>

v2-24fa08dbf30249c2f3ed1c2b5997602d_720w.jpg

精卫的技术架构其实很简单的,它是一个完整的前端项目,主要包括前端的 UI编排/逻辑编排/数据编排,后端的编译器,我们一一道来

UI 编排

精卫的 UI 编排,最开始是树的可视化编辑

v2-8456388c62a26327d556854ee4e9a42d_720w.jpg

这块的实现其实和 vscode 的文件树是一样的,交互方面也参考了许多 vscode

我们的 UI 编排最终生成的是一棵组件树,逻辑编排生成的也是一棵语法树,数据编排生成的也是一棵字段树……

也就是说,需要有一个树结构贯穿始终……

所以我特地抽了一个类

export class Tree {
  constructor(tree, name) {
    this.stack = []
    this.tree = Array.isArray(tree) ? tree : [tree]
    this.index = 0
    this.level = 1
    this.deps = []
    this.name = name
  }
  recleNode(index) {
    let walk = node => {
      return {
        type: node.type,
        props: node.props,
        children: node.children.map(walk),
      }
    }
    const res =
      index != null ? walk(this.tree[index]) : this.tree.map(walk)
    return res
  }
  flatNode(node, level) {
    node.key = '.' + this.index++
    node.level = level
    this.addDeps(node.props)
    this.stack.push(node)
    if (node.children.length > 0) {
      level++
      node.children.forEach(item => {
        item.parent = node
        this.flatNode(item, level)
      })
    }
  }
  addDeps(props) {
    for (const key in props) {
      if (key.startsWith('bind')) {
        const has = this.deps.find(s => s.type === props[key])
        if (!has) {
          this.deps.push({
            type: props[key],
            props: {},
            key: '.' + this.index++,
            level: 1,
            children: [],
          })
        }
      }
    }
  }
  concatDeps(nodes) {
    this.tree = this.tree.concat(nodes)
  }
  flatNodes() {
    this.reset()
    this.tree.forEach(item => this.flatNode(item, this.level))
    return this.stack
  }
  reset() {
    this.level = 1
    this.index = 0
    this.stack = []
  }
}

然后就可以愉快的 new 树了

const uiTree = new Tree([])
const logicTree = new Tree([])

对于树组件的增删改查,就不多说了,基本思路就是,就是我们维护一个拍平的栈,用来查引用,然后将引用修改反馈到树上……

如果去年参加过 fre conf,章总已经透露过很多技巧啦,有兴趣阔以找章总索要一下 ppt 啊哈哈

除了树的可视化编辑,UI 编排还有很重要的一点,就是——

拖拽,和,布局

这基本上每个 lowcode 系统必须面对的难题,主要有以下几种策略

就是鼠标拖拽,拖到哪里就记录好坐标,然后最终用定位渲染

市面上比如 H5-Dooring 等使用了这种策略,比较适合 h5 活动页

这种的好处是真的是实实在在的拖拽,坏处就是不适用于 RN 这种不支持定位的平台,响应性不好,结构也很糟糕

2. 容器栅栏

也就是基于行和列的布局方式,先添加容器,再在容器里塞固定大小的组件

百度的 aims,阿里的宜搭,腾讯的微搭都是使用这种布局方式,比较适合 pc 管理平台等场景

优点是有了基本的布局的概念,缺点就是 想要的位置 ≠ 预测的位置 ≠ 实际渲染的位置

使用这种布局策略的系统,都顶不住瞎几把拖拽

flex 布局的缺陷

因为精卫是移动端的 lowcode,不是活动页,也不是后台管理平台

移动端的布局有明显的特点,比如 RN,flutter,swiftUI,jetpack compose 等现代移动端框架,都不约而同选择了纯 flex 的响应性布局方式

可以说在移动端,定位和固定容器都不合适

但是 flex 布局太难拽了,因为它是自动响应的,一定是 最终渲染的位置 ≠ 拖入的位置

有没有能拖的 flex 布局?

答案是有的

grid 布局

flex 布局之所以难拽,是因为它是响应性的,行列都不固定,拖入的位置一定不是最终渲染的位置,那么其实我们只要能够事先定义好响应性布局就好了

grid 布局可以让我们做这个事情,举个例子

这个 grid 布局就是我们事先定义好的

grid-template-areas:
    'header header header'
    'sidebar content content'
    'footer footer footer';

这是一个矩阵,用户只需要定义好这个矩阵,那么我就可以生成上面的布局

然后拖拽就只需要替换具体的 block 即可

比如我们往 header 中拖入一个 View 组件,则只需要修改 View 的 style

<View style={{ gridArea: 'header'}}/>

可以看到,grid 布局最终其实只是比 flex 布局多了一个 二维矩阵预定义 的功能,可这就解决了根本问题

同样的,要想支持 RN,我们只需要实现 grid 的算法即可

无论何时何地,将实际问题转换为结构和算法问题,才是王道

望天,说了好多哇……

逻辑编排 && 数据编排

逻辑编排也是一棵树,只不过它是语法树,目前我做的也是一棵树的编辑

它的编辑对象来自 UI 的依赖收集,它编辑完的结果要传给数据编排进行数据处理,最终再映射到 UI 上

但是此树非彼树,我们最好是用一种简化的横向的流程图的结构去做

这块俺还需要继续研究,我们下一篇文章再贱

望天,又编了这么多,我裂开


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK