Почему TypeScript не выводит правильный тип при повторном объявлении переменной

#typescript

#typescript

Вопрос:

Я писал код, который выглядит примерно так:

 function helloWorld(customName: string) {
  return `Hello, ${customName}`;
}

const myName: string | null = null;
const isValidName = myName !== null;

if (isValidName) {
  console.log(helloWorld(myName));
}
  

Если вы запустите этот код на игровой площадке TypeScript, вы увидите, что TypeScript пожалуется, что Argument of type 'null' is not assignable to parameter of type 'string'.

Но как это происходит? Этот код выполняется только тогда, когда isValidName он соответствует действительности, и isValidName может быть, только true если myName это не null так. Следовательно, myName string здесь. Почему это не работает?

Ответ №1:

Насколько я понимаю, тот факт, что вы сохраняете результат myName !== null в переменной isValidName , заставляет TypeScript проверять это значение во время выполнения и вести себя соответствующим образом (поэтому вызов helloWorld(myName) кажется незаконным, потому isValidName что потенциально может быть либо true или false во время выполнения).

Однако, если вы измените проверку на

 if (myName !== null) {
  console.log(helloWorld(myName));
}
  

TypeScript сможет определить, что единственным возможным типом myName может быть a string в этих обстоятельствах.

Ответ №2:

То, как вы это написали, typescript видит isValidName boolean , что это константа, и все.

Вы, автор кода, знаете, что значение isValidName указывает что-то о значении myName , но typescript не учитывает взаимосвязь между двумя переменными.

Вы хотите, чтобы в вашем if заявлении использовалась защита типа. По сути, защита типа — это логическое значение, вычисляемое во время выполнения, которое зависит от значения myName и является ли логическим true или false предоставляет некоторую информацию, которая сужает определение типа myName .

Как указал @falinsky, myName !== null он подходит для защиты типа, если вы используете его внутри условия вашего if оператора. Он говорит: «если это true так, то myName это не null так».

Вы также можете создать isValidName функцию as, которая проверяет имя и определяет, что это строка.

 const isValidName = (name: string | null): name is string => {
  return name !== null;
}

if (isValidName(myName)) {
  console.log(helloWorld(myName));
}
  

Ответ №3:

Если вы хотите сохранить результат сужения isValidName для дальнейшего использования и не хотите его повторно вычислять, вы можете создать служебную функцию, подобную этой

 /**
 * @param a The precomputed result
 * @param _b The variable to be narrowed down
 * @template T The type to which `_b` will be narrowed down to when `a` is true
 */
function typeCheck<T>(a: boolean, _b: any): _b is T {
  return a
}
  

Используйте так

 if (typeCheck<string>(isValidName, myName)) {
  console.log(helloWorld(myName)); // string here
} else {
  console.log(myName) // null here
}
  

Игровая площадка

Это может быть полезно при вычислении длинных выражений, таких как

 if (
    isLoneAvailableCapacity(feed) amp;amp;
    (isAvailableCapacitySubbeamPath(link) || isAvailableCapacityConnectivityLegPath(link)) amp;amp;
    toAvailableCapacityKey(feed.availableCapacity) === link.availableCapacityKey
) {
    // do something
}

// snip....
// some more code

if (
    isLoneAvailableCapacity(feed) amp;amp;
    (isAvailableCapacitySubbeamPath(link) || isAvailableCapacityConnectivityLegPath(link)) amp;amp;
    toAvailableCapacityKey(feed.availableCapacity) === link.availableCapacityKey
) {
    // do something else
}
  

Его можно легко заменить

 const isValidFeed = isLoneAvailableCapacity(feed) amp;amp;
    (isAvailableCapacitySubbeamPath(link) || isAvailableCapacityConnectivityLegPath(link)) amp;amp;
    toAvailableCapacityKey(feed.availableCapacity) === link.availableCapacityKey

if(typeCheck<Feed>(isValidFeed, feed)) {
  // do something
}

// snip....
// some more code

if(typeCheck<Feed>(isValidFeed, feed)) {
  // do something else
}
  

Ответ №4:

Другие ответы хорошо объясняют обходные пути, но не объясняют почему.

Простой факт заключается в том, что Typescript еще недостаточно сложен, чтобы понимать несколько зависимых переменных.

Очень возможно, что в будущем будут добавлены функции для этого. Нет причин, по которым это не могло бы сработать, но я полагаю, что это просто сложно.

Я надеюсь, что однажды это сработает, потому что это также упрощает некоторые вещи, которые я делаю.