低代码信创开发核心技术(一):基于Vue.js的描述依赖渲染DDR实现模型驱动的组件

前言

随着数字化转型的不断发展,低代码开发平台已成为企业快速建立自己的应用程序的首选方案。然而,实现这样一个平台需要具备高效、灵活和可定制化的能力。这正是基于描述依赖渲染(Description dependency rendering)所实现的。通过使用该技术,我们可以实现动态渲染组件,从而大大提高开发效率和灵活性。此外,组件的递归调用、扁平化的事件处理还能实现复杂的界面效果,让用户体验更加友好。本文将介绍如何使用Vue.js实现描述依赖渲染,让您在几分钟内轻松掌握这一技术,并享受到它所带来的便利。作为前端开发人员,了解这种前沿技术将有助于您在低代码领域中保持竞争优势。因此,通过阅读本文并掌握这一重要技术,是做出低代码开发平台的第一步,也是信息化创新的重要一环。

效果展示

动态根据JSON构建组件:
在这里插入图片描述

开发环境准备

1、首先我们需要建立一个使用js的vue3项目,如果还没有vue3项目,则用以下命令手动创建一个,如果没有这个命令,那么请先准备好node.js环境、npm环境和vue-cli环境,这里不再赘述。

vue create .

2、本示例会用到angular-expressions和axios,所以需要先npm install angular-expressionsnpm install axios安装一下。这里axios负责进行网络通讯,angular-expressions负责实现脚本解析引擎,它因为运行在独立的沙盒环境里,具有比eval和new Function更好的安全性,可以一定程度上防止XSS攻击。

引入控件系统思想

控件系统思想是一种将应用程序拆分成多个可复用的组件的编程模型。它可以提高开发效率,降低代码复杂度,使得应用程序更易于维护和扩展。在控件系统中,每个控件都有自己的方法、事件和属性,并且可以被其他组件调用和操作。

这里可以参考传统老牌的微软COM模型,它具有以下核心特点:
组件化:将应用程序拆分成多个可复用的组件;
接口化:每个组件都有自己的接口,允许其他组件调用和操作;
注册表:组件需要在注册表中注册,以便其他应用程序可以找到并使用它们。

与微软COM类似,VUE的组件也具有类似的特点。VUE组件是一个可复用的代码块,具有自己的方法、事件和属性,并且可以被其他组件调用和操作。

区别在于,VUE组件是基于Web技术的,可以轻松地嵌入到HTML页面中,并且可以与其他Web技术(如CSS和JavaScript)无缝集成。而微软COM则是基于Windows操作系统的,只能在Windows平台上运行。所以我们需要在Web上复刻出来控件系统只需要将接口化变为事件驱动,注册表变为全局自动注册即可。

在控件系统中,方法、事件和属性是控件的三个核心组成部分。方法用于执行控件的操作;事件用于通知其他控件发生了某些事情;属性则用于存储和获取控件的状态信息。
在这里插入图片描述
如上图,通过这个例子,可以一目了然一个控件的主要功能。

方法、事件和属性的作用和重要性在控件系统中非常重要。
通过方法,我们可以执行控件的操作,例如:设置控件的值、获取控件的状态等;
通过事件,我们可以通知其他控件发生了某些事情,例如:当用户点击按钮时触发Click事件;
通过属性,我们可以存储和获取控件的状态信息,例如:文本框的值等。

控件系统思想可以提高开发效率,降低代码复杂度,使得应用程序更易于维护和扩展。方法、事件和属性则是控件的三个核心组成部分,在控件系统中具有重要的作用和意义。接下来我们基于控件系统思想开始进行描述,从而实现描述依赖渲染DDR的一个简单模式。

定义描述格式

文件位于public/test.json,实际项目中,这个JSON是由后台拼装后提供的

{
	"is":"uiPage",
	"props":{
		"id":"uiPage1",
		"style":"background-color:#f3f3f3;padding:20px;box-sizing:border-box;"
	},
	"controls":[{
		"is":"uiTextBox",
		"props":{
			"id":"uiTextBox1",
			"text":"在这里输入姓名"
		}
	},{
		"is":"uiButton",
		"props":{
			"id":"uiButton1",
			"text":"确认"
		},
		"events":{
			"Click":"this.uiLabel1.text=this.uiTextBox1.text"
		}
	},{
		"is":"uiLabel",
		"props":{
			"id":"uiLabel1",
			"text":"这是一个Label组件",
			"style":"color:blue"
		}
	}]
}

这个JSON是一个描述界面的示例,也被称之为界面描述或界面模型,它描述了一个包含三个组件的页面。其中,uiPage是一个页面容器组件,它的props属性指定了页面的id和样式。controls属性中包含了三个子组件:uiTextBox1、uiButton1和uiLabel1。

uiTextBox是一个文本框组件,它的props属性指定了文本框的id和默认文本。

uiButton是一个按钮组件,它的props属性指定了按钮的id和显示文本,同时还定义了一个events属性,用于配置该按钮的点击事件。在该示例中,当按钮被点击时,执行this.uiLabel1.text = this.uiTextBox1.text语句,将文本框中的内容显示在uiLabel1组件中。

uiLabel是一个标签组件,它的props属性指定了标签的id、默认文本和样式。

这段JSON描述了一个简单的界面,下面我们将其转换为Vue.js组件进行渲染。通过使用描述依赖渲染(DDR)技术,我们可以轻松地实现低代码开发平台,快速构建出符合需求的应用程序。

自动化引入组件

接下来我们改造main.js,为了能快速开发组件,我们让webpack自动扫描路径并全局注册组件

let app = createApp(App);

const requireComponent = require.context(
	// 其组件目录的相对路径
	'./components',
	// 是否查询其子目录
	false,
	// 匹配基础组件文件名的正则表达式
	/\.vue$/
)

// 自动引入组件开始
const requireComponents = require.context("/src/components", true, /\w+\.vue$/);
requireComponents.keys().forEach((item) => {
  const componentConfig = requireComponents(item);
  const componentName = item
    .split("/")
    .pop()
    .replace(/\.\w+$/, "");
  app.component(componentName, componentConfig.default || componentConfig);
});

这段代码将自动引入组件SFC文件,它通过require.context函数来实现自动化引入。context函数在第一个参数中,我们指定了组件所在目录的相对路径;第二个参数表示是否查询其子目录;第三个参数则是匹配基础组件文件名的正则表达式。

在第二段代码中,我们使用了require.context函数来获取所有组件的上下文信息,并通过forEach函数遍历每个组件。在遍历过程中,我们首先获取组件的配置信息componentConfig,然后提取组件的名称componentName,并通过Vue.js的全局方法app.component将其注册为全局组件。需要注意的是,由于require函数返回的是一个对象,因此我们需要使用default属性来获取组件的默认导出。

通过这段代码,我们可以实现组件的自动化引入,避免手动引入组件时可能出现的疏漏和错误,提高开发效率。

定义元组件

元组件相当于真正组件的一个逻辑层包装器,模拟了真实组件环境中的嵌套效果。
文件位于src/components/metaComponent.vue

<template>
	<component :is="data.is" v-bind="data.props" :context="context" :initData="data">
		<metaComponent v-if="data.controls" v-for="(item,i) in data.controls" :data="item" :context="context">
		</metaComponent>
	</component>
</template>

<script>
	export default {
		props: ['data', 'context']
	}
</script>

<style>
</style>

首先,该组件使用了Vue.js的动态组件功能,通过传递一个data对象的is属性来指定渲染的组件类型,具体组件则是上文中我们通过自动化引入src/components下的对应SFC。另外,组件的props属性也通过v-bind指令动态绑定了data.props中的所有属性,实现了属性透传。此外,还传递了context和initData属性,其中context是上下文对象,用于在组件内部访问其他资源,例如API、状态管理器等;initData则是组件初始化数据。组件还包含了一个metaComponent子组件,用于渲染data.controls中定义的所有子元素,这样就实现了递归自动渲染,可以渲染无线层级。

我们再来看script,props属性接收了两个参数:data和context。data是一个对象,包含了组件所需的各种属性和控制元素,context则是组件上下文对象,全局唯一的,我们可以认为对于同一个页面工厂来说,只有一个context。

实现UI工厂

UI工厂是一切渲染的源头,我们通过UI工厂来管理上下文,并将后台传过来的JSON转变成我们能看到的界面
文件位于src/UIFactory.vue:

<template>
	<div>
		<metaComponent :data="page" :context="context"></metaComponent>
	</div>
</template>

<script>
	import axios from 'axios'
	import expressions from 'angular-expressions'
	export default {
		components: {},
		data() {
			return {
				page: {},
				context: {
					fireEvent: function(control, event) {
						if (control.initData.events && control.initData.events[event]) {
							let code=control.initData.events[event]
							expressions.compile(code)(this)
						}
					},
					initControl(control) {
						let {
							id,
							initData,
							context
						} = control;

						let proxyObj = {}

						for (let key in initData.props) {
							Object.defineProperty(proxyObj, key, {
								get() {
									return initData.props[key]
								},
								set(value) {
									initData.props[key] = value;
								}
							})
						}

						for (let methodName in control) {
							if (typeof control[methodName] == "function") {
								proxyObj[methodName] = control[methodName]
							}
						}

						proxyObj.controls = initData.controls

						context[id] = proxyObj;
					}
				}
			}
		},
		mounted() {
			axios.get('/test.json').then((data) => {
				let rData = data.data
				this.page = rData
			})
		}
	}
</script>

该组件使用了metaComponent自定义组件来动态渲染页面。

在script中,该组件引入了axios和angular-expressions模块,并定义了一个包含两个方法的context对象。其中,fireEvent方法用于触发控件事件;initControl方法用于初始化控件。

在mounted生命周期函数中,该组件使用axios.get方法从服务器获取页面数据,并将页面数据赋值给page属性。通过这种方式,我们可以动态地生成页面。

在methods部分,该组件定义了initControl方法,该方法用于初始化控件。当我们创建一个控件时,该控件会回调initControl方法,为控件创建一个代理对象并注册到上下文对象中。只有通过代理对象的方式,我们才能在脚本引擎的沙盒里实现双向绑定,像this.uiLabel1.text = this.uiTextBox1.text这样的语法就可以轻松实现,达到对属性的无感访问,非常类似于VB.net和C#的属性读写方式。

该组件还使用了angular-expressions模块来编译和执行控件事件代码。通过这种方式,我们可以在运行时动态地处理控件事件,而且因为运行在沙盒里,安全性要比eval和new Function高出不少。不过需要注意的是尽管它运行在沙盒里,但是如果我们给某一个图片展示控件传递了http://www.example.com/hacker.img?cookie=xxx这样的图片地址,仍然会有泄漏当前用户cookie的可能性,因为img标签加载src并不在沙盒的管控范围里,同样的,这样的地址传递给一个用了axios的方法也可能有安全问题,需要各位架构师读者们仔细进行代码审查和安全加固。

实现uiPage控件

接下来我们就实现各个控件了,每一种控件实际上都是基于VUE的组件,先实现src/components/uiPage.vue:

<template>
	<div>
		<slot></slot>
	</div>
</template>

<script>
	export default {
		props: ['id', 'context', 'initData'],
		mounted() {
			this.context.initControl(this)
		}
	}
</script>

<style>
</style>

这段代码实现了一个简单的页面容器功能。在模板部分,该组件使用了Vue.js的插槽(slot)功能,将子组件插入到页面容器中,可以很方便的实现嵌套效果。

在脚本部分,该组件定义了三个属性:id、context和initData。其中,id属性指定了页面容器的唯一标识符;context属性用于访问上下文对象,以便在组件内部访问其他资源;initData属性则是初始化数据,用于在页面容器创建时初始化组件状态。

在mounted生命周期函数中,该组件调用了context.initControl方法,该方法用于在上下文对象中注册当前组件,以便在需要时可以对其进行操作。通过这种方式,我们可以在整个应用程序中轻松地访问和管理组件,后面不做赘述。

实现uiTextBox控件

文本框:src/components/iTextBox.vue:

<template>
	<input type="text" :value="text" @input="onInput($event)" />
</template>

<script>
	export default {
		props: ['id', 'context', 'initData', 'text'],
		mounted() {
			this.context.initControl(this)
		},
		methods:{
			onInput(e){
				this.initData.props.text=e.target.value
				this.context.fireEvent(this,'Input')
			}
		}
	}
</script>

<style>
</style>

在模板部分,该组件使用了input元素来实现文本框的显示,并使用了Vue.js的双向绑定语法:value来绑定文本框的值。

在脚本部分,该组件定义了四个属性:id、context、initData和text。其中,id属性指定了文本框的唯一标识符;context属性用于访问上下文对象,以便在组件内部访问其他资源;initData属性则是初始化数据,用于在文本框创建时初始化组件状态;text属性则是绑定到文本框的值。

该组件还定义了一个名为onInput的方法,用于处理文本框输入事件。当用户在文本框中输入内容时,该方法会更新initData.props.text的值,并调用context.fireEvent方法触发Input事件。通过这种方式,我们可以在文本框值发生变化时及时驱动自定义事件。

实现uiButton控件

按钮:src/components/uiButton.vue:

<template>
	<button @click="click" type="button">{{text}}</button>
</template>

<script>
	export default {
		props: ['id', 'text', 'context', 'initData'],
		methods: {
			click() {
				this.context.fireEvent(this,'Click')
			}
		},
		mounted() {		
			this.context.initControl(this)
		}
	}
</script>

<style>
</style>

在模板部分,该组件使用了button元素来实现按钮的显示,并使用了Vue.js的双向绑定语法:text来绑定按钮的显示文本。

在脚本部分,该组件定义了四个属性:id、text、context和initData。其中,id属性指定了按钮的唯一标识符;text属性用于绑定按钮的显示文本;context属性用于访问上下文对象,以便在组件内部访问其他资源;initData属性则是初始化数据,用于在按钮创建时初始化组件状态。

在methods部分,该组件定义了一个名为click的方法,用于处理按钮的点击事件。当用户点击按钮时,该方法会调用context.fireEvent方法触发Click事件。通过这种方式,我们可以在按钮被点击时及时通知其他组件。

实现uiLabel控件

文本标签:src/components/uiLabel.vue:

<template>
	<div>{{text}}</div>
</template>

<script>
	export default {
		props: ['id', 'text', 'context', 'initData'],
		mounted() {		
			this.context.initControl(this)
		}
	}
</script>

<style>
</style>

在模板部分,该组件使用了div元素来实现文本标签的显示,并使用了Vue.js的双向绑定语法:text来绑定文本标签的显示文本。

在脚本部分,该组件定义了四个属性:id、text、context和initData。其中,id属性指定了文本标签的唯一标识符;text属性用于绑定文本标签的显示文本;context属性用于访问上下文对象,以便在组件内部访问其他资源;initData属性则是初始化数据,用于在文本标签创建时初始化组件状态。

测试

最后我们把UI工厂显示到界面上,读取我们设置好的json文件,即可看到组装好的界面,经测试,功能一切正常:
请添加图片描述

总结

低代码开发平台基于模型驱动,而模型驱动后的控件则是解决复杂度的关键,控件系统思想是一种将应用程序拆分成多个可复用的组件的编程模型,它可以提高开发效率、降低代码复杂度,使得应用程序更易于维护和扩展。在控件系统中,每个组件都有自己的方法、事件和属性,并且可以被其他组件调用和操作。为了能够将模型驱动与控件系统相结合,所以使用了描述依赖渲染(DDR)思想,作为胶水方法论,将模型转化成了控件。

这一套组合拳打下来不仅仅适用于Web应用程序,还适用于其他类型的应用程序,例如:桌面应用程序、移动应用程序等。它可以帮助开发人员提高开发效率、降低代码复杂度,使得应用程序更易于维护和扩展。

因此,我们应该认真学习低代码、描述依赖渲染、控件系统的思想,掌握概念和实现方式,以便在开发应用程序时更加高效、优雅地进行编程。同时,我们也应该关注利用低代码的信息化发展和创新,探索出更加灵活、高效的编程模型,推动信创领域的不断进步。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言CRC-16 XMODEM格式校验函数

C语言CRC-16 XMODEM格式校验函数 CRC-16校验产生2个字节长度的数据校验码&#xff0c;通过计算得到的校验码和获得的校验码比较&#xff0c;用于验证获得的数据的正确性。基本的CRC-16校验算法实现&#xff0c;参考&#xff1a; C语言标准CRC-16校验函数。 不同应用规范通过对…

三分钟阿里云服务器u1通用算力型性能、使用限制及费用说明

阿里云服务器u1是通用算力型云服务器&#xff0c;CPU采用2.5 GHz主频的Intel(R) Xeon(R) Platinum处理器&#xff0c;通用算力型u1云服务器不适用于游戏和高频交易等需要极致性能的应用场景及对业务性能一致性有强诉求的应用场景(比如业务HA场景主备机需要性能一致)&#xff0c…

C/C++每日一练(20230512) 成绩打印、补齐数组、水仙花数

目录 1. 成绩打印 ※ 2. 按要求补齐数组 &#x1f31f;&#x1f31f;&#x1f31f; 3. 水仙花数 ※ &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 成绩打印 一个班有10个同学&am…

隐语v0.8.2版本更新,首次发布TEEU

隐语v0.8.2版本更新&#x1f31f; 应用层 机器学习&#xff1a; - MPC 纵向 LR &#xff08;SSRegression&#xff09;新增 Policy SGD 优化器和 Early Stopping 支持&#xff0c;减少调参成本&#xff0c;加快收敛速度&#xff1b; - WOE 分箱进行了若干优化&#xff0c;性…

本地搭建wamp服务器并内网穿透实现无公网IP远程访问

文章目录 前言1.Wamp服务器搭建1.1 Wamp下载和安装1.2 Wamp网页测试 2. Cpolar内网穿透的安装和注册2.1 本地网页发布2.2 Cpolar云端设置2.3 Cpolar本地设置 3. 公网访问测试4. 结语 转载自cpolar极点云的文章&#xff1a;无公网IP&#xff1f;教你在外远程访问本地Wamp服务器「…

前端CSS学习(三)

1、盒子模型 盒子的概念1、页面中的每一个标签&#xff0c;都可看做是一 个“盒子” &#xff0c;通过盒子的视角更方便的进行布局2、浏览器在渲染 (显示)网页时&#xff0c;会将网页中的元素看做是一个个的矩形区域&#xff0c;我们也形象的称之为盒子CSS中规定每个盒子分别由…

Postman安装及入门接口测试使用步骤

前言 在软件测试行业中&#xff0c;作为一款比jemter更便捷更好用的软件测试工具&#xff0c;postman以其便捷灵活性首当其冲&#xff0c;成为当今测试行业领域使用较广泛的主流系统软件接口测试工具。今天Darren洋为大家讲解postman这款软件测试工具的下载安装及入门接口测试步…

Linux权限 - 概念与管理 | 文件权限的修改与转让 【详解】

目录 Linux权限 Linux权限的概念 Linux权限的基础操作 (1).实现用户账号的切换 (2).仅提升当前指令的权限 Linux权限管理 1、文件访问者的分类&#xff08;人&#xff09; 2、文件类型和访问权限&#xff08;事物属性&#xff09; 3、文件权限值的表示方法 4、文件访…

刷题刷题。

自然数拆分 利用step记录组合情况&#xff0c;只用sum不能判断组合情况 1.选择dfs原因&#xff1a;产生排列组合&#xff0c;和为7&#xff0c;step为8&#xff0c;其中7个空位&#xff0c;第8个step为输出&#xff1b; 参量的设置sum&#xff0c;step (进入下一层&#xff09;…

ThingsBoard教程(四十):规则节点解析 计算增量节点 Calculate delta

本篇文章介绍一个ThingsBoard 规则引擎中的一个节点,Calculate delta Calculate delta 计算增量 该节点可以在规则中获取上一次遥测的值,以此可以实现二次遥测的差。比如一个设备,一天上传一次数据,如果你要对比今天和昨天的数据,并将两者数据差保存到数据库,就能够使用…

Spring MVC

目录 什么是Spring MVC MVC定义 MVC和Spring MVC的关系 怎么学Spring MVC 创建Spring MVC项目 0.使用Spring Boot来创建Spring MVC项目 1.实现连接 2.获取参数 获取单个参数 获取多个参数 获取对象 后端参数重命名 获取JSON对象 从基础的URL中获取参数 上传文件Re…

1688获取商品api接口

作为一名技术爱好者&#xff0c;我们总会遇到各种各样的技术问题&#xff0c;需要寻找合适的技术解决方案。而在互联网时代&#xff0c;我们可以快速通过搜索引擎获取丰富的技术资源和解决方案。然而&#xff0c;在不同的技术分享中&#xff0c;我们常常会遇到质量参差不齐的文…

linux中查看某个文件夹下文件的个数和大小

一、统计某个目录的文件和子目录的大小 1、stat指令 stat命令 主要用于显示文件或文件系统的详细信息&#xff0c;该命令的语法格式如下&#xff1a; -f  不显示文件本身的信息&#xff0c;显示文件所在文件系统的信息-L  显示符号链接-t  简洁模式&#xff0c;只显示…

如何压缩pdf文件大小?四种方法随意选择

如何压缩pdf文件大小&#xff1f;PDF文件格式由于其跨平台性&#xff0c;易于浏览、打印和传输等特点&#xff0c;在现代社会中广泛应用于各个领域。然而&#xff0c;随着PDF文件越来越大&#xff0c;传输及存储所需的时间也会变得越来越长&#xff0c;从而降低了工作效率。在这…

如何用ChatGPT协助搭建品牌视觉体系(VI)?

该场景对应的关键词库&#xff08;18个&#xff09;&#xff1a; VI体系、品牌、目标市场、品牌DNA、人群特征、设计理念、标志设计、配色方案、字体选择、图形元素、价值观、形象、客户经理、需求、品牌定位、目标受众、主色调、辅助色 提问模板&#xff08;2个&#xff09;&…

进阶自定义类型——结构体,枚举,联合

本章重点&#xff1a; 1.结构体 1.1 结构体类型的声明 1.2 结构的自引用 1.3 结构体变量的定义和初始化 1.4 结构体内存对齐 1.5 结构体传参 1.6 结构体实现位段(位段的填充&可移植性) 2.枚举 2.1 枚举类型的定义 2.2 枚举的优点 2.3 枚举的使用 3.联合 3.1 联合类…

【TCP 协议】连接管理之 “三次握手,四次挥手”

哈喽&#xff0c;大家好~我是你们的老朋友&#xff1a;保护小周ღ 本期为大家带来的是网络编程中的 TCP 传输控制协议保证数据可靠性传输的机制之一的——连接管理&#xff0c;通信双方采用 “三次握手” 来建立连接&#xff0c;采用 “四次挥手” 会断开连接&#xff0c;如何…

React + ts学习笔记

前提准备&#xff1a; 环境配置 安装node.js 官网安装&#xff1a;当前使用版本18.15.0 安装新的react应用&#xff1a; 运行命令新建react-app npx create-react-app study-ts-app当前版本&#xff1a; “react”: “^18.2.0”,“react-dom”: “^18.2.0”, 如果出现如…

CompletableFuture使用教学

CompletableFuture使用教学 一、开始一个线程异步执行不需要返回值 通过runAsync方式 //1.不加线程池方式 CompletableFuture<Void> completableFuture CompletableFuture.runAsync(() -> {System.out.println(Thread.currentThread().getName());//停顿几秒try {…

对象应用:C++字符串和vector,对象的new与delete重构

对象应用 C字符串和vector字符串创建方式字符串拼接字符串追加 字符串截断autovector创建方式vector操作 new与delete重构new与delete的工作步骤new与delete重构应用只能生成栈对象只能生成堆对象 C字符串和vector C的字符串是一个对象&#xff0c;存在于std标准库中&#xff0…