低代码: 开发难点分析,核心技术架构设计

开发难点分析


1 )怎样实现组件

  • 核心问题:编辑器 和 页面其实整个就是一系列元素构成的
  • 这些元素的自然应该抽象成组件,这些组件的属性应该怎样设计
  • 在不同的项目中怎样做到统一的使用

2 )跨项目使用

  • 在不同的项目中怎样做到统一的使用

3 )组件的可扩展性

  • 组件的可扩展性,虽然在需求中我们只要求了三种组件
  • 但是最初的设计是否能够具有良好的可扩展性

4 )编辑器的整体状态

  • 说完了组件作为个体的问题,现在把它融合到编辑器页面来讨论
  • 编辑器做的功能其实就是对一系列组件增删改的操作
  • 所以怎样设计编辑器的整体状态

5 )增加和删除

  • 说完了组件作为个体的问题,现在把它融合到编辑器页面来讨论
  • 编辑器做的功能其实就是对一系列组件增删改的操作
  • 所以怎样设计编辑器的整体状态

6 )属性渲染成表单

  • 组件有多种,它的属性也有多种
  • 1 怎样将这些属性渲染成不同的表单组件(也有可能不仅仅是表单组件)
  • 2 在表单组件中,属性作出修改以后,怎样实时的将值反射到组件中去

7 )实时的反馈

  • 在编辑器中通过属性修改,如何同步到页面

8 )插件化

  • 编辑器有很多的交互:拖动移动位置,拖动改变大小,快捷键,右键菜单,缩放,重做/回滚等等功能
  • 它们都是在核心问题之外的交互,那么很自然,我们是否能将这些功能进行解耦?

核心技术架构


1 )项目拆分

基于上述分析,我们可以把项目拆分成不同的项目

1.1 前端-B端系统仓库

  • 可以采用简单的SPA模型,基于 Vue 或 React
  • 这里可以集成下面的编辑器项目,但是会成为一个巨石仓库
  • 建议采用微前端的模式,集成下面的编辑器项目

1.2 前端-编辑器

  • 用于实现复杂的编辑器项目
  • 可以再上述B端中耦合,但推荐使用微前端拆分出来单独成一个项目

1.3 前端-组件库项目

  • 复用于 B端,C端(包含移动端)

1.4 后端项目

  • 基于 Restful API 提供接口服务
  • 基于SSR 提供H5页面

2 )项目间的关系

3 ) 技术方案设计注意事项

  • 技术方案设计,为的就是寻找一个方向,论证:可行性、扩展性、复杂度高低。
  • 不要一直沉浸在细节里,要站在上帝的视角来看问题

4 ) 核心问题分析与设计

4.1 业务组件库实现方案

设计原则

  • 业务组件库大多数都是展示型组件
  • 其实就是把对应的 template 加上属性(大部分是 css 属性)展示出来
  • 会有少量行为,比如点击跳转等
  • 而且这些组件会在多个不同的端进行展示,所以业务组件库就是从简的原则
  • 必须避免和编辑器编辑流程的耦合

组件命名

  • 使用一个字母(X)加组件的名称:比如 XText 或 x-text
  • 这个 X 可以是你的域名,公司名,部门名等有意义的名称

属性设计

  • 属性其实可以分为两大类
  • 伪代码
    // 方案一,将 css 作为一个统一的对象传入
    <XTextcss={{color: '#fff' ...}}text="nihao"
    />
    // 内部实现比较简单
    <p style={props.css}></p>// 方案二,将 所有属性全部平铺传入
    <XText:text="nihao":color="#fff"...
    />
    // 内部实现会复杂一点
    const styles = stylePick(props)
    <p style={styles}></p>
    
  • 方案一内部实现简单,但是保存的时候要多一层结构
    • 并且更新数据的时候要知道是样式还是其他属性
  • 方案二 内部实现稍微复杂一点,但是保存简单,更新数据不需要在做辨别
    • 这些组件目前有一些共有的属性,称之为公共属性
    • 提到公共属性我们就要注意代码重用的问题
  • 点击跳转伪代码
    // 比如 在 Xtext 和 XImage 中都点击跳转的功能,属于公共属性的行为
    // 抽象出一些通用的函数,在组件中完成通用的功能
    import useClick from 'useClick'useClick(props)
    

4.2 编辑器细节问题

4.2.1页面的组成

  • 可能存在背景的设定:由图片或者纯色组成
  • 关于页面的元素
    • 由各种不同的元素(组件)组成。在这里面有属性的配置
      • 一部分属性界定它的位置(position)
      • 一部分属性界定它的展示(looks),比如宽高,内容等

4.2.2 由此,我们可以设计相关JSON数据模型

{"page": {"title": "作品1""props":{"backgroundImage":"","backgroundPostion": """backgroundMusic":"2.mp3", // 可能有的网页背景音乐"backgroundMusicAutoStart": "true" // 背景音乐是否可以自动播放//...},}// 该采用对象还是数组?"components":[{//指代对应的组件类型-可以和公共组件库的组件命名对应"name": "text",//要有特殊的ID,因为每个组件都是独特的,需要使用ID筛选组件"id":"1","props": {//位置属性"left": "10px", "top": "10px",//展示属性"text": "hello world", "fontSize": "16px"},},{"name": "image","id": "2","props": {"left": "5px","top": "20px","src": "cdn.url", "width": "100px", "height": "100px"},},{"name":"date", // 日期组件"props": {"left": "5px", "top": "20px","date": "now", "width": "100px", "height": "100px", "style":1}}]
}

4.2.3 数据的流转

  • 向画布添加组件或者删除组件(向 components 数组添加或者删除特定的组件)
  • 更新组件的某个属性 (找到对应的 component,然后更新它的 props)
  • 渲染画布或者作品(循环保存的作品信息,使用每个组件特定的属性进行渲染)

4.2.4 编辑器的设计

  • 在实现工程中,我们完成了表单和数据的对应其实完成了一个高层次的抽象,用数据描述界面

  • 对比阿里的项目 form-render 现在更名为 x-render, 或访问 xrender.fun 这个开源工具其实和我们的思路是一样的

  • 编辑器页面主要有三个部分,为左中右结构,左侧为组件模版库,中间为画布,右侧是设置面板

    • 左侧是预设各种组件模版并进行添加
    • 中间是使用交互的手段更新元素的值
    • 右侧是使用表单的手段更新元素的值
  • 注意:和后端相关的暂不讨论 - 预览 发布 等

整体状态设计

  • 不难看出我们的编辑器其实就是围绕着中间画布的元素来进行一系列操作
  • 那么自然而然着是一系列的元素组成的
  • 我们应把它抽象成一系列拥有特定数据结构的数组
  • 伪代码
    export interface GlobalDataProps {// 供中间编辑器渲染的数组components: ComponentData[];// 当前编辑的是哪个元素,uuidcurrentElement: string;// 当然最后保存的时候还有有一些项目信息,这里并没有写出,等做到的时候再补充
    }
    interface ComponentData {// 这个元素的 属性,属性请详见下面props: { [key: string]: any };// id,uuid v4 生成id: string;// 业务组件库名称 x-text,x-image 等等 name: string;
    }
    

场景设计

  • 根据上面的数据结构我们可以针对不同的需求进行技术方案设计
    • 将元素渲染到画布
      • 使用 store 中 compoents 当中的数据,循环渲染输出组件即可
        compoents.map(component => <component.name {...props} />
    • 渲染左侧预设组件模版
      • 原理和上面一样的,只不过数据是预设好的,这个可以写死在本地,也可以从服务器端取得。
      • 他们和中间元素不一样的是,这些组件都有一个点击事件,我们可以添加一层 wrapper 来解决这个问题。
      • 这样也可以和内部的 components 做到隔离,互不影响
        compoents.map(component => <Wrapper><component.name {...props} /></Wrapper>
    • 添加和删除组件
      • 非常简单的逻辑,向 store 中添加和删除组件即可。
        // 添加
        components.push({type: '', props: {} })
        // 删除
        components = components.filter((component) => component.id !== id)
        

编辑组件设计

  • 将属性映射到表单

    • 点击画布中的某个组件需要将该元素的属性以不同表单的形式展示到右侧
    • 几个典型场景的实现,大家应该发现,其实没那么复杂
    • 就是对全局状态中的 components 字段进行修改而已
    • 现在我用一张更大的图,来描述应用的整个流程
    • 通常方案, 将这些表单组件写死到页面中去 (不推荐)
      {text: '123'color: '#fff'
      }<input value={text}/>
      <color-picker value={color}/>
      ...
      
      • 这样写非常简单,但是后期遇到的问题也会非常多,比如代码会非常冗余
      • 对不同类型的业务组件都要做一大堆判断,可扩展性很差
      • 对新的业务组件都要加一堆代码等等
    • 抽象遍历方案
      • 看到界面展示,应该想到另外一个纬度,界面UI 其实就是数据的抽象
      • 所以我们自然想到的就是使用特定的数据结构将它渲染成界面
        const textComponentProps = {text: 'hello',color: '#fff'
        }interface PropsToForm {component: String;
        }
        const propsMap = {text: {component: 'input'},color: {component: 'color-picker'}
        }// 这里我们还是循环所有属性,在每个属性中渲染对应处理这个属性的组件
        map(textComponentProps, (key, value) => {<propsMap[key].component value={value}/>
        })
        
        • 当遇到没有类似的 Form 组件的时候,我们可以进行二次开发
        • 只要这个组件有 value 的对应属性
        • 这在一定程度上还满足了 可扩展性这个命题,组件的属性可以扩展
        • 对于 color 这个属性,我们自己开发一个取色器或者二次封装一个取色器组件
        • 只要传入 value 属性即可
  • 更新表单将数据更新到属性

    • 我们的数据流始终保持自上而下的顺序
    • 也就是说表单更新最终要反射回到总体的 store 当中去
    • 这个时候我们在对应的组件当中发射出一个事件,change,当 change 发生的时候
    • 我们能够知道是哪个元素的哪个属性,以及新的值是什么,我们就用这些信息更新这个值
    • 这样 store 完成更新,元素的 props 发生更新,那么整个数据流动就完成了
      map(textComponentProps, (key, value) => {const handleChange = (propKey, newValue, id) => {const updatedComponent = store.components.find(component.id === id)updatedComponent.props[propKey] = newValue}<propsMap[key] value={value} @change={handleChange}>
      }
      
  • 更新表单将数据更新到属性

    • 我们的数据流始终保持自上而下的顺序

    • 也就是说表单更新最终要反射回到总体的 store 当中去

    • 这个时候我们在对应的组件当中发射出一个事件,change

    • 当 change 发生的时候,我们能够知道是哪个元素的哪个属性

    • 以及新的值是什么,我们就用这些信息更新这个值

    • 这样 store 完成更新,元素的 props 发生更新,那么整个数据流动就完成了

      map(textComponentProps, (key, value) => {const handleChange = (propKey, newValue, id) => {const updatedComponent = store.components.find(component.id === id)updatedComponent.props[propKey] = newValue}<propsMap[key] value={value} @change={handleChange}>
      }
      
    • 除了表单的更新,还要说一下画布中的交互更新

    • 其实画布中的更新也是采用发射事件的方式对store 的某些值进行更新

    • 比如说拖动改变位置,最终拖动的过程中也是触发对应的change 事件去用相同的逻辑对值进行更新

    • 这里也要注意,我们需要在业务组件外层,添加一个Wrapper

    • 各种事件都是放在这个 Wrapper 上面的,比如支持拖动,改变位置后发送 change 事件

    • 对于复杂组件也是如此,不管你内部的逻辑有多复杂,添加上传图片,删除,编辑

    • 最终发送出来的事件里面的值,就是对这个 pictures 的值的变换

    • 比如多加了一张照片,那就是数组中的值变成了三项

  • 业务组件可扩展性

    • 属性映射到表单:更复杂的业务组件也只不过是对应各种新的属性而已
    • 可以用现有的 form 对应组件或者是自研或者二次包装的组件对其进行处理
      // 假如有复杂组件 - 比如说是幻灯片
      {pictures: ['1.jpg', '2.jpg']
      }{pictures: {component: 'pics-processer'}
      }
      // 我们自己开发一个 图片处理的 组件即可,传入 value ,这个数组,他应该会渲染出一系列的图片显示
      
    • 表单修改后更新
    • 不管你内部有的逻辑有多复杂,添加上传图片,删除,编辑,最终发送出来的事件里面的值
    • 就是对这个 pictures 的值的变换,比如多加了一张照片,那就是数组中的值变成了三项
  • 画布插件化

    • 比如快捷健,他只写成普通的可重用的函数即可,提供回调即可
    • 在回调中,我们可以对全局 store 进行一系列的改写
    • 而快捷键这个功能和编辑器是没有任何关系的
      // 伪代码
      useHotKey()
      useContextMenu()
      ...                                                                                                                                                       
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/392414.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】线程互斥

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

C# Unity 面向对象补全计划 七大原则 之 依赖倒置原则 (DIP)难度:☆☆ 总结:多抽象,多接口,少耦合

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 本系列作为七大原则和设计模式的进阶知识&#xff0c;看不懂没关系 请看专栏&#xff1a;http://t.csdnimg.cn/mIitr&#xff0c;查漏补缺 1.依赖倒置原则 (DIP) 这…

「队列」实现FIFO队列(先进先出队列|queue)的功能 / 手撕数据结构(C++)

概述 队列&#xff0c;是一种基本的数据结构&#xff0c;也是一种数据适配器。它在底层上以链表方法实现。 队列的显著特点是他的添加元素与删除元素操作&#xff1a;先加入的元素总是被先弹出。 一个队列应该应该是这样的&#xff1a; --------------QUEUE-------------——…

骨传导耳机哪个牌子好?五款业界高性能机型推荐,让你选购不迷茫!

骨传导耳机哪个牌子好&#xff1f;哪款耳机值得入手&#xff1f;作为一名资深的数码设备测评师&#xff0c;我极力推荐大家尝试下骨传导耳机&#xff0c;它无需直接堵塞耳道&#xff0c;既能起到保护听力的作用&#xff0c;又能在使用中保持对外界的环境感知。然而&#xff0c;…

OD C卷 - 园区参观路径

园区参观路径&#xff08;100&#xff09; 有一个矩形园区&#xff0c;从左上角走到右下角&#xff0c;只能向右、向下走&#xff1b;共有多少条不同的参观路径&#xff1b; 输入描述&#xff1a; 第一行输入长度、宽度 后续每一行表示 对应位置是否可以参观&#xff0c;0可…

poetry配置镜像

1.简介 poetry 是一个包管理和打包的工具。 在 Python 中&#xff0c;对于初学者来说&#xff0c;打包系统和依赖管理是非常复杂和难懂的。即使对于经验丰富的开发者&#xff0c;一个项目总是要同时创建多个文件&#xff1a; setup.py ,requirements.txt,setup.cfg , MANIFES…

【数据结构与算法】十大经典排序算法深度解析:冒泡排序、选择排序、插入排序、归并排序、快速排序、希尔排序、堆排序、计数排序、桶排序、基数排序

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 引言 一、排序算法概述 排序算法简介 排序算法的分类 性能指标 二、十大排序算法…

Unity Rigidbody 踩坑记录

1&#xff1a;两个带有刚体的物体碰撞会一直不停的弹 把被动受力的刚提的 Freeze Position 的勾选 去掉&#xff08;碰到过一次&#xff0c;有一种受力无法释放又返回给目标的 所以一直弹跳的感觉&#xff09; 2&#xff1a;子物体 和父物体 都有刚体的情况下 子物体 Freeze R…

zdpy+vue3+onlyoffice文档系统实战上课笔记 20240805

上次 上次计划 1、最近文档表格完善 2、实现登录功能 3、新建文件&#xff0c;复制文件&#xff0c;删除文件 4、其他 目前任务&#xff1a;最近文档表格完善 1、在名称前面&#xff0c;渲染这个文档的图标 2、大小的基本的单位是kb&#xff0c;超过1024kb则换成mb&#xff0…

Java | Leetcode Java题解之第318题最大单词长度乘积

题目&#xff1a; 题解&#xff1a; class Solution {public int maxProduct(String[] words) {Map<Integer, Integer> map new HashMap<Integer, Integer>();int length words.length;for (int i 0; i < length; i) {int mask 0;String word words[i];in…

Mysql中事务的读一致性问题,以及如何用MVCC解决

事务四大特性的实现&#xff1a; 原子性事务具有回滚的能力&#xff0c;InnoDB引擎使用undo log日志表来进行回滚操作。 持久性InnoDB引擎使用redo log日志表来保证数据的持久性。 事务的隔离性产生的问题&#xff1a; 脏读&#xff1a;一个事务读取到了另一个事务未提交的数…

Linux系统驱动(五)

文章目录 一、实现机制二、字符设备驱动分布实现流程三、添加自己的系统调用函数1. 找到系统调用文件2. 找到 一、实现机制 应用层 vfs层 驱动层 字符设备按照字节流顺序访问&#xff0c;但是实际它提供了无序访问的功能 vi -t sys_open 内核中通过inode号可以唯一的找到一…

请转告HPC计算AI计算单位,选对存储事半功倍

U.2 NVMe全闪混合统一存储GS 5000U是Infortrend产品中一款高性能机型。得益于搭载强劲的第五代IntelXeon处理器&#xff0c;以及支持PCIe 5.0、NVMe-oF、100GbE等多种特点&#xff0c;GS 5000U单台块级性能可达50 GB/s的读、20 GB/s的写&#xff0c;以及1300K的IOPS&#xff1b…

Xshell安装图文

1.下载 通过百度网盘分享的文件&#xff1a;Xshell安装图文 链接&#xff1a;https://pan.baidu.com/s/1k4ShbhUVQmdxpM9H8UYOSQ 提取码&#xff1a;kdxz --来自百度网盘超级会员V3的分享 2.安装 3.连接与使用 见下载

论文辅导 | 基于二次分解和BO-BiLSTM组合模型采煤工作面瓦斯涌出量预测方法研究

辅导文章 模型描述 结合变分模态分解&#xff08;VMD&#xff09;、自适应噪声完备经验模态分解&#xff08;CEEMDAN&#xff09;、贝叶斯优化算法&#xff08;BO&#xff09;和双向长短期记忆神经网络&#xff08;BiLSTM&#xff09;这4种时间序列处理方法&#xff0c;建立了…

AllReduce通信库;Reduce+LayerNorm+Broadcast 算子;LayerNorm(层归一化)和Broadcast(广播)操作;

目录 AllReduce通信库 一、定义与作用 二、常见AllReduce通信库 三、AllReduce通信算法 四、总结 Reduce+LayerNorm+Broadcast 算子 1. Reduce 算子 2. LayerNorm 算子 3. Broadcast 算子 组合作用 LayerNorm(层归一化)和Broadcast(广播)操作 提出的创新方案解析 优点与潜在…

项目实战_图书管理系统(简易版)

你能学到什么 一个简单的项目——图书管理系统&#xff08;浏览器&#xff1a;谷歌&#xff09;基础版我们只做两个功能&#xff08;因为其它的功能涉及的会比较多&#xff0c;索性就放在升级版里了&#xff0c;基础版先入个门&#xff09; 登录: ⽤⼾输⼊账号,密码完成登录功…

登录相关功能的优化【JWT令牌+拦截器+跨域】

登录相关功能的优化 登录后显示当前登录用户el-dropdown: Element - The worlds most popular Vue UI framework <el-dropdown style"float: right; height: 60px; line-height: 60px"><span class"el-dropdown-link" style"color: white;…

音视频开发 sdl库

介绍 SDL (Simple DirectMedia Layer) 是一个跨平台的开源多媒体库,它提供了底层访问多种硬件的接口,如音频、视频、输入设备等。它主要用于游戏开发,但也可用于其他类型的多媒体应用程序。下面是 SDL 的一些主要特点: 跨平台性: SDL 支持多种操作系统,包括 Windows、macOS、L…

HashMap中 put()方法的流程、扩容的思路(源码分析~)

文章目录 put() 方法的流程扩容流程为什么它会按照2的幂次方进行扩容呢&#xff1f; put() 方法的流程 下面我们通过分析源码来总结一下 put() 方法的流程 扩容流程 根据上图的分析&#xff0c;就可以总结出 HashMap 的扩容流程&#xff1a; 在插入元素时&#xff0c;会先…