Pull to refresh

Comments 11

Интересно было бы сравнить как это работает в других языках, например, будет ли отличаться поведение в Rescript

Передача объекта как аргумент функции с лишним свойством

Помоему это совсем не проблема, и оно так работает во всех ООП языках. Всегда можно использовать объект с типом потомка там, где используется тип базового класса.

То что в TS "наследование" может быть неявным - это уже отдельный разговор...

А то, что возникает ошибка когда вы передаете литеральный объект - ну там просто не происходит "наследования" и лишние проперти уже лишние.

А то, что возникает ошибка когда вы передаете литеральный объект - ну там просто не происходит "наследования" и лишние проперти уже лишние.

Скорее всего, это частный случай ради удобства. С точки зрения TS, лишние поля литерала всё равно недоступны "законными" способами (без приведения типа) и потому бесполезны, и лучше нарисовать подсказку, что их нет в типе.

Спасибо за дополнение, в данном случае возникает ошибка в литерале не потому что не происходит "наследования", а как написал@meonsou,это можно рассмотреть как правило линтера https://www.typescriptlang.org/tsconfig#suppressExcessPropertyErrors, которое можно отключить

Само поведение получается неочевидным, что с помощью объекта нет ошибки, с помощью литерала есть

Немного дополню.

Если же передать не содержащееся в enum значение в качестве аргумента, мы должны увидеть ошибку "Argument of type '-100' is not assignable to parameter of type 'LogLevel'". Но в typescript ниже версии 5.0 такой ошибки нет, хотя по логике она должна быть.

В 5.0 починили только самые очевидные случаи, когда литерал явно не подходит, но например такое всё ещё работает:

let level = -100
showMessage(level, 'debug message')

Не смотря на то, что number не совместим с LogLevel.

Передача объекта как аргумент функции с лишним свойством

Это нормальное поведение, оно соответствует структурной типизации. Тип задаёт не точную структуру данных, а минимально необходимую.

Что касается этих ошибок, это сделано больше как правило линтера. Когда напрямую пытаешься запихнуть объектный литерал с лишними свойствами ТС считает что это скорее всего баг и предупреждает пользователя. Это кстати можно отключить настройкой suppressExcessPropertyErrors в тсконфиге.

Потеря типизации при использовании Object.keys

Не указано почему так происходит - дело в том что здесь очень высока вероятность несовпадения типов с рантаймом. Например:

const c1: { a: number, b: number } = { a: 1, b: 2 }

const c2: { a: number } = c1

const c3: { a: number, c?: number } = c2

Получаем ключи ["a", "c"] в типах и ["a", "b"] в рантайме.

Спасибо большое за уточнения, очень ценно!

const newArr2 = arr.filter((item): item is number => item !== undefined);

Другой способ решения – использовать ts-reset. Тогда не придется вручную прописывать конструкцию из примера выше. 

Конструкцию из примера выше с ts-reset заменить невозможно. В библиотеке обрабатывается один конкретный кейс - с .filter(Boolean), т.е. в аргументе filter должно быть что-то с типом BooleanConstructor и только оно. Вариант с .filter((item) => item !== undefined) работать не будет. Ну а если числа фильтровать через Boolean все же - то оно отфильтрует кроме undefined еще и нули.

Спасибо! Не учли. Сейчас обновил этот кейс в статье :)

Вообще было бы классно, если TypeScript из коробки умел бы определять, что для простеньких функций типа лямбды из примера

const newArr2 = arr.filter(item => item !== undefined);

возвращаемый тип не просто boolean, а предикат типа. Ну а покуда этого нет, кажется что реализовать кастомный обобщенный тайпгвард более корректное решение по смыслу задачи, нежели использование Boolean c ts-reset. Иначе, как вы верно подметили, можно встретить нежелательное поведение с потерей ложноподобных значений типа нуля и пустой строки. Вот пример решения:

function isPresent<T>(arg: T): arg is Exclude<T, undefined | null> {
    return arg !== null && arg !== undefined;
}

// `filtered` типизирован как `number[]`
const filtered = [0, 1, 2, undefined, null].filter(isPresent)

Или вот пример немного другой реализации из библиотеки ts-is-present

export function isPresent<T>(t: T | undefined | null | void): t is T {
  return t !== undefined && t !== null;
}

Sign up to leave a comment.