来来来, 试一试 GateSchema
发布于 2 天前 作者 William17 152 次浏览 来自 分享

TL;DR

大家好, 我建了 2 个规范和写了相关的实现, 借助相关的实现和工具, 你可以

  • 创建 schema 和验证数据
    • 简单而富有表达力的语法和关键字
    • 描述字段间的依赖关系
    • 动态的字段
  • 客户端和服务端共享 schema
    尽管目前只有 js 的实现, 后面增加不同的语言实现后, 理论上可以跨语言
  • 生成表单
    • 把 schema 传入表单组件, 即可得到一个表单
    • 支持 vue 和 react
    • 只需要少量代码, 就可以支持一个组件库, 目前支持 iview 和 antd

下面是两个使用相同的 schema 在 iview 和 antd 下生成的表单例子
vue + iview

Edit gateschema-form-vue demo

react + antd

Edit gateschema-form-react demo

下面我将分别简单介绍这两个规范和相关的工具.

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-vuegateschema-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-vuegateschema-form-react

最后

目前各个项目的文档还不够完善, 我有时间会分别写详细的例子.
另外, 我还计划定一个描述 RPC 接口的规范(PartonAPI), 目前已有初稿. 就像 SwaggerAPI 描述 restful 接口, PartonAPI 将使用 GateSchema 来描述 RPC 接口.

如果你有任何想法或者建议, 欢迎提 issue 或 PR

回到顶部