【EasyPage 教你写表单13】- 扩展一个通用组件

15 阅读4分钟

扩展一个通用组件

我们知道,我们有个默认的 UI 库,包含了一些基础组件,但若是这些组件还无法满足需求,我们希望通过:ui 属性指定我们想要的组件,该怎么办呢?

  • 我们可以扩展组件

扩展一个组件,大概有这些步骤:

  • 需要通过 connector 给组件连接副作用处理。
  • 需要声明组件的:Props 类型,用于属性透传 Props
    • Props 的组成为:框架通用 Props + 组件自身 Props + 扩展 Props
  • 定义组件的 EffectType,使其具备副作用处理能力

听起来很复杂,实际应该比较简单,我们可以看看下面的例子。

扩展一个输入框

我们自定义一个输入框吧:

// Input/index.tsx
import { ComponentProps, connector } from '@easy-page/antd-ui';
import { Input as AntdInput, InputProps as AntdInputProps } from 'antd';
import React from 'react';

/** 1. 组件的基础属性 **/
export type InputBaseProps = AntdInputProps;

/** 2. 结合框架的 Props */
export type CustomInputProps = ComponentProps<InputBaseProps, string>;

/** 3. 声明类型提示 */
declare module '@easy-page/antd-ui' {
  export interface FieldUIConfig {
    customInput?: InputBaseProps;
  }
}

/** 4. 通过 connector 和 React.memo 包裹组件 */
export const CustomInput = connector(
  React.memo((props: CustomInputProps) => {
    const { frameworkProps: _, value, onChange, ...baseProps } = props;
    return <AntdInput {...baseProps} onChange={onChange} value={value} />;
  })
);

通过 declare module '@easy-page/antd-ui' 的声明,我们就可以定义字段时,有提示:

基于上述:1 - 4 步,我们就完成了一个组件的扩展,我们注册一下组件。首先,对 UI_COMPONETS 常量扩展一下:

import { UI_COMPONENTS } from "@easy-page/antd-ui";

export const EXT_UI_COMPONENTS = {
  ...UI_COMPONENTS,
  CUSTOM_INPUT: 'customInput'
}

  • 这里有个细节:CUSTOM_INPUT: 'customInput' 后面的:customInput 其实是declare module '@easy-page/antd-ui' 中定义的属性的 key,否则找不到对应的配置。

EasyPage 中注册此组件:

import { DEFAULT_COMPONENTS, EasyPage } from '@easy-page/antd-ui';
import React from 'react';
import '../common/style';
import '@easy-page/antd-ui/style.css';
import { pageInfo } from './pageInfo';
import { EXT_UI_COMPONENTS } from './constant';
import { CustomInput } from './Input';

const Topic29 = () => {
  return (
    <EasyPage<{ name: string; age: string }>
      {...pageInfo}
      /** 按需加载表单所需组件 * */
      components={{
        ...DEFAULT_COMPONENTS,
        [EXT_UI_COMPONENTS.CUSTOM_INPUT]: CustomInput,
      }}
      pageType="form"
    />
  );
};

export default Topic29;

接着,我们在定义字段的时候,指定我们的扩展组件,并给其透传属性:

import { nodeUtil } from '@easy-page/antd-ui';
import { EXT_UI_COMPONENTS } from './constant';

export const name = nodeUtil.createField(
  'name',
  '姓名',
  {
    value: '',
  },
  {
    ui: EXT_UI_COMPONENTS.CUSTOM_INPUT,
    customInput: {
      placeholder: '这是姓名字段',
    },
  }
);

效果如下:

使用副作用

我们给定义的输入框增加一下副作用的能力:

  • 可以基于副作用改变组件的 Props

首先,我们定义副作用类型:InputEffectedType:

export type InputEffectedType = {
  inputProps?: InputBaseProps;
};

/**
 * - 1. 定义组件 Props,一般由:UI 库组件本身 Props + 框架通用组件 Props + 自定义组件 Props 构成
 */
export type InputProps = ComponentProps<InputBaseProps, any, InputEffectedType>;

然后,我们就拿着传递过来的副作用结果,直接放到组件上即可:

export const CustomInput = connector(
  React.memo((props: CustomInputProps) => {
    const {
      frameworkProps: { effectedResult },
      value,
      onChange,
      ...baseProps
    } = props;
    console.log('baseProps:', baseProps);
    return (
      <AntdInput
        {...baseProps}
        {...(effectedResult?.inputProps || {})}
        onChange={onChange}
        value={value}
      />
    );
  })
);

如上,我们在:frameworkProps 中取出:effectedResult 放到了输入框上,就使其具备了此能力,效果如下:

  • 姓名2 基于姓名的值展示:placeholder
import { Empty, nodeUtil } from '@easy-page/antd-ui';
import { EXT_UI_COMPONENTS } from './constant';
import { InputEffectedType } from './Input';

export const name2 = nodeUtil.createField<
  string,
  { name: string },
  Empty,
  InputEffectedType
>(
  'name2',
  '姓名2',
  {
    value: '',
    actions: [
      {
        effectedKeys: ['name'],
        initRun: true,
        action: ({ effectedData }) => {
          return {
            effectResult: {
              inputProps: {
                placeholder: `基于 name: ${
                  effectedData.name || ''
                } 的 placeholder`,
              },
            },
          };
        },
      },
    ],
  },
  {
    ui: EXT_UI_COMPONENTS.CUSTOM_INPUT,
  }
);

效果如下:

扩展输入框的能力

我们给输入框增加一个能力:

  • 当设置了: trigger = 'onBlur' 时,在 onBlur 时才触发值的变化。

    export type InputBaseProps = AntdInputProps & {
      /** 字段变化时间:onBlur 表示 onBlur 时才提交变化, 默认 onBlur */
      trigger?: 'onChange' | 'onBlur';
    };
    

详细改动如下:

/* eslint-disable @typescript-eslint/no-explicit-any */
import { ComponentProps, connector } from '@easy-page/react-ui';
import { Input as AntdInput, InputProps as AntdInputProps } from 'antd';
import React, { useEffect, useState } from 'react';

export type InputBaseProps = AntdInputProps & {
  /** 字段变化时间:onBlur 表示 onBlur 时才提交变化, 默认 onBlur */
  trigger?: 'onChange' | 'onBlur';
};

export type InputEffectedType = {
  inputProps?: InputBaseProps;
};
/**
 * - 1. 定义组件 Props,一般由:UI 库组件本身 Props + 框架通用组件 Props + 自定义组件 Props 构成
 */
export type InputProps = ComponentProps<InputBaseProps, any, InputEffectedType>;

/**
 * - 2. 重写 FieldUIConfig,增加组件配置提示
 */
declare module '@easy-page/react-ui/interface' {
  export interface FieldUIConfig {
    input?: InputBaseProps;
  }
}
/**
 * - 3. 编写通用组件逻辑
 * @param props
 * @returns
 */
export const Input = connector(
  React.memo((props: InputProps) => {
    const {
      frameworkProps: { effectedResult },
      value,
      onChange,
      onBlur,
      trigger = 'onChange',
      ...baseProps
    } = props;
    const [fieldValue, setFieldValue] = useState(value);
    useEffect(() => {
      if (value && value !== fieldValue) {
        setFieldValue(value);
      }
    }, [value]);
    if (trigger === 'onChange') {
      return (
        <AntdInput
          {...baseProps}
          onBlur={onBlur}
          {...(effectedResult?.inputProps || {})}
          onChange={onChange}
          value={value}
        />
      );
    }
    return (
      <AntdInput
        {...baseProps}
        {...(effectedResult?.inputProps || {})}
        onBlur={(e) => {
          onChange({ target: { value: fieldValue } });
          onBlur?.(e);
        }}
        onChange={(val) => {
          setFieldValue(val.target.value);
        }}
        value={fieldValue}
      />
    );
  })
);

  • 其余组件扩展可参考:@easy-page/antd-ui 里的组件进行扩展,流程大同小异。