#javascript #typescript #ecmascript-6 #arrow-functions
Вопрос:
TLDR; Проверка переменной перед ее использованием в анонимной функции по-прежнему предупреждает, что переменная, возможно, не определена
В приведенном ниже примере кода переменная baseDirId
проверяется, если она не определена, а затем передается в функцию array.map, но предупреждения TS baseDirId
могут быть неопределенными.
// Ссылка на игровую площадку для машинописи
const rstr = async (a: string) => {
return a "bleh"
}
const args: { baseDirId: string | undefined } = {
baseDirId: "someid"
// baseDirId: undefined
}
const someArr = ["bleh1", "bleh2"]
const func1 = async (): Promise<void> => {
try {
// Assume baseDirId can be undefined
let baseDirId = args.baseDirId
// Trigger if baseDirId is undefined
if (!baseDirId) {
const baseDirObj = { id: "valid string" }
baseDirId = baseDirObj.id
}
console.log(typeof baseDirId)
// baseDirId cant be anything other than a string
if (typeof baseDirId !== "string") {
return Promise.reject("Base Dir response invalid")
}
// Why is baseDirId `string | undefined` inside rstr call below even after above checks
const bleharr = someArr.map((val) => rstr(baseDirId))
console.log(await Promise.all(bleharr))
} catch (err) {
console.error(err)
}
}
func1().catch(err => console.error(err))
Есть ли какой-нибудь возможный случай, когда baseDirId
это может быть undefined
?
Почему TS не позволяет этого ? Лучший способ сделать это ?
Комментарии:
1. проблема в том, что типы конфликтуют, даже если вы установили проверки, чтобы убедиться, что baseDirId является строкой, но это гарантирует, что его значение является строковым, то есть определенный тип по-прежнему является строкой или неопределенным. Вы можете использовать что-то вроде
baseDirId as string
явного приведения его к строке, это возможно, потому что вы уже убедились, что это будет строка в приведенном выше коде и не должно ничего нарушать, иначе измените типrstr
параметра функции, чтобы он соответствовалbaseDirId
2. Я бы предпочел явно не вводить приведение
baseDirId as string
. Также в коде я удостоверяюсь, что var отсутствуетundefined
(строка 18), затем проверяю единственный другой возможный типstring
(строка 25). Таким образом, я не понимаю, что вы имели в виду под «но это гарантирует, что его значение является строковым, то есть определенный тип по-прежнему является либо строкой, либо неопределенным».3. Я предполагаю, что это из-за
setTimeout(() => console.log(baseDirId), 1000); setTimeout(() => baseDirId = undefined, 500);
. Или другими словами: обратный вызов может быть вызван в более поздний момент времени, и тогда значение может быть неопределенным.
Ответ №1:
Давайте немного изменим код на
return () => someArr.map((val) => rstr(baseDirId))
поэтому вместо .map
прямого вызова он может быть запущен в более поздний момент времени. За это время мог быть написан какой-то другой код baseDirId
, в который не определено. Поэтому, чтобы правильно определить тип, Typescript должен был бы проверить, что никакой другой код не переопределяет переменную. Это довольно сложная задача (в некоторых случаях это может быть даже невозможно). Кроме того, это становится еще более сложным, если наша внутренняя функция вызывалась в нескольких местах:
let mutable: string | undefined = /*...*/;
const inner = () => fn(mutable); // << how to infer this?
mightCall(inner); // if called back here, mutable would be "string | undefined"
if(typeof mutable === "string") mightCall(inner); // if called here, mutable could be narrowed down to "string", but only if mightCall calls back synchronously
mutable = undefined; // if mightCall calls back asynchronously, 'mutable' would have to be inferred as undefined
Поэтому, когда функции обращаются к переменным из внешней области, компилятор Typescript принимает максимально широкий тип. Сужение типа работает только для самого тела функции. Чтобы сузить тип, вам потребуется либо утверждение типа, либо альтернативно скопировать значение в const
:
let mutable: string | undefined = /*...*/;
if(typeof mutable !== "string") return;
// mutable get's narrowed down to string here due to the typeof type guard
const immutable = mutable;
// ^ inferred as string, this is the widest possible type
Это также работает в вашем случае.
Ответ №2:
TypeScript не проверяет типы и даже не изменяет типы во время выполнения, поэтому baseDirId
тип всегда будет string | undefined
таким, если вы не сделаете узкие типы или что-то еще для типа, поэтому вы можете попробовать множество вариантов.
1. Используйте значение по умолчанию
let baseDirId = args.baseDirId || "valid string"
2. Выполните проверку условного значения
if(args.baseDirId){
let baseDirId = args.baseDirId
// ...
// do something you want
}
Но вы не можете сделать это непосредственно в следующем фрагменте, так как вы раньше let
объявляли baseDirId
, и тогда он не будет работать из-за того, что его можно изменить undefined
в любое время, если он не объявлен через const
if(baseDirId){
const bleharr = someArr.map((val) => rstr(baseDirId))
}
3. Используйте !
оператор ненулевого утверждения
Когда вы уверены, что это должно быть потрясающе, и вы не хотите ничего менять
const bleharr = someArr.map((val) => rstr(baseDirId!))
Комментарии:
1. Можно ли будет сузить тип baseDirId до
string
?2. @Solaris Конечно, вы можете это сделать, но так как вы раньше
let
объявлялиbaseDirId
, даже если вы используете проверку условного значения, похоже, это не сработает из-за того, что его можно изменитьundefined
в любое время