来自React嵌套表单与Vue数据绑定的灵感

新的发布/订阅模式的多层级数据管理机制

  原先一直在用ant-design来开发React站点。但是最近来了一个样式自定义性比较强的项目,就进入ant-design的盲区了。普通显示组件都是顺手拈来,但是当我做到一个庞大的功能性页面时,就@#¥%~^了,我需要一个类似ant-design的Form的数据嵌套表单组件。
  在某 Du 和某 Ge 上搜了一顿,没发现特别健壮可用的(恕我孤陋寡闻),同时又研究了下rc-field-form,感觉在灵活性上还是差了一点。最终决定自己撸一份(一言不和就手撸),于是就有了一个新儿子@idler8/form-react

什么是嵌套表单呢

  • 在以前,我们做复杂表单是类似这样的(当然,以前没有 ReactHook)

    function FormPage() {
      const [fieldValue, setFieldValue] = useState("");
      const [fieldObjectValues, setFieldObjectValues] = useState({});
      return (
        <div>
          <input
            value={fieldValue}
            onChange={(e) => setFieldValue(e.target.value)}
          />
          <div>
            <div>这是一个嵌套表单</div>
            <div>
              <input
                value={fieldObjectValues.fieldObjectSubKey}
                onChange={(e) =>
                  setFieldObjectValues({
                    ...fieldObjectValues,
                    fieldObjectSubKey: e.target.value,
                  })
                }
              />
            </div>
          </div>
          <button
            onClick={() => {
              console.log({
                fieldKey: fieldValue,
                fieldObjectKey: fieldObjectValue,
              });
            }}
          >
            打印输出
          </button>
        </div>
      );
      // 咋一看没啥问题吧,但是如果fieldObjectValues下还有一级对象甚至更多呢?
    }
    
  • 用了rc-field-form之后是这样的

    function FormPage() {
      const [form] = Form.useForm();
      return (
        <Form form={form}>
          <Form.Item name="fieldKey">
            <Input />
          </Form.Item>
          <div>
            <div>这是一个嵌套表单</div>
            <div>
              <Form.Item name={["fieldObjectKey", "fieldObjectSubKey"]}>
                <Input />
              </Form.Item>
            </div>
          </div>
          <Button
            onClick={() => {
              console.log(form.getFieldValues());
            }}
          >
            打印输出
          </Button>
        </Form>
      );
      // 真香,数据想怎么组装就怎么组装。可惜碰到了自定义界面的需求,ant-design歇菜
    }
    
  • 现在,你可以用@idler8/form-react这样做。

    // 获取组件,想怎么做就怎么做,只需要用useFieldReader即可拿到数据
    // 输入组件,想怎么做就怎么做,只需要用useFieldTrigger即可拿到修改函数
    function Input({ name }) {
      const value = useFieldReader(name);
      const onChange = useFieldTrigger(name);
      return <input value={value} onChange={(e) => onChange(e.target.value)} />;
    }
    function FormComponent() {
      const values = useFieldReader(); //不传参即可拿到该表单上下文所有数据
      return (
        <div>
          <Input name="fieldKey" />
          <div>
            <div>这是一个嵌套表单</div>
            <div>
              <Input name={["fieldObjectKey", "fieldObjectSubKey"]} />
            </div>
          </div>
          <button
            onClick={() => {
              console.log(values);
            }}
          >
            打印输出
          </button>
        </div>
      );
    }
    function FormPage() {
      return (
        <Form>
          <FormComponent />
        </Form>
      );
    }
    // @idler8/form-react 是与界面无关的数据管理机制
    

@idler8/form-react可以做什么

  • 你可以直接用钩子,在不使用Form时它有默认上下文

    import { useFieldReader } from "@idler8/form-react";
    function Component({ name = "key" }) {
      const values = useFieldReader();
      return <div>{JSON.stringify(values)}</div>;
    }
    
  • 一个独立的Form上下文

    import { Form } from "@idler8/form-react";
    import Component from "第一个例子";
    function FormPage() {
      return (
        <Form>
          <Component />
        </Form>
      );
    }
    
  • 可以在上下文上做隔离

    import { FKeys } from "@idler8/form-react";
    import Component from "第一个例子";
    function FormPage() {
      return (
        <div>
          <Component />
          <FKeys name="form1">
            <Component />
          </FKeys>
        </div>
      );
    }
    
  • 数据引用变化时,父元素必然触发 onChange,子元素判断引用变化才触发 onChange

    import { useFieldReader, useFieldTrigger } from "@idler8/form-react";
    import Component from "第一个例子";
    function FormPage() {
      const values = useFieldReader();
      const onChange = useFieldTrigger(["key1", "key2"]);
      return (
        <div>
          <Component name="key1" /> // 父组件
          <Component name={["key1", "key2"]} /> // 中间组件
          <Component name={["key1", "key2", "key3"]} /> // 子组件
          <button onClick={() => onChange({ key3: 1 })}>操作</button>
        </div>
      );
      /** 第一次点击操作
       * values 由 undefined 转为 {"key1":{"key2":{"key3":1}}}
       * 主界面刷新
       */
      /** 第二次点击操作
       * values 数据表现未发生变化 仍然是 {"key1":{"key2":{"key3":1}}}
       * values.key1.key2 的引用产生变化,产生以下结果
       * 主界面刷新 -> 父组件刷新 -> 中间组件刷新
       * 由于子组件值仍然是1,子组件不会刷新
       */
    }
    
  • 表单验证不能少

    import { useFieldReader, useFieldTrigger } from "@idler8/form-react";
    function Component({ name = "key", rule }) {
      const errResponse = useRuleValidator(rule);
      return <div>{JSON.stringify(errResponse)}</div>;
    }
    // 可以使用任意第三方库灵活判断内容变化,throw或者return非undefined的值,都算作出错
    async function isSuccess(values) {
      if (typeof values?.key !== "Success") {
        return "Not Success";
      }
    }
    async function isFail(values) {
      if (typeof values?.key === "Fail") {
        return "Not Fail";
      }
    }
    function FormPage() {
      const validator = useFormValidator();
      const onChange = useFieldTrigger("key");
      return (
        <div>
          <ValidInput rule={isSuccess} />
          <ValidInput rule={isFail} />
          <button
            onClick={() => {
              onChange((oldValue) =>
                oldValue === "Success" ? "Fail" : "Success"
              );
              validator()
                .then(() => {
                  console.log("这个例子永远到不了这里,总有一个错误抛出");
                })
                .catch((err) => {
                  console.log("错误内容以数组形式输出在这里", err);
                });
            }}
          >
            操作
          </button>
        </div>
      );
    }
    
  • 在Github或Gitee上查看它的用例。

  • 更多用法等待你来发现

就这?还没说到 Vue 呢

  大家都知道Vue是采用 Observer 对数据做双向绑定的,闲人老师做表单的时候忽然发现,这个底层逻辑和 Observer 好像啊,那为啥不拆出来呢,还能用在其它非 React 的项目里(比如某个需要兼容到 IE6 的项目)。于是就把底层单独拎了出来。这就有了@idler8/observer

它是一个简单的数据订阅/发布模型,只会做这个

import { createRootObserver } from "@idler8/observer";
//生成一个根观察器
const observer = createRootObserver({});
//订阅数据变化
observer.addCallback(function (values) {
  console.log("data changed", values);
});
//获取订阅器当前数据
const value = observer.get();
//生成子数据订阅器
const subObserver = observer.checkout(["key1", "key2"]);
//订阅子数据变化
subObserver.addCallback(function (values) {
  console.log("data is changed", values);
});
//发布数据变化
subObserver.set("key1.key2 = anything");

更多功能等待你来发现

你可能感兴趣的:(web前端,react.js,vue.js,javascript)