Как определить predicate на методе класса?
Речь о typescript predicates и неверном поведении типов в некоторых встроенных объектах JS.
Если самостоятельно определить predicate, TypeScript "помнит" о каком типе идет речь и соответственно не вызывает ошибок ...
const isString = (s: unknown): s is string => typeof s === 'string'
const isBoolString = (s: unknown): boolean => typeof s === 'string'
function foo(something: unknown) {
if (isString(something)) something.replace('a', '')
if (isBoolString(something)) something.replace('a', '')
// "Правильная" ошибка ----- ^^^^^^^^^
}
... но вариант с методами экземпляров, например с Map<K, V>, определенный по умолчанию в "lib.es2015.collection.d.ts", не "помнит" что строкой выше мы уже удостоверились в наличии свойства и вызов Map.get() не должен возвращать undefined:
function bar<K, V>(key: K, map: Map<K, V>): V | 123 {
if (map.has(key)) {
return map.get(key) // V | undefined
// ^^^^^^^^^^^^^^^
}
return 123
}
Хотелось бы переопределить некоторые типы, т.е. должно получится что-то вроде такого:
interface Map<K, V> {
has(key: K): false | is V
}
И ваши варианты с методами кастомного класса...
Ответы (1 шт):
Такой метод определения predicate не желает работать с "переменной типа"
interface Map<K, V> {
// Извлекаем тип
has<T extends K>(key: T): this is (
// Извлекаем метод
Omit<Map<K, V>, 'get'> &
{ // Переопределяем на момент вызова "predicate | has"
get<R extends K>(k: R):
// При вызове get(), проверяем ранее извлеченный тип T
R extends T ? V : (V | undefined)
}
)
get(key: K): V | undefined
}
Вариант ниже работает
const map = new Map<string, number>()
const v1 = map.get('key') // number | undefined
if (map.has('key')) {
// Оба варианта работают как и ожидается
const v2 = map.get('key') // number
const v3 = map.get('error') // number | undefined
}
... но если тип заранее неизвестен, опять ошибка
function foo<K, V>(key: K, m: Map<K, V>): V | 123 {
if (m.has(key)) {
return m.get(key)
// error ^^^^^^^^
}
return 123
}