我想用happyValidate
代替sadValidate
,但是我不能让我的泛型类型在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>);
一般可以这样输入:
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。键没有特别键入。
您可以将泛型应用于整个验证系统,如下面的游乐场链接所示。这将始终为您提供类型安全,而不需要强制类型转换或编译器断言。
要分解它,首先,对ValidationFn
和Validation
类型应用泛型,以便它们接受任何类型的值(但默认为字符串):
type ValidationFn<T> = (t: T) => boolean;
type Validation<T = string> = {
check: ValidationFn<T>,
message: string
}
然后定义一个基本的Values
类型:
type Values = Record<string, unknown>;
使用映射类型从Values
类型派生出Errors
和Config
类型:
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('') }));