2 min read

UI 组件库的组件参数类型验证

UI 组件库的组件参数类型验证
Photo by Lautaro Andreani / Unsplash

对于 TypeScript 的 React 项目,编写一个类型安全的组件是非常自然的。这里以 UUI 为例。UUI 是一个用 TypeScript 打造的 React 组件库。

UUI 精简版的 TextField 组件:

export interface TextFieldFeatureProps {
  name?: string;
  type?: 'text' | 'tel' | 'url' | 'email' | 'password';
  placeholder?: string;
  loading?: boolean;
  disabled?: boolean;
  value?: string | null | undefined;
  onChange?: (value: string, event: React.ChangeEvent<HTMLInputElement>) => void;
}

export funtion TextField(props: TextFieldFeatureProps) {
    // implementation
}

如果是在实际开发写业务功能的组件时,直接用 TypeScript 就完全满足了对组件参数类型安全的保证。但是对于一个组件库来说,这个库可能会被一个 Pure JavaScript 项目所使用,这时候 TypeScript 编译期时面的类型安全保证就完全失效了。所以在编译时类型安全的同时,还要在运行时对传入的这些参数类型进行验证。

React 官方有提供一个工具 prop-types 用于运行时验证参数类型。可以使用这个工具对组件做一个运行时验证的补充,同时写一个工具用来创建 propTypes。

export function createComponentPropTypes<
  T extends { [key in string]: any }
>(data: UUIComponentFeaturePropTypes<T>) {
  return  data
}

function RecursiveShapeType<S, A extends string>(
  shape: S,
  childAttr: A,
  mapper = <X extends PropTypes.Requireable<PropTypes.InferProps<S>>>(shape: X) => PropTypes.arrayOf(shape),
) {
  const children = mapper(PropTypes.shape(shape));
  (shape as any)[childAttr] = children;
  const tagPropTypes = PropTypes.shape(shape);
  return tagPropTypes as PropTypes.Requireable<PropTypes.InferProps<S & { [key in A]: typeof children }>>
}

function NullableType(propType: any) {
  return (props: any, propName: any, ...rest: any) => props[propName] === null ? null : propType(props, propName, ...rest);
}

const NullType = PropTypes.oneOf([null])

export const ExtraPropTypes = {
  null: NullType,
  nullable: NullableType,
  recursiveShape: RecursiveShapeType,
}
export const TextFieldPropTypes = createComponentPropTypes<TextFieldFeatureProps>({
  name: PropTypes.string,
  type: PropTypes.oneOf(['text', 'tel', 'url', 'email', 'password']),
  placeholder: PropTypes.string,
  loading: PropTypes.bool,
  disabled: PropTypes.bool,
  value: PropTypes.string,
  onChange: PropTypes.func,
})

TextField.propTypes = TextFieldPropTypes

顺便一说,现在做工具库一般有两种模式:

  • JavaScript 为主,补写裸的 TypeScript *.d.ts 文件用于描述工具库API的类型
  • TypeScript 为主,补充做一些运行时检查

大势所趋,同时我个人也认为,以 TypeScript 为主更好。从这几年的 TypeScript 开发经验来看,TypeScript 的确是对于开发规范齐整安全正确的项目有非常大的帮助。

createComponentPropTypes 这个工具也是在这样的思路下写出来的,只是对于 TypeScript 做一个补充,而且还利用 TypeScript 类型推断,确保开发者在新增 Props Interface 定义时,在编译器就告知开发者要补充 propTypes。