UForm v1 要来了

2,030 阅读17分钟

时隔半年,历经风雨洗刷,UForm居然活下来了,真是难得!

过去的都是时间,留下来的只有沉淀,半年时间,UForm沉淀了无数Issue与PR,感谢为UForm做过贡献的所有人。

UForm v1官网(临时地址):uform-next.netlify.com/

github仓库地址:github.com/alibaba/ufo…

简要介绍


考虑到还有并不太了解UForm的同学,所以这里先大概介绍一下UForm。

UForm是一个面向中后台复杂业务场景的高性能表单解决方案,它最Base的能力就是高性能,主要原因是它内部的表单渲染从过去对React已知的单向数据流概念变成了点对点通讯模式,保证最小粒度的渲染效率,从而提升了表单整体性能。同时,它还提供了一套强大的副作用管理能力,可以让用户编写各种复杂联动、复杂校验等业务逻辑。还有,它也提供了一套强大的JSON Schema数据驱动的动态表单渲染能力,可以帮助用户解决动态渲染表单的各种需求。简而言之,UForm,非常强大,您想到的,想不到的,在UForm里都有考虑。这也是UForm的核心定位。就是要做一套真正的表单领域型解决方案,而非一个单纯的React组件

特性


今天,UForm带着广大用户的期待而来,下面先介绍下UForm V1主要有哪些新特性,我们可以从不同维度来讲:

  • 项目维度
    • 集团多BU共建,包括 alibaba/nopage(noform)作者 @鬼鼠 ,同时也包括集团内部多个BU的前端技术专家参与共建,比如 @黄子毅,目标是为了统一集团对外表单解决方案。
    • 从源码到单测的整体Typescript重构,更规范的类型定义,更完备的智能提示
    • 更高的单测覆盖率,目前从@uform/core层到@uform/react-schema-renderer层都包含了大量单元测试,保证底层代码绝对稳定。
    • 更详细的文档体系
      • 增加了很多二次开发(自定义组件开发)教程
      • 增加英文文档
  • Packages
    • @uform/core
      • 时间旅行能力支持,整体Form Tree State可观察,可回溯
      • 内置immer,精确更新,智能降级,无需关心浏览器版本
      • 更加完备的校验引擎
        • 悲观校验(validateFirst)能力
        • warning校验
        • 手动批量或精确校验字段能力
        • 手动清除校验消息能力
        • 更加校验规则扩展机制
        • 增加正则规则扩展机制
        • 增加校验消息模板引擎定制能力
      • 更加完备的生命周期钩子
      • 更加完备的路径解析引擎
      • 解耦schema,更易扩展/二次开发(过去0.x的core是强耦合json schema的)
    • @uform/react
      • 包名变更,0.x @uform/react ---> 1.x @uform/react-schema-renderer,现在的@uform/react是一个全新的库,它只负责react组件的状态管理,无关json schema,若用户对UI定制需求高,则可直接使用该库进行开发
      • 全新架构,基于React Hooks开发,更灵活,更易扩展
      • 同样的actions/effects,但带来的是更大的想象空间
    • @uform/react-schema-renderer
      • 包含了过去0.x的@uform/react几乎100%的能力,只有少部分API有变化
      • 升级表单协议,提供了更完备的json schema解析校验能力
      • 更高效方便的Form/FormItem样式定制能力
      • 提供了更加灵活的自定义组件开发方式,用来适配更复杂的业务场景
    • @uform/antd or @uform/next
      • 废除过去私有FormItem样式,全部继承组件库样式,所以不会再存在18px的空白占位符问题,同时也能支持对应组件库的主题样式定制能力
      • 废除array组件的默认样式,统一采用cards样式,更加标准化,也能很好的适配组件库主题样式定制能力
      • array table内部的table组件采用了对应组件库的table,可以适配更加复杂的table定制需求
      • 支持了FormStep布局组件,可以非常方便的处理分步表单场景

架构总览


整体来看,UForm未来是要打造一个完整的表单体系化的生态,目前UForm v1花了很大精力在研发核心底层,主要原因就是为了给上层生态体系打下坚实基础。

核心差异


@uform/core

已经完全重构,如果有用户直接基于v0 @uform/core开发的,是不能平滑迁移的,新版API变化非常大。

@uform/react

属于新库,如果有用户依赖@uform/react开发的,只需要将包名换为@uform/react-schema-renderer即可

@uform/react-schema-renderer

几乎100%迁移0.x的@uform/react能力,但还是存在少许break change。

如果除了这些还有和旧版不一致的地方,直接视为bug,走正常提issue流程即可,我们会尽快修复:

  • 移除 registerFormFieldPropsTransformer API,如果要批量配置属性,可以在effects中批量配置
  • 移除 registerFormWrapper API,使用registerFormItemComponent 替代
  • 移除 calculateSchemaInitialValues API
  • x-props含义改变,现在的x-props代表Field级别的扩展属性,比如FormItem的属性labelCol/wrapperCol等,现在组件级别的扩展属性使用x-component-props,不过这个改变在当前版本不会彻底改掉,所以还是向后兼容的,在未来的版本中很有可能移除兼容代码,或者将兼容代码抽离成第三方包。

@uform/antd or @uform/next

  • Field组件改名SchemaMarkupField,目前向后兼容,可同时使用Field组件与SchemaMarkupField,后续考虑废弃此兼容项
  • 移除帮助信息自动变成pop tips功能,目前title/dependencies都可以直接传react node

整体来看,API层面上基本无差别,更多的是样式层面上,因为是继承了原有组件库的Form/FormItem样式,在一些细微布局上会有差别

核心亮点


时间旅行能力

因为新版内核完全做了重构,采用了Observable Graph的数据结构来管理整个表单的状态,使得表单任何状态都是可观察的,表单状态也是Immutable的,所以我们可以轻易的实现时间旅行,这个能力主要在以下场景有较大价值:

  • 开发者调试工具,可以开发一个devtools,方便开发者清晰的查看表单当前或过去的状态扭转情况
  • 保存用户操作现场,当用户执行一系列操作之后,我们可以把表单整体状态保存起来,存储在localstorage中,在用户关机之后再打开相同页面,则可以完全复原之前的操作状态,注意:这个是操作状态,而不是单纯的表单数据,比如,用户在分步表单场景,用户操作停留在某一步,中途退出之后再打开该页面,只要使用时间旅行能力,则可以完完全全复原之前的操作状态,该在第几步,就在第几步,甚至你在某一步操作过程中出现了校验异常,在回滚过程中,也能把所有校验状态给恢复,如果用户还能将用户的浏览器滚动位置一并缓存,那基本上可以做到完美回滚了。

现在我们直接看代码:

import React from "react";
import ReactDOM from "react-dom";
import {
  SchemaForm,
  Field,
  FormButtonGroup,
  Submit,
  createFormActions,
  FormCard,
  FormStep
} from "@uform/next";
import { Button } from "@alifd/next";
import "@alifd/next/dist/next.css";

const actions = createFormActions();

let cache = {};

const App = () => (
  <SchemaForm actions={actions} labelCol={{ span: 8 }} wrapperCol={{ span: 6 }}>
    <FormStep
      style={{ marginBottom: 20 }}
      dataSource={[
        { title: "基本信息", name: "step-1" },
        { title: "财务信息", name: "step-2" },
        { title: "条款信息", name: "step-3" }
      ]}
    />
    <FormCard name="step-1" title="基本信息">
      <Field name="a1" required title="A1" type="string" />
    </FormCard>
    <FormCard name="step-2" title="财务信息">
      <Field name="a2" required title="A2" type="string" />
    </FormCard>
    <FormCard name="step-3" title="条款信息">
      <Field name="a3" required title="A3" type="string" />
    </FormCard>
    <FormButtonGroup>
      <Submit>提交</Submit>
      <Button onClick={() => actions.dispatch(FormStep.ON_FORM_STEP_PREVIOUS)}>
        上一步
      </Button>
      <Button onClick={() => actions.dispatch(FormStep.ON_FORM_STEP_NEXT)}>
        下一步
      </Button>
      <Button
        onClick={() => {
          cache = actions.getFormGraph();
        }}
      >
        存储当前状态
      </Button>
      <Button
        onClick={() => {
          actions.setFormGraph(cache);
        }}
      >
        回滚状态
      </Button>
    </FormButtonGroup>
  </SchemaForm>
);

ReactDOM.render(<App />, document.getElementById("root"));

想要直接看效果?点击:codesandbox.io/s/exciting-…

整个例子主要演示了分步表单,如何缓存表单状态,如果你停留在任何一个步骤中缓存了状态,再点击回滚就会立即回滚到对应的状态上。

完备的校验引擎

校验引擎属于除了表单状态管理之外的第二大核心能力,如果校验引擎做的不够好,不够完备,那么整体表单研发效率或者产品用户体验都会大打折扣。

validateFirst校验

validateFirst校验就是,如果给一个Field传入一组rules规则,那么当Field校验的时候,如果第一个校验不通过,则不会继续后续校验流程,这种校验模式比较适合前面的是同步校验,后面的是异步校验的场景

import React from "react";
import ReactDOM from "react-dom";
import { SchemaForm, Field, FormButtonGroup, Submit, Reset } from "@uform/next";
import Printer from "@uform/printer";
import "@alifd/next/dist/next.css";

const sleep = duration =>
  new Promise(resolve => {
    setTimeout(resolve, duration);
  });

const App = () => (
  <Printer>
    <SchemaForm validateFirst labelCol={8} wrapperCol={6}>
      <Field
        name="aaa"
        x-rules={[
          {
            required: true,
            message: "不能为空"
          },
          {
            async validator() {
              await sleep(1000);
              return "异步校验失败";
            }
          }
        ]}
        type="string"
        title="字段1"
      />
      <FormButtonGroup sticky offset={8}>
        <Submit>提交</Submit><Reset>重置</Reset></FormButtonGroup>
    </SchemaForm>
  </Printer>
);
ReactDOM.render(<App />, document.getElementById("root"));

想直接看效果?请点击 codesandbox.io/s/smoosh-pi…

从上面的例子中我们可以看到,只要必填校验不通过,那么异步校验永远不会执行,如果我们关闭validateFirst,那么校验的时候就会全量校验,并合并错误消息。

warning校验

warning校验就是解决一些阈值设置类场景阈值设定到某个程度,会有提醒文案,但是不会阻止提交

import React from "react";
import ReactDOM from "react-dom";
import { SchemaForm, Field, FormButtonGroup, Submit, Reset } from "@uform/next";
import Printer from "@uform/printer";
import "@alifd/next/dist/next.css";

const App = () => (
  <Printer>
    <SchemaForm validateFirst labelCol={8} wrapperCol={6}>
      <Field
        name="aaa"
        x-rules={value => {
          if (value > 50) {
            return {
              type: "warning",
              message: "阈值大于50可能会对业务造成负面影响"
            };
          }
        }}
        type="number"
        title="阈值设置"
      />
      <FormButtonGroup sticky offset={8}>
        <Submit>提交</Submit><Reset>重置</Reset></FormButtonGroup>
    </SchemaForm>
  </Printer>
);
ReactDOM.render(<App />, document.getElementById("root"));

想直接看效果?请点击 codesandbox.io/s/musing-we…

从上面的例子中,我们可以看到,在自定义校验器中,只需要返回一个type为warning的对象即可透出警告信息,但是并不会阻塞提交

失焦校验

失焦校验,主要是针对异步校验场景,可以大大减少重复请求次数

import React from "react";
import ReactDOM from "react-dom";
import { SchemaForm, Field, FormButtonGroup, Submit, Reset } from "@uform/next";
import Printer from "@uform/printer";
import "@alifd/next/dist/next.css";

const sleep = duration =>
  new Promise(resolve => {
    setTimeout(resolve, duration);
  });

const App = () => (
  <Printer>
    <SchemaForm validateFirst labelCol={8} wrapperCol={6}>
      <Field
        name="aaa"
        x-props={{
          triggerType: "onBlur"
        }}
        x-rules={async value => {
          if (Number(value) > 50) {
            await sleep(2000);
            return {
              type: "warning",
              message: "阈值大于50可能会对业务造成负面影响"
            };
          }
          if (!/^\d+$/.test(value)) {
            return "必须为数字";
          }
        }}
        type="string"
        title="阈值设置"
      />
      <FormButtonGroup sticky offset={8}>
        <Submit>提交</Submit><Reset>重置</Reset></FormButtonGroup>
    </SchemaForm>
  </Printer>
);
ReactDOM.render(<App />, document.getElementById("root"));

想直接看效果?请点击 codesandbox.io/s/keen-ferm…

在上面例子中,我们可以在x-props上简单的配置triggerType即可快速处理失焦校验

手工批量校验/手工清除校验消息

某些场景,我们在一些联动交互过程中,可能需要人工去触发一批字段的校验逻辑,也可能需要人工去清除一批字段的校验逻辑

import React from "react";
import ReactDOM from "react-dom";
import {
  SchemaForm,
  Field,
  FormButtonGroup,
  createFormActions
} from "@uform/next";
import Printer from "@uform/printer";
import { Button } from "@alifd/next";
import "@alifd/next/dist/next.css";

const actions = createFormActions();

const App = () => (
  <Printer>
    <SchemaForm actions={actions} validateFirst labelCol={8} wrapperCol={6}>
      <Field
        name="aaa"
        x-rules={[
          {
            required: true,
            message: "不能为空"
          },
          {
            pattern: /^\d+$/,
            message: "必须为数字"
          }
        ]}
        type="string"
        title="阈值设置"
      />
      <FormButtonGroup sticky offset={8}>
        <Button
          onClick={() => {
            actions.clearErrors("aaa"); //这里可以传FormPathPattern做批处理
          }}
        >
          输入完成点我清空校验
        </Button>
        <Button
          onClick={() => {
            actions.validate("aaa"); //这里可以传FormPathPattern做批处理
          }}
        >
          点我触发校验
        </Button>
      </FormButtonGroup>
    </SchemaForm>
  </Printer>
);
ReactDOM.render(<App />, document.getElementById("root"));

想直接看效果,请点击:codesandbox.io/s/wonderful…

校验规则扩展/正则规则扩展

考虑到业务私有规则沉淀,所以需要支持一种更加方便的校验规则扩展机制

import React from "react";
import ReactDOM from "react-dom";
import {
  SchemaForm,
  Field,
  FormButtonGroup,
  createFormActions,
  registerValidationRules,
  setValidationLocale,
  registerValidationFormats
} from "@uform/next";
import Printer from "@uform/printer";
import { Button } from "@alifd/next";
import "@alifd/next/dist/next.css";

const actions = createFormActions();

setValidationLocale({
  zh: {
    custom: "必须以123开头"
  }
});

registerValidationFormats({
  custom: /^123.+/
});

registerValidationRules({
  max12345: value => {
    return Number(value) <= 12345 ? "必须大于12345" : "";
  }
});

const App = () => (
  <Printer>
    <SchemaForm actions={actions} validateFirst labelCol={8} wrapperCol={6}>
      <Field
        name="aaa"
        x-rules={[
          {
            format: "custom"
          },
          {
            max12345: true
          }
        ]}
        type="string"
        title="阈值设置"
      />
      <FormButtonGroup sticky offset={8}>
        <Button
          onClick={() => {
            actions.clearErrors("aaa"); //这里可以传FormPathPattern做批处理
          }}
        >
          输入完成点我清空校验
        </Button>
        <Button
          onClick={() => {
            actions.validate("aaa"); //这里可以传FormPathPattern做批处理
          }}
        >
          点我触发校验
        </Button>
      </FormButtonGroup>
    </SchemaForm>
  </Printer>
);
ReactDOM.render(<App />, document.getElementById("root"));

想直接看效果,请点击:codesandbox.io/s/hardcore-…

校验消息模板引擎

考虑到校验消息可能会存在富文本情况,所以需要支持模板引擎扩展能力,保证业务定制能力最大化

import React from "react";
import ReactDOM from "react-dom";
import {
  SchemaForm,
  Field,
  FormButtonGroup,
  createFormActions,
  registerValidationRules,
  setValidationLocale,
  registerValidationFormats,
  registerValidationMTEngine,
  FormPath
} from "@uform/next";
import Printer from "@uform/printer";
import { Button } from "@alifd/next";
import "@alifd/next/dist/next.css";

const actions = createFormActions();

const template = (message, context) => {
  const elements = [];
  let lastOffset = 0;
  message.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, $0, offset) => {
    elements.push(message.slice(lastOffset, offset));
    elements.push(FormPath.getIn(context, $0));
    lastOffset = offset + match.length;
    return "";
  });
  if(elements.length <=0) return ;
  return React.createElement("span", {}, ...elements);
};

registerValidationMTEngine(template);

setValidationLocale({
  zh: {
    custom: "必须以123开头,{{help}}"
  }
});

registerValidationFormats({
  custom: /^123.+/
});

registerValidationRules({
  max12345: value => {
    return Number(value) <= 12345 ? "必须大于12345,{{help}}" : "";
  }
});

const App = () => (
  <Printer>
    <SchemaForm actions={actions} validateFirst labelCol={8} wrapperCol={6}>
      <Field
        name="aaa"
        x-rules={[
          {
            format: "custom",
            help: <a href="//github.com/alibaba/uform">查看帮助链接</a>
          },
          {
            max12345: true,
            help: <a href="//github.com/alibaba/uform">查看帮助链接</a>
          }
        ]}
        type="string"
        title="阈值设置"
      />
      <FormButtonGroup sticky offset={8}>
        <Button
          onClick={() => {
            actions.clearErrors("aaa"); //这里可以传FormPathPattern做批处理
          }}
        >
          输入完成点我清空校验
        </Button>
        <Button
          onClick={() => {
            actions.validate("aaa"); //这里可以传FormPathPattern做批处理
          }}
        >
          点我触发校验
        </Button>
      </FormButtonGroup>
    </SchemaForm>
  </Printer>
);
ReactDOM.render(<App />, document.getElementById("root"));


想直接看效果,请点击 codesandbox.io/s/pensive-l…

整个案例给您呈现了一个超级强大的模板引擎扩展能力,轻松实现校验消息富文本化

标准的JSON Schema校验能力

之前有用户反馈,UForm的schema描述并不能支持原生JSON Schema的校验属性,比如pattern/max这些标准校验属性,都是通过x-rules来配置的,这次,我们将所有的JSON Schema相关的校验属性统一支持了一遍,当然,其实内部实现,都是转换成x-rules,只是用户感知不到

import React from "react";
import ReactDOM from "react-dom";
import { SchemaForm, Field, createFormActions } from "@uform/next";
import Printer from "@uform/printer";
import "@alifd/next/dist/next.css";

const actions = createFormActions();

const App = () => (
  <Printer>
    <SchemaForm actions={actions} validateFirst labelCol={8} wrapperCol={6}>
      <Field name="aaa" required maximum={100} type="string" title="阈值设置" />
      <Field name="bbb" format="url" type="string" title="URL" />
    </SchemaForm>
  </Printer>
);
ReactDOM.render(<App />, document.getElementById("root"));

想直接看效果,请点击 codesandbox.io/s/withered-…

Vue适配能力

因为内核的完全重构,从设计上也更加贴合vue的设计理念,所以,UForm便可以轻松的适配vue技术栈,这里是基于@uform/core快速实现了@uform/vue原型代码,感兴趣的同学可以移步:codesandbox.io/s/vue-templ…

状态管理能力/局部副作用管理能力

UForm内部提供了一个完备的响应式状态管理系统,我们可以使用它做一些额外的状态管理,同时,这些状态最终都会挂到FormGraph上,所以也会享受到FormGraph时间旅行能力,还是针对上面分步表单的例子,这次我们直接看源码:

//注册控制器组件
const FormStep = createControllerBox<IFormStep>(
  'step',
  ({ form, schema, children }) => {
    //通过useFieldState设置当前字段默认状态,同时获取操作函数
    const [{ current }, setFieldState] = useFieldState({
      current: 0
    })
    const ref = useRef(current)
    const { dataSource, ...stepProps } = schema.getExtendsComponentProps()
    const items = toArr(dataSource)
    const update = (cur: number) => {
      form.notify(StateMap.ON_FORM_STEP_CURRENT_CHANGE, {
        value: cur,
        preValue: current
      })
      //更新当前字段状态,该状态最终会挂到FormGraph上的fieldState上去
      setFieldState({
        current: cur
      })
    }
    //局部副作用管理,用于实现复杂自定义组件的逻辑功能
    useFormEffects(($, { setFieldState }) => {
      items.forEach(({ name }, index) => {
        setFieldState(name, (state: any) => {
          state.display = index === current
        })
      })
      $(StateMap.ON_FORM_STEP_CURRENT_CHANGE).subscribe(({ value }) => {
        items.forEach(({ name }, index) => {
          if (!name)
            throw new Error('FormStep dataSource must include `name` property')
          setFieldState(name, (state: any) => {
            state.display = index === value
          })
        })
      })

      $(StateMap.ON_FORM_STEP_NEXT).subscribe(() => {
        form.validate().then(({ errors }) => {
          if (errors.length === 0) {
            update(
              ref.current + 1 > items.length - 1 ? ref.current : ref.current + 1
            )
          }
        })
      })

      $(StateMap.ON_FORM_STEP_PREVIOUS).subscribe(() => {
        update(ref.current - 1 < 0 ? ref.current : ref.current - 1)
      })

      $(StateMap.ON_FORM_STEP_GO_TO).subscribe(payload => {
        if (!(payload < 0 || payload > items.length)) {
          update(payload)
        }
      })
    })
    ref.current = current
    return (
      <Fragment>
        <Steps {...stepProps} current={current}>
          {items.map((props, key) => {
            return <Steps.Step {...props} key={key} />
          })}
        </Steps>{' '}
        {children}
      </Fragment>
    )
  }
) 



具体源码地址:packages/antd/src/components/FormStep.tsx

整体看下来,想要实现一个带逻辑的布局组件,我们只需要使用createControllerBox即可,同时想要在布局组件或者自定义组件内实现逻辑功能,我们就使用useFieldState和useFormEffects即可,当然,我们还提供了useFormState的能力,不过在自定义组件或者布局组件中不太推荐使用它,因为它会监听FormState变化重新渲染,这样对性能会有影响,所以我们更推荐用户直接从props上取form实例,从实例中直接拿状态即可。

Effects复用能力(Effect Hook)

考虑到用户在实现业务逻辑的过程中,其实也会存在大量可复用的业务逻辑,即便是在副作用这一层,所以,我们支持了Effects可复用能力

import React from "react";
import ReactDOM from "react-dom";
import {
  SchemaForm,
  Field,
  createFormActions,
  createEffectHook
} from "@uform/next";
import { Button } from "@alifd/next";
import Printer from "@uform/printer";
import "@alifd/next/dist/next.css";

const dyiHook$ = createEffectHook("onDiyChange");

const outerActions = createFormActions();

const getCustomEffects = () => {
  const innerActions = createFormActions();
  dyiHook$().subscribe(() => {
    innerActions.setFieldState("aaa", state => {
      state.value = "123";
    });
  });
};

const App = () => (
  <Printer>
    <SchemaForm
      actions={outerActions}
      effects={() => {
        getCustomEffects();
      }}
      validateFirst
      labelCol={8}
      wrapperCol={6}
    >
      <Field name="aaa" required type="string" title="AAA" />
      <Field name="bbb" required type="string" title="BBB" />
      <Button
        onClick={() => {
          outerActions.dispatch("onDiyChange");
        }}
      >
        触发自定义事件
      </Button>
    </SchemaForm>
  </Printer>
);
ReactDOM.render(<App />, document.getElementById("root"));


想直接看效果,请点击 codesandbox.io/s/cranky-ch…

以上例子演示了我们可以自定义生命周期钩子函数,同时这个钩子函数可以在任何函数体内直接使用,同时,createFormActions API目前也支持了可以再任何函数体内使用的能力,其实它内部会自动获取outerActions并透传给每个自定义effects函数

超复杂自定义组件

什么才是超复杂自定义组件:

  • 组件内部存在大量表单组件,同时内部也存在大量联动关系
  • 组件内部存在私有的服务端动态渲染方案
  • 组件内部有复杂布局结构

对于超复杂自定义组件,在旧版中很难实现,主要受限于表单数据和校验不能更好的同步。

import React from "react";
import ReactDOM from "react-dom";
import {
  SchemaForm,
  Field,
  InternalField,
  registerFormField,
  useFormEffects,
  FormEffectHooks,
  FormPath,
  FormButtonGroup,
  Submit
} from "@uform/next";
import { Form, Input } from "@alifd/next";
import Printer from "@uform/printer";
import "@alifd/next/dist/next.css";

const FormItem = ({ component, ...props }) => {
  return (
    <InternalField {...props}>
      {({ state, mutators }) => {
        const messages = [].concat(state.errors || [], state.warnings || []);
        let status = "";
        if (state.loading) {
          status = "validating";
        }
        if (state.invalid) {
          status = "error";
        }
        if (state.warnings && state.warnings.length) {
          status = "warning";
        }
        return (
          <Form.Item
            {...props}
            help={messages.length ? messages : props.help && props.help}
            validateStatus={status}
          >
            {React.createElement(component, {
              ...state.props,
              value: state.value,
              onChange: mutators.change,
              onBlur: mutators.blur,
              onFocus: mutators.focus
            })}
          </Form.Item>
        );
      }}
    </InternalField>
  );
};

//不用connect包装
registerFormField("complex", ({ path }) => {
  useFormEffects(({ setFieldState }) => {
    FormEffectHooks.onFieldValueChange$("ccc").subscribe(({ value }) => {
      if (value === "123") {
        setFieldState("ddd", state => {
          state.value = "this is linkage relationship";
        });
      }
    });
  });

  return (
    <div>
      <FormItem
        label="AA"
        name={FormPath.parse(path).concat("aaa")}
        component={Input}
      />
      <FormItem
        label="BB"
        name={FormPath.parse(path).concat("bbb")}
        component={Input}
      />
      <FormItem label="CC" name="ccc" component={Input} />
      <FormItem label="DD" name="ddd" component={Input} />
    </div>
  );
});

const App = () => (
  <Printer>
    <SchemaForm validateFirst labelCol={8} wrapperCol={6}>
      <Field type="complex" name="aaa" />
      <FormButtonGroup>
        <Submit>提交</Submit>
      </FormButtonGroup>
    </SchemaForm>
  </Printer>
);
ReactDOM.render(<App />, document.getElementById("root"));


想要直接看效果?请点击 codesandbox.io/s/kind-reso…

在这个例子中,我们主要使用了两个核心 API,主要是 useFormEffects 和 InternalField,useFormEffects 给开发者提供了局部写 effects 逻辑的地方这样就能很方便的复用 effects 逻辑,InternalField 则就是@uform/react 的 Field 组件,这个可以具体看看@uform/react 的文档,因为 SchemaForm 内部也是使用的@uform/react,所以可以共享同一个 Context,所以我们就能很方便的在自定义组件内使用 InternalField,同时需要注意一点,直接使用 InternalField 的时候,我们注册的 name 是根级别的 name,如果想要复用当前自定义组件的路径,可以借助 FormPath 解析路径,然后再 concat 即可。

升级指南


如果您现在一直在使用@uform/antd或者@uform/next,同时,您也没有使用前面所说的@uform/react-schema-renderer被废弃的API,那么完全可以无缝平滑升级,当然,使用过程中肯定会遇到bug,这个只需要您积极的在github上提issue即可,我们会尽快修复。

品牌升级


因为UForm已经是一个集团共建的技术产品了,而不是个人或单个团队的技术产品,所以需要在品牌命名上考虑整体的品牌文化建设,经过内部多次讨论,最终我们确定了一个全新的技术品牌,大概在今年年底的时候,UForm会以全新的品牌姿势给大家见面,敬请期待。

Q/A


  • UForm v1预计什么时候正式发布?

    答:目前已经发布alpha版本,同时我们也正在内部业务验证中,等业务验证差不多,基本稳定之后便可以正式发布,大概率在12月中旬即可正式发布。

  • UForm Vue适配版预计什么时候投入开发?

    答:因为目前Vue也在着手3.0的开发,所以我们打算后续主要基于3.0版本的API来开发,因为3.0对Typescript更加友好,也更加FP,UForm希望保持技术新鲜性,所以未来会直接基于3.0开发。

  • UForm品牌升级之后的版本会与UForm v1出现API大变化吗?

    前面提到的被废弃的API,同时已经内置已兼容的,后续很有可能不会内置兼容逻辑,我们可能会考虑提供polyfill单独包来提供兼容逻辑

  • UForm 小程序适配版预计什么时候投入开发?

    答:目前还在调研设计阶段,后续是有这方面规划的,可能主要会从集团标准小程序着手投入,同时也会从rax方向着手投入

  • UForm的Form Schema什么时候能描述逻辑?

    答:目前还在设计阶段,这部分的东西属于一个独立且厚重的模块,需要详细设计,并验证。

  • UForm开发者工具预计什么时候发布?

    答:预计明年年初开始投入开发。