美文网首页
【EasyPage 教你写表单01】- EasyPage 框架介

【EasyPage 教你写表单01】- EasyPage 框架介

作者: pikun | 来源:发表于2024-07-07 17:51 被阅读0次

    简介

    EasyPage 正如其名,含义是:简单的页面。它能让我们的前端页面开发更加简单,目前我们专注于解决表单问题。

    • Make Front-End Development Easier

    它提供了一套描述式的 API,来帮助你高效地开发用户界面。无论是简单还是复杂的界面,EasyPage 都可以胜任。

    下面是一个最基本的示例,创建一个姓名的表单:

    // ./name.tsx
    import { nodeUtil } from '@easy-page/antd-ui';
    
    export const name = nodeUtil.createField('name', '姓名', {
      value: '',
      required: true,
    });
    

    上面的字段定义,展示了 EasyPage 的两个特性:

    • 简洁性
      • 我想用任何的写法去描述一个姓名字段,都会比上面更长。
      • 随着场景的复杂,EasyPage 的简介优势则更加明显。
    • 描述性
      • 基于描述,与 UI ”解耦“,底层可做更多适配(antd、acro 等 UI),为业务逻辑的复用性提供了更多的想象空间。

    你可能已经有了些疑问——先别急,在后续的文档中我们会详细介绍每一个细节。现在,请继续看下去,以确保你对 EasyPage 作为一个框架到底提供了什么有一个宏观的了解。

    EasyPage 能做什么?

    • 它可以用于开发表单页面,也可直接用于开发前端页面
    • 它可以帮我们做页面状态管理,再也不用手动在组件间各种透传 Props。
    • 它可以帮我们做页面的精确渲染,再也不用为因为状态变化,导致刷新过多,降低页面性能而烦恼。
    • 它可以帮我们监听页面内任意状态变化,并执行相关副作用处理,去改变组件的:值、组件 Props、选项、显隐状态等。
    • 它可以帮我们更好的复用逻辑,减少代码中大量的 if else 判断以及相互影响。
    • 它可以帮助我们解决复杂的业务场景难题,如:父子表单多层嵌套内外联动、数组表单增删等。
    • 在后续的文档中,我们将一一见识到上述好处。

    EasyPage 的优势是什么?

    简洁性

    上述示例可以初步看到其简洁性,下面我们描述如下复杂逻辑,再看其简洁性。

    • 新建一个性别字段,两个选项:男、女
    • 当选中“男”时,出现字段:喜欢看的书(输入框)
    • 当选中“女”时,不出现任何内容。
    
    /** 选项 **/
    const manOption = nodeUtil.createNode('man', { name: '男' });
    const womenOption = nodeUtil.createNode('female', { name: '女' })
    
    /** 字段 **/
    const sexField = nodeUtil.createField('sex','性别', { value: '', mode: 'single' })
    
    /** 子表单字段 **/
    const hobby =  nodeUtil.createField('like', '喜欢看的书', { value: '' })
    
    export const sex = sexField.appendChildren([
        manOption.appendChildren([hobby]),
        womenOption,
      ]);
    
    
    • 若此场景有更简洁的写法,请在 Github 下评论。

    高内聚、低耦合

    你可以想象到的关于字段的一切,都可以在自身定义中,完整独立逻辑闭环。

    以下还是以表单元素为一个实际的例子,来展示上述特点:

    • 新建一个年龄字段,以输入框展示,必填。

      import { Empty, InputEffectedType, nodeUtil } from '@easy-page/antd-ui';
      import React from 'react';
      
      export const age = nodeUtil.createField<
        string,
        { name: string },
        Empty,
        InputEffectedType
      >(
        'age',
        '年龄',
        {
          /** 默认值 */
          value: '',
          /** 必填 * 号 */
          required: true,
        }
      );
      
      
    • 【联动属性】基于姓名字段,来展示 placeholder: ${name} 的年龄

          import { Empty, InputEffectedType, nodeUtil } from '@easy-page/antd-ui';
          import React from 'react';
      
          export const age = nodeUtil.createField<string,{ name: string },Empty,InputEffectedType>(
            'age',
            '年龄',
            {
              ...,
              actions: [
                {
                  effectedKeys: ['name'],
                  /** 加载时,立即执行 */
                  initRun: true,
                  action: ({ effectedData }) => {
                    return {
                      effectResult: {
                        inputProps: {
                          placeholder: `${effectedData.name || '-'} 的年龄`,
                        },
                      },
                    };
                  },
                },
              ],
          );
      
    • 【联动显隐】当 姓名=a 时,隐藏年龄

        import { Empty, InputEffectedType, nodeUtil } from '@easy-page/antd-ui';
        import React from 'react';
      
        export const age = nodeUtil.createField<string,{ name: string },Empty,InputEffectedType>(
          'age',
          '年龄',
          {
          ...,
            /** 字段显示与隐藏 */
            when: {
              effectedKeys: ['name'],
              show({ effectedData }) {
                return effectedData.name !== 'a';
              },
            },
          },
          {
            ...
          }
        );
      
      
      • 【联动提示】当 age < 10 时,提示:儿童

        import { Empty, InputEffectedType, nodeUtil } from '@easy-page/antd-ui';
        import React from 'react';
        
        export const age = nodeUtil.createField<string,{ name: string },Empty,InputEffectedType>(
          'age',
          '年龄',
          {
            ...
          },
          {
            /** 输入框配置 */
            input: { trigger: 'onChange' },
            /** FormItem 配置 */
            formItem: {
              /** 自定义提示语:带上下文 */
              customExtra: ({ value }) => {
                return <div>{value && +value < 10 ? '儿童' : ''}</div>;
              },
            },
          }
        );
        
        
      • 【提交时,数据处理】将数据处理成数字

        import { Empty, InputEffectedType, nodeUtil } from '@easy-page/antd-ui';
        import React from 'react';
        
        export const age = nodeUtil.createField<string,{ name: string },Empty,InputEffectedType>(
          'age',
          '年龄',
          {
            ...,
            /** 数据预处理 */
            postprocess(context) {
              return {
                age: Number(context.value),
              };
            },
            ...
          },
          {
            ...
          }
        );
        
        
    • 【联动校验】当 age < 0 || age > 200 时,提示:请输入合法年龄;当 姓名=pk 时,age > 200 合法;当姓名变化时,触发 age 校验。

      import { Empty, InputEffectedType, nodeUtil } from '@easy-page/antd-ui';
      import React from 'react';
      
      export const age = nodeUtil.createField<string,{ name: string },Empty,InputEffectedType>(
        'age',
        '年龄',
        {
          ...,
          /** 字段验证 */
          validate({ value, pageState }) {
            if (!value) {
              return { success: false, errorMsg: '请输入年龄' };
            }
            if (+value < 0 || (+value > 200 && pageState.name !== 'pk')) {
              return { success: false, errorMsg: '请输入合法年龄' };
            }
            return { success: true };
          },
          
        },
        {
          ...
          },
        }
      );
      
      

    从上可见,上述字段逻辑比较复杂,依赖 name 的填写,做相关联动,但从头到尾,并没有去其他地方做任何事情,自身就可以把逻辑完全描述。
    充分可见其:高内聚、低耦合特点。

    • 小惊喜:我们的 API 描述对于类型提示很友好哦!
      [图片上传失败...(image-ddde6e-1720432245229)]

    复用性

    对于组件复用,相信大家都不陌生,在这种场景下,我们可以抽离公共组件:

    • 梳理抽象业务逻辑、定义组件接口

    但随着业务的愈发复杂,我们发现:

    • 【抽象难】组件很难抽象的很完美,几乎都会有不满足的情况。
    • 【场景复杂】组件内渐渐基于业务场景,逐步增多的 if else。
    • 【耦合】牵一发而动全身,有时改了某个组件,却影响了其他页面逻辑。

    这种现象和问题在表单里体现的更加淋漓尽致。JAVA 面向对象思想中,提出了:继承的概念来解决复用性问题,但在前端 hooks 的写法下,很难实践。也造成了现在的困扰。

    但,EasyPage 提出了针对这一场景的解决方案,以下还是承接上面的例子,来展示其复用性。

    • 继承 age 的属性,定义一个 newAge 字段,变化如下:
      • 字段名改为:我的年龄
      • 字段改为:非必填
      • 字段校验:允许为空
      • 字段显示:任何时候都展示
      • 字段增加 Tooltip 提示:这是新的年龄
    import { nodeUtil } from '@easy-page/antd-ui';
    import { age } from './age';
    
    export const newAge = nodeUtil.extends(age, {
      name: '我的年龄',
      required: false,
      validate(oldValidate) {
        return (options) => {
          if (!options.value) {
            return { success: true };
          }
          return oldValidate?.(options);
        };
      },
      when(oldWhen) {
        return {
          effectedKeys: oldWhen?.effectedKeys || [],
          show(context) {
            return true;
          },
        };
      },
      fieldUIConfig: (oldConfig) => {
        const newConfig = { ...(oldConfig || {}) };
        newConfig.formItem = newConfig.formItem || {};
        newConfig.formItem.tooltip = '这是新的年龄';
        return newConfig;
      },
    });
    
    

    运行效果见官网。

    可以看到,上述年龄字段继承了原来的所有逻辑,并进行了修改和更新,并和原来的代码无任何冲突和交集,也无需做任何的抽象处理。

    扩展性

    扩展性主要是从开发角度来描述,如何更灵活的应对各种场景。

    首先,我们想创建一个输入框字段:

    import { nodeUtil } from '@easy-page/antd-ui';
    
    export const name = nodeUtil.createField('name', '姓名', {
      value: '',
      required: true,
    });
    

    一般创建的默认组件:

    • 输入型,默认:输入框组件
    • 选择型,默认:单选-RadioGroup、多选-CheckBoxGroup

    其余,靠指定组件,如:写一个描述字段:

    export const desc = nodeUtil.createField(
      'desc',
      '介绍',
      {
        value: '',
      },
      {
        /** 指定 TextArea 组件 **/
        ui: UI_COMPONENTS.TEXTAREA,
      }
    );
    
    
    • @easy-page/antd-ui 包里的组件即目前所支持的组件。

    如果默认的组件,还无法满足要求。此时,有两种方式:

    • 采用自定义节点完成
      export const desc1 = nodeUtil.createCustomField(
        'desc',
        '介绍',
        ({ value, onChange }) => {
          /** 自定义输入框组件 */
          return <Input value={value} onChange={onChange} />;
        },
        {
          value: '',
        },
      );
      
    • 扩展一个通用组件,再通过 ui 属性进行配置。

    我们除了可以:扩展一个通用组件外,可能还因为不同的公司,要求的基础组件库不一样,如:

    • 在字节可能是 arco、在阿里可能是 antd

    我们可以参考:@easy-page/antd-ui 扩展一个自己的组件库,扩展成本大概在 1 - 2 天左右。

    • 在框架的设计上,预留了支持 vue 的可能
    • 除了编写页面外,做 cli 的问题列表开发,也可以扩展

    EasyPage 和 Formily

    相同点

    • 都是基于 Schema 描述 + 解析引擎模式渲染页面
    • 都能做到精确渲染

    差异

    • 定位不同
      • 我们专注于研发提效,面向代码开发,不面向低代拖拽。本质是:更优雅、更简洁的写表单,而不是不用代码拖拽搭建表单,生成静态代码。
    • 面向场景不同
      • 我们不单单解决表单开发的问题,更解决前端页面开发的问题。
    • Schema 设计不同
      • Formily 最初设计更倾向于用 Schema 描述一切,Schema 比较庞大,只能是 JSON 模式,而非 JS。
      • EasyPage 只描述数据和数据之间的关系,Schema + UIConfig,数据和 UI 信息分离,Schema 比较简洁,是 JS 协议,非纯粹 JSON Schema。
    • 解决研发提效思路不同
      • Formily 更倾向于通过:Schema 描述(拖拉拽等方式)减少基础代码开发量,提升开发效率。
      • 我们则是倾向于建立研发标准,减少代码量,在具有非常好的可维护性同时,提升研发效率。
    • 使用方式不同
      • Formily 可以通过:拖拉拽、组件描述、schema 描述等多种方式来开发页面。
      • EasyPage 通过轻量的 API,来开发页面。(后续会支持更赞的方式,埋一个彩蛋!)

    相关文章

      网友评论

          本文标题:【EasyPage 教你写表单01】- EasyPage 框架介

          本文链接:https://www.haomeiwen.com/subject/wpqocjtx.html