工作原理
Amis 工作原理
amis 的渲染过程是将 json
转成对应的 React 组件。先通过 json
的 type 找到对应的 React Component
,然后把其他属性作为 props
传递过去,通过 React 组件中的代码完成渲染。
拿一个表单页面来说,amis JSON 配置一般是:
json
{
"type": "page",
"title": "页面标题",
"subTitle": "副标题",
"body": {
"type": "form",
"title": "用户登录",
"body": [
{
"type": "input-text",
"name": "username",
"label": "用户名"
}
]
}
}
对应的 React 组件是:
jsx
<Page title="页面标题" subTitle="副标题">
<Form title="用户登录">
<InputText name="username" label="用户名" />
</Form>
</Page>
对应 Amis 中的 React 组件是:
jsx
import * as React from 'react';
import {Renderer} from 'amis-core';
@Renderer({
type: 'page'
// ... 其他信息隐藏了
})
export class PageRenderer extends React.Component {
// ... 其他信息隐藏了
render() {
const {
title,
body,
render // 用来渲染孩子节点,如果当前是叶子节点则可以忽略。
} = this.props;
return (
<div className="page">
<h1>{title}</h1>
<div className="body-container">
{render('body', body) /*渲染孩子节点*/}
</div>
</div>
);
}
}
// 如果不支持 Decorators 语法也可以使用如下写法
export Renderer({
type: 'page'
})(class PageRenderer extends React.Component {
render() {
// ...同上
}
})
jsx
@Renderer({
type: 'form'
// ... 其他信息隐藏了
})
export class FormRenderer extends React.Component {
// ... 其他信息隐藏了
render() {
const {
title,
body,
render // 用来渲染孩子节点,如果当前是叶子节点则可以忽略。
} = this.props;
return (
<form className="form">
{body.map((control, index) => (
<div className="form-item" key={index}>
{render(`${index}/control`, control)}
</div>
))}
</form>
);
}
}
jsx
@Renderer({
type: 'input-text'
// ... 其他信息隐藏了
})
export class FormItemTextRenderer extends React.Component {
// ... 其他信息隐藏了
render() {
const {
label,
name,
onChange
} = this.props;
return (
<div className="form-group">
<label>{label}<label>
<input type="text" onChange={(e) => onChange(e.currentTarget.value)} />
</div>
);
}
}
仿 Amis 实现 Vue 低代码
Amis 是 React 开发的,虽然提供了 Vue 的使用方式,但不如直接使用 Vue 低代码灵活,API 的差异也大大增加了前端的使用成本。
参照 Amis 的原理,可以解析预置算的 JSON 配置,通过 Vue 的动态组件 + 递归调用,将组件名和属map到动态组件上,实现 Vue 低代码。
下面是一个案例:
vue
/**
* 在 pages 中,预期的使用方式
*/
<template>
<div>递归动态组件</div>
<LowTree :lowjson="lowjson"></LowTree>
</template>
<script setup>
import LowTree from "../components/LowTree.vue";
// 注意:实际使用时,lowjson放在统一的文件夹中,动态加载。
const lowjson = [{
name: 'LowA',
params: { msg: ' components 1' },
click: () => { alert() },
children: [
{
name: 'LowB',
params: { msg: ' components 2' },
children: [
{
name: 'LowC',
params: { msg: ' components 3' },
}
]
}
]
}, {
name: 'LowA'
}]
</script>
vue
/**
* 根据json传参,动态完成组件渲染
* 核心逻辑:组件名匹配、递归调用、参数结构
*/
<template>
<component
v-for="(item, index) in lowjson" :key="index"
:is="components[item.name]"
:params="item.params"
@click="item.click"
>
<template v-if="item.children">
<LowTree
:lowjson="item.children"
></LowTree>
</template>
</component>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
import LowA from "../components/LowA.vue";
import LowB from "../components/LowB.vue";
import LowC from "../components/LowC.vue";
// 一个全局注册所有组件的对象(项目中通过app.use全局注册)
const components = {
LowA,
LowB,
LowC
}
const props= defineProps({
/**
* 注意:json可传递属性、指令、生命周期callback、defineEmits等。
* 参数全部由使用者自定义,下沉到使用者的自定义组件内实现。不在LowTree组件兼容。减少复杂度和熟悉成本。
*/
lowjson: {
type: Array,
required: true
}
})
</script>
vue
/**
* LowA 组件。LowB、LowC与LowA结构相同。
*/
<template lang="pug">
div LowA
span {{params?.msg}}
slot
</template>
<script setup>
import {defineProps} from 'vue'
const props= defineProps({
params: {
type: Object,
required: false
}
})
</script>
<style scoped>
div {
border: 1px solid #585656;
display: inline-block;
}
</style>
实现效果:
LowA components
LowB components
LowC components
Vue 项目使用
Vue 项目中快速在项目内实现低代码混合开发模式,可以直接引入 LowTree.vue
+ 自定义组件即可。
若需继续提效,还需实现可视化编辑器。即在核心代码的基础上,做边缘功能的设计和开发。
就如同接下来介绍的 uni-app 低代码案例。
仿 Amis 实现 uni-app 低代码
参照 uni-lowcode:
vue
<r-drag :index="index" @swapComp="swapComp" @addComp="addComp">
<component
:is="item.type"
:option="item.option"
:compStyle="item.compStyle"
></component>
</r-drag>