#javascript #typescript
#javascript #typescript
Вопрос:
У меня есть поля формы, представленные в виде объектов, которые отображаются поверх и на основе type
возвращаемых элементов React:
import { FieldProps } from "~types";
const fields: FieldProps[] = [
{ type: "text", name: "username", value: "", required: true, ...other props },
{ type: "password", name: "password", value: "", required: true, ...other props },
...etc
]
export default fields;
Проблема, с которой я сталкиваюсь, заключается в том, что я пытаюсь проверить поля после отправки формы и проверить наличие ошибок в value
:
handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const { validatedFields, errors } = fieldValidator(this.state.fields);
if(errors) {
this.setState({ errors });
return;
}
...etc
}
но эта повторно используемая функция проверки имеет ошибку ts:
import isEmpty from "lodash.isempty";
/**
* Helper function to validate form fields.
*
* @function
* @param {array} fields - an array containing form fields.
* @returns {object} validated/updated fields and number of errors.
* @throws {error}
*/
const fieldValidator = <
T extends Array<{ type: string; value: string; required?: boolean }>
>(
fields: T,
): { validatedFields: T; errors: number } => {
try {
if (isEmpty(fields)) throw new Error("You must supply an array of form fields to validate!");
let errorCount: number = 0;
// this turns the "validatedFields" array into an { errors: string; type: string; name:
// string; value: string; required?: boolean | undefined;}[] type, when it needs to be "T",
// but "T" won't be inferred as an Array with Object props: type, value, required, value defined within it
const validatedFields = fields.map(field => {
let errors = "";
const { type, value, required } = field;
if ((!value amp;amp; required) || (isEmpty(value) amp;amp; required)) {
errors = "Required.";
} else if (
type === "email" amp;amp;
!/^[A-Z0-9._% -] @[A-Z0-9.-] .[A-Z]{2,4}$/i.test(field.value)
) {
errors = "Invalid email.";
}
if (errors) errorCount = 1;
return { ...field, errors };
});
return { validatedFields, errors: errorCount }; // TS error here
} catch (err) {
throw String(err);
}
};
export default fieldValidator;
Поскольку validatedFields
превращается в:
{
errors: string;
name: string;
value: string;
type: string;
required: boolean;
}[]
и он возвращается { validatedFields, errors }
, он выдает эту ошибку TS:
Type '{ errors: string; type: string; value: string; required?: boolean | undefined; }[]' is not assignable to type 'T'.
'{ errors: string; type: string; value: string; required?: boolean | undefined; }[]' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ type: string; value: string; required?: boolean | undefined; }[]'.ts(2322)
index.ts(17, 6): The expected type comes from property 'validatedFields' which is declared here on type '{ validatedFields: T; errors: number; }'
Есть ли способ вывести T
как массив объектов, который ожидает по крайней мере 4 (или более) свойства, но возвращает тот же типизированный массив (только с обновленным errors
свойством)?
Ответ №1:
Это то, что вы ищете?
type EssentialField = { type: string; value: string; required?: boolean }[];
В Typescript используется утиный ввод.
type FieldProps = {
className?: string,
disabled?: boolean,
errors?: string,
placeholder?: string,
label: string,
// onChange?: (e: React.ChangeEvent<any>) => void;
name: string,
type: string,
readOnly?: boolean,
required: boolean,
value: string
styles?: object
};
const fields: FieldProps[] = [
{
type: "text",
label: "Username",
name: "username",
errors: "",
value: "",
required: true,
},
{
className: "example",
type: "password",
label:"Passowrd",
name: "password",
errors: "",
value: "",
required: true,
styles: { width: "150px" },
}
];
type EssentialField = { type: string; value: string; required?: boolean }[];
const fieldValidator = (
fields: EssentialField,
): { validatedFields: EssentialField; errors: number } => {
try {
if (!fields || fields.length < 0) throw new Error("You must supply an array of fields!");
let errorCount: number = 0;
const validatedFields = fields.map(field => {
let errors = "";
const { type, value, required } = field;
if ((!value amp;amp; required) || ((value amp;amp; value.length < 0) amp;amp; required)) {
errors = "Required.";
} else if (
type === "email" amp;amp;
!/^[A-Z0-9._% -] @[A-Z0-9.-] .[A-Z]{2,4}$/i.test(field.value)
) {
errors = "Invalid email.";
}
if (errors) errorCount = 1;
return { ...field, errors };
});
return { validatedFields, errors: errorCount };
} catch (err) {
throw String(err);
}
};
const { validatedFields, errors } = fieldValidator(fields)
console.log(validatedFields)
console.log(errors);
Это устраняет ошибку без использования as any
или других трюков.
РЕДАКТИРОВАТЬ: Может быть, вы хотите что-то подобное:
type EssentialField = ({ type: string; value: string; required?: boolean }amp;{[key: string]: any})[];
// this allow you to do:
validatedFields[0].placeholder; // ...
// or
validatedFields[0].someUnknowProperty; // ...
Это пересечение типа с ассоциативным массивом ( {[key: string]: any})
). Обычно к нему обращаются подобным образом associativeArray['someKey']
, но его также можно использовать подобным образом associativeArray.someKey
.
Преимущество заключается в том, что ваша среда разработки должна предлагать вам поле type
и required
в автозаполнении.
Комментарии:
1. Это близко, но ни одно из приведенных выше решений не сохраняет другие типы, расположенные внутри
FieldProps[]
. Например,validatedFields[0].label
(который требуется, см. Typescript playground) не вводится и не предоставляется в автозаполнении после проверки.
Ответ №2:
Мне удалось выяснить это, расширив T
до any[]
, а затем определив результат validatedFields as T
, чтобы сохранить переданные типы:
const fieldValidator = <
T extends any[]
>(
fields: T,
): { validatedFields: T; errors: number } => {
try {
if (!fields || fields.length < 0) throw new Error("You must supply an array of fields!");
let errorCount: number = 0;
const validatedFields = fields.map(field => {
let errors = "";
const { type, value, required }: Pick<BaseFieldProps, "type" | "value" | "required"> = field;
if ((!value amp;amp; required) || ((value amp;amp; value.length < 0) amp;amp; required)) {
errors = "Required.";
} else if (
type === "email" amp;amp;
!/^[A-Z0-9._% -] @[A-Z0-9.-] .[A-Z]{2,4}$/i.test(value)
) {
errors = "Invalid email.";
}
if (errors) errorCount = 1;
return { ...field, errors };
}) as T;
return { validatedFields, errors: errorCount };
} catch (err) {
throw String(err);
}
};