如何使用 Typescript 泛型将参数与我的函数正确关联?

问题描述 投票:0回答:2

我想用

sadValidate
替换
happyValidate
但我无法让我的通用类型在
makeValidate
上运行。

type ValidationFn = (s: string) => boolean;

interface Validation {
  check: ValidationFn;
  message: string;
}

const validIdentityNameCharacters = /^[a-zA-Z0-9_.-\s]*$/;
const hasSpecialCharacters: Validation = {
  check: s => !validIdentityNameCharacters.test(s),
  message: 'Must only include letters, numbers and the following special characters: (_ -.)',
};

const LIMIT = 255;
const isLengthTooLong: Validation = {
  check: s => s.length >= LIMIT,
  message: `Must be less than ${LIMIT} characters`,
};

const isBlank: Validation = { check: s => s === '', message: 'Must not be blank' };

interface Values {
  name: string;
  tags: string;
}

const sadValidate = (values: Values): Partial<Values> => {
  const errors: Partial<Values> = {};
  const { name, tags } = values;

  if (hasSpecialCharacters.check(name)) {
    errors.name = hasSpecialCharacters.message;
  } else if (isLengthTooLong.check(name)) {
    errors.name = isLengthTooLong.message;
  } else if (isBlank.check(name)) {
    errors.name = isBlank.message;
  }

  if (isLengthTooLong.check(tags)) {
    errors.tags = isLengthTooLong.message;
  }

  return errors;
};

const makeValidate = config => values =>
  Object.keys(config).reduce((acc, fieldName) => {
    const validators = config[fieldName];
    const problem = validators.find(({ check }) => check(values[fieldName]));
    if (problem) {
      acc[fieldName] = problem.message;
    }
    return acc;
  }, {});

const happyValidate = makeValidate({
  name: [hasSpecialCharacters, isLengthTooLong, isBlank],
  tags: [isLengthTooLong],
});

test('works', () => {
  expect(happyValidate({ name: '', tags: new Array(260).fill('a').join('') })).toEqual({
    name: isBlank.message,
    tags: isLengthTooLong.message,
  });
});

https://codesandbox.io/s/eloquent-agnesi-t71jiq?file=/src/index.spec.ts

这行得通,但是很多

as
...

export const makeValidate =
  <V>(config: Record<keyof V, Validation[]>) =>
  (values: V): Record<keyof V, string> =>
    Object.keys(config).reduce<Record<keyof V, string>>((acc, fieldName) => {
      const validators = config[fieldName as keyof V];
      const problem = validators.find(({ check }) => check(values[fieldName as keyof V] as string));
      if (problem) {
        acc[fieldName as keyof V] = problem.message;
      }
      return acc;
    }, {} as Record<keyof V, string>);
typescript typescript-generics formik
2个回答
0
投票

一般可以这样输入:

type ValidationConfig<K extends string> = Record<K, Array<Validation>>
type ValidationValues<K extends string> = Record<K, string>
type ValidationResult<K extends string> = Record<K, string>

const makeValidate = 
    <K extends string>(config: ValidationConfig<K>) => 
    (values: ValidationValues<K>) => {
        const keys = Object.keys(config) as Array<K> // required because Object.keys returns string[]
        return keys.reduce((acc, fieldName) => {
            const validators = config[fieldName];
            const problem = validators.find(({ check }) => check(values[fieldName]));
            if (problem) {
                acc[fieldName] = problem.message;
            }
            return acc;
        }, {} as ValidationResult<K>); // required because we can't add fields to an empty object {}
    }

请注意,您必须使用一些强制转换来解决 Object.keys 的类型不是很明确的问题。


0
投票

您可以将泛型应用于整个验证系统,如这个游乐场链接所示。这将始终为您提供类型安全,而无需强制转换或编译器断言。

要分解它,首先,将泛型应用于您的

ValidationFn
Validation
类型,以便它们接受任何类型的值(但默认为字符串):

type ValidationFn<T> = (t: T) => boolean;

type Validation<T = string> = {
   check: ValidationFn<T>,
   message: string
}

然后定义一个基

Values
类型:

type Values = Record<string, unknown>;

使用映射类型

Errors
类型派生出
Config
Values
类型:

type Errors<V extends Values> = { [K in keyof V]?: string };

type Config<V extends Values> = { [K in keyof V]: Validation<V[K]>[] };

按原样重用现有的验证方法:

const validIdentityNameCharacters = /^[a-zA-Z0-9_.-\s]*$/;
const hasSpecialCharacters: Validation = {
  check: s => !validIdentityNameCharacters.test(s),
  message: 'Must only include letters, numbers and the following special characters: (_ -.)',
};

const LIMIT = 255;
const isLengthTooLong: Validation = {
  check: s => s.length >= LIMIT,
  message: `Must be less than ${LIMIT} characters`,
};

const isBlank: Validation = { check: s => s === '', message: 'Must not be blank' };

对新的makeValidate()函数使用

通用约束

const makeValidate = <V extends Values>(config: Config<V>) => (values: V): Errors<V> => {
   const errors: Errors<V> = {}

   for (let key in values) {
      for (let validation of config[key]) {
         if (validation.check(values[key])) {
            errors[key] = validation.message;
            break;
         }
      }
   }

   return errors;
};

happyValidate()
功能重用您现有的模式:

const happyValidate = makeValidate({
  name: [hasSpecialCharacters, isLengthTooLong, isBlank],
  tags: [isLengthTooLong],
});

并测试它是否一切正常:

console.log(happyValidate({ name: '', tags: new Array(260).fill('a').join('') }));
© www.soinside.com 2019 - 2024. All rights reserved.