#typescript #deno
#typescript #deno
Вопрос:
Я пытаюсь понять правила автоматического ввода. В этом примере я
- Есть функция с необязательным параметром.
- Проверьте, не определено ли оно, и если да, введите новое значение.
- Используйте его.
- И, наконец, верните его.
Шаги 1, 2 и 4 работают так, как ожидалось. На шаге 4 typescript четко знает, что параметр «map» не может быть неопределенным. Но на шаге 3 я должен явно добавить! или я получу сообщение об ошибке.
Код работает (теперь, когда я добавил восклицание / утверждение), но это не имеет смысла. Это правильное поведение TypeScript? Я делаю что-то не так в своем коде? 3 и 4 ссылаются на одну и ту же переменную, а 2 было выполнено до любого из них, поэтому я не вижу разницы.
function parseUrlArgs(inputString: string, map?: Map<string, string>) : Map<string, string> {
if (!map) {
map = new Map();
}
//map = map??new Map(); // This has the exact same effect as the if statement, above.
// Note: JavaScript's string split would not work the same way. If there are more than two equals signs, String.split() would ignore the second one and everything after it. We are using the more common interpretation that the second equals is part of the value and someone was too lazy to quote it.
const re = /(^[^=] )=(.*$)/;
// Note: trim() is important on windows. I think I was getting a r at the end of my lines and r does not match ".".
inputString.trim().split("amp;").forEach((kvp) => {
const result = re.exec(kvp);
if (result) {
const key = decodeURIComponent(result[1]);
const value = decodeURIComponent(result[2]);
map!.set(key, value); // Why do I need this exclamation mark?
}
});
return map;
}
Я не менял никаких настроек TypeScript. Я использую настройки по умолчанию, встроенные в Deno, перечисленные здесь . Я получил аналогичные результаты на игровой площадке typescript.
Ответ №1:
Проблема здесь в ограничении в TypeScript: [анализ потока управления] (https://www.typescriptlang.org/docs/handbook/2/narrowing.html#control-flow-analysis не распространяется ни на границы области действия функции, ни за ее пределы. Подробности и обсуждение см. в microsoft / TypeScript #9998 . Существует также более конкретная проблема, microsoft / TypeScript # 11498, которая предполагает возможность «встроенного» анализа потока управления для определенных типов обратного вызова.
Компилятор анализирует блок кода if (!map) { map = new Map(); }
и успешно понимает, что после этого блока map
определенно нет undefined
, как вы можете продемонстрировать, попытавшись использовать методы map
до и после этого блока кода:
map.has(""); // error
if (!map) {
map = new Map();
}
map.has(""); // okay
Все идет хорошо, пока вы не попадаете внутрь тела функции обратного вызова, пересекая границу области действия функции:
[1, 2, 3].forEach(() => map.has("")); // error, map might be undefined
Компилятор действительно не знает, когда и будет ли вызван этот обратный вызов. Вы знаете, что массив forEach()
выполняет обратный вызов синхронно один раз для каждого элемента в массиве. Но компилятор не знает этого или даже не знает, как представить это в системе типов (без реализации какого-либо способа отслеживания того, что функции делают со своими обратными вызовами, как предложено в microsoft / TypeScript # 11498 .)
Представьте, что вы увидели функцию foobar(() => map.has(""))
. Знаете ли вы, когда или если этот обратный вызов будет вызван, не найдя реализацию foobar()
и не изучив ее? Это то, о чем думает компилятор forEach()
.
Компилятор считает возможным, что обратный вызов будет вызван в какой-то момент, когда его предыдущий анализ потока управления больше не применяется. «Возможно map
, устанавливается undefined
в какой-то другой более поздней части внешней функции», и поэтому он сдается и рассматривает map
как возможный undefined
. Опять же, вы знаете, что это не так, поскольку map
выходит за рамки, даже не delete
будучи улучшенным или не map = undefined
выполнив его. Но компилятор не тратит циклов, необходимых для выяснения этого. Отказ — это компромисс, когда производительность ценится выше полноты.
Становится еще хуже, когда вы понимаете, что компилятор просто предполагает, что закрытое значение не будет изменено внутри функции обратного вызова. Точно так же, как анализ потока управления из внешней области не распространяется внутрь, анализ потока управления из внутренней области не распространяется наружу:
[4, 5, 6].forEach(() => map = undefined);
return map; // no error?!
В приведенном выше коде map
это определенно будет undefined
, когда вы дойдете return map
, но компилятор разрешает это без предупреждения. Почему? Опять же, компилятор понятия не имеет, что обратный вызов когда-либо будет вызван или когда. Было бы безопаснее просто выбросить все результаты анализа потока управления после определения или вызова замыкания, но это сделало бы анализ потока управления практически бесполезным. Попытка встроить обратный вызов потребует понимания того forEach()
, чем он отличается от foobar()
и требует много работы и, вероятно, приведет к гораздо более медленному компилятору. Притворство, что обратные вызовы не влияют на анализ потока управления, является компромиссом, когда производительность и удобство ценятся выше надежности.
Итак, что можно сделать? Одна простая вещь — присвоить ваше значение const
переменной в области, где выполняется анализ потока управления. Компилятор знает, что const
переменная никогда не может быть переназначена, и он знает (ну, притворяется), что это означает, что тип переменной также никогда не изменится:
function parseUrlArgs(inputString: string, map?: Map<string, string>): Map<string, string> {
if (!map) {
map = new Map();
}
const resultMap = map; // <-- const assignment here
const re = /(^[^=] )=(.*$)/;
inputString.trim().split("amp;").forEach((kvp) => {
const result = re.exec(kvp);
if (result) {
const key = decodeURIComponent(result[1]);
const value = decodeURIComponent(result[2]);
resultMap.set(key, value); // <-- use const variable here
}
});
return resultMap; // <-- use const variable here
}
Копируя map
в resultMap
точку, где map
, как известно, определено, компилятор знает, что resultMap
это тип Map<string, string>
, а не undefined
нет . Этот тип сохраняется для остальной части функции, даже внутри обратных вызовов. Это может быть немного излишним, но компилятор может отслеживать его и относительно безопасен для типов.
Или вы могли бы продолжать использовать ненулевой оператор !
. Это зависит от вас.
Ответ №2:
Причина, по которой у typescript возникают проблемы, заключается в том, что вы присваиваете новую карту той же map
переменной. Он уже определил, что тип для map
is Map<string, string> | undefined
и этот тип сохраняется повсюду.
Простое решение — создать новую карту в сигнатуре функции, чтобы этого map
никогда не могло быть undefined
.
function parseUrlArgs(
inputString: string,
map: Map<string, string> = new Map()
): Map<string, string> {