Сужение не выполняется для назначений с: Тип не может быть назначен на «никогда»

Я хочу динамически изменять значения в объекте:

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)

Хотя на реальную работу это не влияет.

Добро пожаловать в Stack Overflow! Пожалуйста, отредактируйте, чтобы изменить имя вашего интерфейса с type на что-то более традиционное, например MyType. Имена интерфейсов обычно пишутся в UpperCamelCase, а type — это ключевое слово TS, которое, хотя и не запрещено быть именем типа, может сбить людей с толку.

jcalz 05.08.2024 15:59

(см. предыдущий комментарий). К сожалению, TS не может следовать логике test[field] === 'number' как способу защиты test[field], особенно при написании. Если вы хотите помочь TS увидеть это, вам, вероятно, следует провести рефакторинг этой проверки в специальную функцию защиты типа, как показано в этой ссылке на игровую площадку. Соответствует ли это вашим потребностям? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?

jcalz 05.08.2024 16:08

@jcalz Спасибо, проблему решило, ошибок больше нет, но я не понимаю, почему во втором абзаце еще можно написать "} else if (typeof test[field] === 'string') { ", и ошибки нет, потому что в моем проекте есть много разных типов, а не только число и строка.

ZVMAGL3 06.08.2024 04:33

В примере есть только number и string, поэтому TS это знает. Если вы поместите больше вещей, вам нужно будет вызвать больше функций защиты типов. Я могу объединить эти несколько функций в одну, как показано в ссылке на игровую площадку. Должен ли я написать это в качестве ответа?

jcalz 06.08.2024 05:33

@jcalz Но это не выдает ошибку. Это потому, что это специальный тип?

ZVMAGL3 06.08.2024 07:50

Это потому, что Partial<Record<Status, boolean>> — это слабый тип со всеми необязательными реквизитами, поэтому ему можно назначить {}, а string можно назначить {}, как показано здесь. Я бы объяснил дальше, но вы уже приняли другой ответ (и я не уверен, что возможность изменения типа геттер/сеттер имеет отношение к вашему вопросу), поэтому я не склонен делать здесь что-то большее, если только вы не хочу увидеть, как я напишу ответ.

jcalz 06.08.2024 15:06
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
6
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В свойствах 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 05.08.2024 16:04

@jcalz да, полностью согласен относительно первого варианта. Это то, что я имел в виду, говоря, что это просто обходит ошибку TS. Но ваше разъяснение полезно! Спасибо за второй пример. Я не знал, что мы можем использовать as в левой части задания. Позвольте мне обновить ответ, чтобы отразить это.

victordobry 05.08.2024 17:43

Спасибо. Итак, причина этой проблемы в том, что тип получателя и тип установки «test[field]» несовместимы, верно? Но почему принятым типом будет «никогда»? Я установил только число и строку.

ZVMAGL3 06.08.2024 03:57

Компилятор @ZVMAGL3 имеет некоторые знания о типе геттера, но ничего о сеттере. Таким образом, он не может гарантировать типобезопасное назначение. Тип never — это тот, которому вы не можете назначить. Он не позволяет вам выполнять небезопасные назначения, но при желании вы можете его обойти.

victordobry 06.08.2024 05:31

Другие вопросы по теме