При проверке обтекта на наличие в свойстве массива, выдает ошибку
Есть обьект, я пытаюсь его типизировать и потом отчистить, но при переборе его и попытке его отчистить type script в упор не хочет видеть отличие строки от массива.
interface CoosingRole {
name: string;
code: string;
}
interface Rating {
name: string;
value: number;
}
interface Form {
username: string
email: string
password: string
password2: string
file: string
coosing_role: CoosingRole[]
rating: Rating[]
}
const form = ref<Form>({
username: '',
email: '',
password: '',
password2: '',
file: '',
coosing_role: [{ name: 'USER', code: 'USER' }],
rating: [{ name: '1', value: 1 }]
})
function clearForm() {
Object.keys(form.value).forEach(key => {
const field = form.value[key as keyof Form];
if (Array.isArray(field)) {
// Если поле - массив, очищаем его
form.value[key as keyof Form] = [] as any;
} else {
// Если поле - строка, присваиваем пустую строку
form.value[key as keyof Form] = ''; // Тип "string" не может быть назначен для типа "{ name: string; code: string; }[]".
}
});
}
Ответы (1 шт):
На самом деле он ругался бы и на строку внутри if, но там богомерзкое as any..
TS никак не реагирует на косвенную проверку типа, потому что это для него пока сложно. Хотя Array.isArray - тайпгард, все равно тут получается слишком длинная цепочка рассуждений. В итоге внутри if и else тип для key так и остается строкой, которую ты потом приводишь к keyof Form.
Далее, есть разница между чтением const field = form.value[key as keyof Form] и записью form.value[key as keyof Form] = newValue. В первом случае имеем тип string | CoosingRole[] | Rating[] - мы ведь не знаем, какой там ключ. Во втором - newValue должен быть подходящим значением независимо от ключа, то есть должен быть string & CoosingRole[] & Rating[], разумеется ни строка, ни массив этому не соответствуют. Потому такое присвоение невозможно.
Вывод: надо написать тайпгард для ключа. Чтобы проверять, что ключ относится к тому набору ключей, по которым лежит значение заданного типа.
type FilterKeysByValue<O, V> = keyof {[K in keyof O as O[K] extends V ? K : never]: O[K];};
function isKeyForValue<T, O>(key: PropertyKey, obj: O, guard: (v: unknown) => v is T): key is FilterKeysByValue<O, T> {
return obj != null && guard(obj[key as keyof O]);
}
function clearForm() {
Object.keys(form.value).forEach(key => {
if (isKeyForValue(key, form.value, Array.isArray)) {
// Если поле - массив, очищаем его
form.value[key] = [];
}
if (isKeyForValue(key, form.value, (v) => typeof v === 'string')) {
// Если поле - строка, присваиваем пустую строку
form.value[key] = '';
}
});
}
Здесь FilterKeysByValue<O, V> - генерик, который для типа O возвращает набор тех ключей, по которым значение соответствует типу V
isKeyForValue - тот самый тайпгард для проверки ключа, ему дополнительно передаем объект и тайпгард проверки значения.
Если у тебя версия TS меньше 5.5, то строку (v) => typeof v === 'string' замени на (v): v is string => typeof v === 'string'