Может ли TypeScript уточнять тип в соответствии с условием if...else?
Есть код, в котором при нажатии на название столбца, массив сортируется именно в соответствии со значениями этого столбца. В нем нужно типизировать аргументы функции для сортировки (в коде a и b).
if (sortState.column === "year") {
if (sortState.type === "asc") {
return [...albums].sort((a: IAlbum, b: IAlbum) => a[sortState.column] - b[sortState.column]);
}
if (sortState.type === "desc") {
return [...albums].sort((a: IAlbum, b: IAlbum) => b[sortState.column] - a[sortState.column]);
}
return albums;
} ...
Элемент массива имеет интерфейс IAlbum, соответственно ключом такого элемента может быть keyof IAlbum. Состояние sortState.column я типизировал как keyof IAlbum | null, потому что состояние null нужно, когда сортировка должна прийти в первоначальный вид.
Но при этом есть условие, в котором проверяется, не имеет ли этот ключ значение null (sortState.column === "year") и по идее в блоке с истинным условием ключ не может иметь значение null. Но в коде выше TypeScript из-за этого ругается:
Тип "null" невозможно использовать как тип индекса.
Хотя в этом случае значения null там никак не может быть. Как это можно исправить? Может ли TypeScript как-то динамически обновлять тип?
Ответы (1 шт):
Но в коде выше TypeScript из-за этого ругается:
Typescript не такой умный, как хотелось бы. Когда он видит функцию, которая передается в качестве callback-а, он не может определить, как в дальнейшем эта функция будет использована (выполнена сразу либо, например, сохранена в какую-либо переменную для отложенного вызова), а значит и не может гарантировать, что значение поля переменной sortState.column не будет переопределено на что-то "неожидаемое" до такого "отложенного" вызова. Для решения такого конфуза в вашем случае достаточно локализовать тип в примитив, который не будет ссылаться ни на что за пределами своей области видимости и соответствующей ей зоны ответственности тайп-гуарда:
if (sortState.column === "year" && typeof sortState.column === 'string') {
const col = sortState.column
if (sortState.type === "asc" ) {
return [...albums].sort((a: IAlbum, b: IAlbum) => a[col] - b[col]);
}
if (sortState.type === "desc") {
return [...albums].sort((a: IAlbum, b: IAlbum) => b[col] - a[col]);
}
return albums;
}
Почему typescript ругается на поле в каллбэке
Для наглядной демонстрации причины, почему typescript ругался на поле в калбэке, рассмотрим следующий пример:
function g(a: {a: unknown}){
let ar = []
if (typeof a.a == 'string'){
const aa = a.a
ar.push(() => console.log(typeof aa)) // string
ar.push(() => console.log(typeof a.a)) // number
}
return ar
}
let r: {a: unknown} = {a: ''}
const ar = g(r)
r.a = 5;
ar[0]()
В примере выше мы использовали type-guard для поля a (сузили его до string) - и, казалось бы, мы должны быть уверены, что в обоих случаях вывода в консоль мы получим string, однако запустив код в браузере (после компиляции), в консоли увидим string number.