TL;DR
大家好, 我建了 2 个规范和写了相关的实现, 借助相关的实现和工具, 你可以
- 创建 schema 和验证数据
- 简单而富有表达力的语法和关键字
- 描述字段间的依赖关系
- 动态的字段
- 客户端和服务端共享 schema
尽管目前只有 js 的实现, 后面增加不同的语言实现后, 理论上可以跨语言 - 生成表单
- 把 schema 传入表单组件, 即可得到一个表单
- 支持 vue 和 react
- 只需要少量代码, 就可以支持一个组件库, 目前支持 iview 和 antd
下面是两个使用相同的 schema 在 iview 和 antd 下生成的表单例子
vue + iview
react + antd
下面我将分别简单介绍这两个规范和相关的工具.
GateSchema
GateSchema 是一个用来描述数据的结构和格式的规范, 它跟 JSON-Schema 一样, 使用一个 JSON 对象来描述数据, 所以不同的语言可以共享同一个 schema. 与 JSON-Schema 不同, GateSchema 使用 “约束” 来描述一个数据, 一个 schema 就是一个 “约束” 列表, 表达这个数据应该符合的所有约束. 具体请参见规范 GateSchema-Specification
手写 schema 可能是一件十分繁琐的事, 所以我们并不建议手写, 而应该使用具体实现提供的便捷语法来构建, 请看下面.
gateschema-js
gateschema-js是 GateSchema 的 javascript 实现, 它提供了非常简单的语法让你创建 schema 和验证数据. 下面是一个稍微有点复杂例子
import _ from 'gateschema'
const schema = _
// 数据应该是一个 map, 它拥有下面这些键和值
.map({
// gender 必须存在, enum(枚举)类型, 只能是 1, 2
gender: _.required.enum({
MALE: 1,
FEMALE: 2
}),
// name 必须存在, map 类型, 包含 firstName 和 lastName
name: _.required.map({
firstName: _.required.string.notEmpty,
lastName: _.required.string.notEmpty,
}),
email: _.required.string.format('email'),
// phone 可选, number(数字)类型
phone: _.optional.number,
// skills 必须存在, list(列表) 类型
skills: _.required.list(
// list 里面的元素: 必须存在(不能为 undefined 或 null), string(字符串) 类型, 不能为空字符串('')
_.required.string.notEmpty
)
})
// 可以继续添加约束
// 它还拥有下面这些键和值
.map({
introduction: _.optional.string
})
// 条件约束
.switch('/gender', [
{
// 如果上面的 gender 的值为 1
case: _.value(1),
// 那么, 必须存在一个 birthday 键, 类型为 string, 格式为 date
schema: _.map({
birthday: _.required.string.format('date')
})
}
])
// 验证数据
const input = {
//....
}
schema.validate(input, (err) => {
// 当某个约束不满足时, gateschema 会终止后面的约束的验证
console.log(err)
// ValidationError {
// keyword: 'required',
// msgParams: { KEY: 'required' },
// path: '/gender',
// value: undefined,
// msg: 'should not be null or undefined' }
})
下面一个表述两个字段中的一个必须存在的例子
import _ from 'gateschema'
const schema = _
.map({
email: _
.switch('/phone', [
{
// 如果 phone 存在
case: _.required,
// 那么 email 是可选的
schema: _.optional
},
{
// 其它所有情况
case: _.any,
// email 都必须存在
schema: _.required
}
])
.string
.format('email'),
phone: _
.switch('/email', [
{
case: _.required,
schema: _.optional
},
{
case: _.any,
schema: _.required
}
])
.string
.number
})
如果你还是觉得这些写起来不够简洁, 你可以把一些逻辑封装成别面使用别名, 另外, 你可以自定义错误提示信息, 下面这个例子使用了内置的别名和自定义错误信息
const schema = _
.map({
name: _
// r -> required
.r.$msg('请输入登录名')
// str -> string
.str
.notEmpty,
password: _
.r.$msg('请输入密码')
.str
.length([6, 16]).$msg('密码应该包含6至16个字符'),
repassword: _
.r.$msg('请输入确认密码')
.equal('/password').$msg('确认密码跟密码不一样')
})
详细的用法请参考 gateschema-js API
StateForm
StateForm 定义了一个基于 JSON 的结构, 用来描述一个表单的状态(state), 同时定义了一些表单事件和内置组件. 借助相关实现, 你可以使用一个 json 对象来生成表单
目前的实现
GateSchema Form
gatescheme-form-vue 和 gateschema-form-react 把一个 gateschema 转换成一个 StateForm 的 state 对象, 然后利用 StateForm 来展示表单, 当用户有新的输入时, 更新整个 state 进而更新整个表单的显示.
下面是使用 gatescheme-form-vue 的例子
// file: GateSchemaForm.js
import Vue from 'vue'
// stateform implementation
import createStateForm from '@stateform/iview'
import "@stateform/iview/dist/stateform-iview.css"
import { createForm } from 'gateschema-form-vue'
// 1. 创建 StateForm 组件, 如果你要使用 upload 相关组件, 你需要自己实现 handleUpload 和 handleRemove 函数
const StateForm = createStateForm({
upload: {
handleUpload(file, props, cb) {
// 在这里上传你的文件, 然后调用 cb 函数, 传入一个上传结果
// 默认会把 url 作为上传组件的输出值, 如果你需要对外输出其它信息, 你可以在上传结果中设置一个 `value` 字段
setTimeout(() => {
cb({
status: 'done', // 'done' | 'error',
url: 'http://....',
// error: 'error msg',
// value: {name: 'file name', url: 'http://....'},
})
}, 1000)
},
handleRemove(file) {
// 这个函数只是一个通知, 你可以在这里通知服务端删除文件
}
},
components: {
// 你可以在表单中使用自定义组件
}
})
// 2. 创建 GateSchemaForm 组件
const GateSchemaForm = createForm({
StateForm
})
// 注册到全局
Vue.component('GateSchemaForm', GateSchemaForm)
// file: App.vue
<template>
<GateSchemaForm
:schema="schema"
v-model="value"
@submit="handleSubmit"
/>
</template>
<script>
import _ from 'gateschema'
// your schema
const schema = _
.required
.map({
name: _
.required
.string
.notEmpty,
gender: _
.required
.enum({
MALE: 0,
FEMALE: 1
}),
age: _
.optional
.number,
intro: _
.optional
.string
.other('form', {
component: 'Textarea'
// StateForm options
// see https://github.com/stateform/StateForm-Specification
})
})
export default {
data() {
return {
schema: schema,
value: {}
}
},
methods() {
handleSubmit() {
console.log(this.value)
}
}
}
</script>
更多细节请参考 gateschema-form-vue 和 gateschema-form-react
最后
目前各个项目的文档还不够完善, 我有时间会分别写详细的例子.
另外, 我还计划定一个描述 RPC 接口的规范(PartonAPI), 目前已有初稿. 就像 SwaggerAPI 描述 restful 接口, PartonAPI 将使用 GateSchema 来描述 RPC 接口.
如果你有任何想法或者建议, 欢迎提 issue 或 PR