Я хочу динамически изменять значения в объекте:
interface myType {
id: number;
name: string;
}
const formFields: (keyof myType)[] = ['id', 'name']
let test: myType= {
id: 0,
name: `name`
}
let data: string[] = ['1', 'Name']
for (let i = 0; i < data.length; i++) {
const field = formFields[i];
if (typeof test[field] === 'number') {
test[field] = Number(data[i]);
} else if (typeof test[field] === 'string') {
test[field] = data[i];
}
}
но я не могу избежать этой ошибки:
Type 'number' is not assignable to type 'never'.(2322)
Хотя на реальную работу это не влияет.
(см. предыдущий комментарий). К сожалению, TS не может следовать логике test[field] === 'number'
как способу защиты test[field]
, особенно при написании. Если вы хотите помочь TS увидеть это, вам, вероятно, следует провести рефакторинг этой проверки в специальную функцию защиты типа, как показано в этой ссылке на игровую площадку. Соответствует ли это вашим потребностям? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?
@jcalz Спасибо, проблему решило, ошибок больше нет, но я не понимаю, почему во втором абзаце еще можно написать "} else if (typeof test[field] === 'string') { ", и ошибки нет, потому что в моем проекте есть много разных типов, а не только число и строка.
В примере есть только number
и string
, поэтому TS это знает. Если вы поместите больше вещей, вам нужно будет вызвать больше функций защиты типов. Я могу объединить эти несколько функций в одну, как показано в ссылке на игровую площадку. Должен ли я написать это в качестве ответа?
@jcalz Но это не выдает ошибку. Это потому, что это специальный тип?
Это потому, что Partial<Record<Status, boolean>>
— это слабый тип со всеми необязательными реквизитами, поэтому ему можно назначить {}
, а string
можно назначить {}
, как показано здесь. Я бы объяснил дальше, но вы уже приняли другой ответ (и я не уверен, что возможность изменения типа геттер/сеттер имеет отношение к вашему вопросу), поэтому я не склонен делать здесь что-то большее, если только вы не хочу увидеть, как я напишу ответ.
В свойствах JS/TS могут быть геттеры и сеттеры, и их типы могут различаться. Вы проверяете тип того, что возвращает геттер, но сеттер может принять другой тип. Посмотрите на этот пример:
const obj = {
val: "",
get prop(): string {
return this.val;
},
set prop(val: number) {
this.val = val.toString()
},
}
// The following assignment does not compile:
if (typeof obj.prop === "string") { // here prop looks like a string property
obj.prop = "str" // but here prop is already a number property
}
Этот пример должен прояснить, почему TS ведет себя таким образом.
Вопрос: Но почему тип свойства обозначается как never
?
О: Компилятор имеет некоторые сведения о типе геттера, но ничего о сеттере. Таким образом, он не может гарантировать типобезопасное назначение. Тип never
— это тот, которому вы никогда не сможете присвоить. Это предотвращает выполнение небезопасных назначений, но вы можете обойти это (см. ниже).
Ниже приведены два варианта решения проблемы.
Первый вариант сохраняет вашу логику и просто обходит ошибку TS:
(на самом деле, это небезопасно и может быть использовано неправильно)
interface type {
id: number;
name: string;
}
const formFields: (keyof type)[] = ['id', 'name']
let test: type = {
id: 0,
name: `name`
}
let data: string[] = ['1', 'Name']
for (let i = 0; i < data.length; i++) {
const field = formFields[i];
if (typeof test[field] === 'number') {
setTestField(field, Number(data[i]))
} else if (typeof test[field] === 'string') {
setTestField(field, data[i])
}
}
function setTestField<T extends keyof type>(key: T, value: type[T]): void {
test[key] = value
}
Или более короткий вариант этого варианта представлен здесь от @jcalz.
И второй вариант — это небольшое переписывание вашей логики:
interface type {
id: number;
name: string;
}
const formFields: (keyof type)[] = ['id', 'name']
let test: type = {
id: 0,
name: `name`
}
let data: string[] = ['1', 'Name']
for (let i = 0; i < data.length; i++) {
const field = formFields[i];
switch (field) {
case 'id':
test.id = Number(data[i]);
break;
case 'name':
test.name = data[i]
break;
}
}
Возможно, один из них подойдет для вашего случая.
К сожалению, ваш первый вариант — это причудливый способ эффективного выполнения утверждения типа; ничто не мешает вам ошибиться . TS не является полностью типобезопасным, так что это не ваша вина или что-то в этом роде, но это дает обманчивое ощущение безопасности. С таким же успехом вы можете просто написать это, потому что это помогает.
@jcalz да, полностью согласен относительно первого варианта. Это то, что я имел в виду, говоря, что это просто обходит ошибку TS. Но ваше разъяснение полезно! Спасибо за второй пример. Я не знал, что мы можем использовать as
в левой части задания. Позвольте мне обновить ответ, чтобы отразить это.
Спасибо. Итак, причина этой проблемы в том, что тип получателя и тип установки «test[field]» несовместимы, верно? Но почему принятым типом будет «никогда»? Я установил только число и строку.
Компилятор @ZVMAGL3 имеет некоторые знания о типе геттера, но ничего о сеттере. Таким образом, он не может гарантировать типобезопасное назначение. Тип never
— это тот, которому вы не можете назначить. Он не позволяет вам выполнять небезопасные назначения, но при желании вы можете его обойти.
Добро пожаловать в Stack Overflow! Пожалуйста, отредактируйте, чтобы изменить имя вашего интерфейса с
type
на что-то более традиционное, напримерMyType
. Имена интерфейсов обычно пишутся в UpperCamelCase, аtype
— это ключевое слово TS, которое, хотя и не запрещено быть именем типа, может сбить людей с толку.