Typescript: Предложение: возможность включения неопределенного в подписи индекса

Созданный на 31 янв. 2017  ·  242Комментарии  ·  Источник: microsoft/TypeScript

Обновление : мое последнее предложение см. В комментарии https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

При включенном strictNullChecks TypeScript не включает undefined в подписи индекса (например, для объекта или массива). Это хорошо известное предостережение, которое обсуждается в нескольких вопросах, а именно https://github.com/Microsoft/TypeScript/issues/9235 , https://github.com/Microsoft/TypeScript/issues/13161 , . .com / Microsoft / TypeScript / issues / 12287 и https://github.com/Microsoft/TypeScript/pull/7140#issuecomment -192606629.

Пример:

const xs: number[] = [1,2,3];
xs[100] // number, even with strictNullChecks

Однако из чтения вышеупомянутых проблем видно, что многие пользователи TypeScript хотели бы, чтобы это было не так. Конечно, если подписи индекса действительно включают undefined , код, вероятно, потребует гораздо большей защиты, но - для некоторых - это приемлемый компромисс для повышения безопасности типов.

Пример подписей индекса, включая undefined :

const xs: number[] = [1,2,3];
xs[100] // number | undefined

Я хотел бы знать, можно ли рассматривать это поведение как дополнительную опцию компилятора поверх strictNullChecks . Таким образом, мы можем удовлетворить все группы пользователей: тех, кто хочет строгих проверок на null с или без неопределенности в своих индексных подписях.

Committed Suggestion

Самый полезный комментарий

Мы по-прежнему весьма скептически относимся к тому, что кто-то получит какую-либо пользу от этого флага на практике. Карты и подобные карты уже могут выбрать | undefined на своих сайтах.

Разве одна из целей TypeScript не заключается в том, чтобы позволить обнаруживать ошибки во время «компиляции», а не полагаться на то, что пользователь помнит / знает, что делает что-то конкретное? Похоже, это идет вразрез с этой целью; требуя от пользователя что-то сделать, чтобы избежать сбоев. То же самое можно сказать и о многих других функциях; они не нужны, если разработчик всегда делает x. Цель TypeScript (предположительно) - упростить работу и устранить эти вещи.

Я столкнулся с этой ошибкой, потому что я включал strictNullChecks в существующем коде, и у меня уже было сравнение, поэтому я получил ошибку. Если бы я писал совершенно новый код, я, вероятно, не осознал бы проблему здесь (система типов сообщала мне, что я всегда получаю значение) и закончил бы с ошибкой во время выполнения. Полагаясь на разработчиков TS, которые помнят (или, что еще хуже, даже знают), что они должны объявлять все свои карты с помощью | undefined кажется, что TypeScript не выполняет то, что люди на самом деле хотят.

Все 242 Комментарий

За исключением strictNullChecks, у нас нет флагов, изменяющих поведение системы типов. флаги обычно включают / выключают отчеты об ошибках.
у вас всегда может быть собственная версия библиотеки, которая определяет все индексаторы с помощью | undefined . должен работать как положено.

@mhegazy Интересная идея. Любое руководство о том, как переопределить сигнатуры типа для массива / объекта?

В lib.d.ts есть interface Array<T> lib.d.ts . Я искал по регулярному выражению \[\w+: (string|number)\] чтобы найти и другие подписи индексации.

Интересно, поэтому я попробовал это:

{
    // https://github.com/Microsoft/TypeScript/blob/1f92bacdc81e7ae6706ad8776121e1db986a8b27/lib/lib.d.ts#L1300
    declare global {
        interface Array<T> {
            [n: number]: T | undefined;
        }
    }

    const xs = [1,2,3]
    const x = xs[100]
    x // still number :-(
}

Любые идеи?

скопируйте lib.d.ts локально, скажем lib.strict.d.ts , измените подпись индекса на [n: number]: T | undefined; , включите файл в вашу компиляцию. вы должны увидеть желаемый эффект.

Круто, спасибо за это.

Проблема с предлагаемым здесь исправлением заключается в том, что для него требуется разветвление и ведение отдельного файла lib .

Интересно, достаточно ли востребована эта функция, чтобы гарантировать какой-то вариант из коробки.

Кстати, интересно, что сигнатура типа для метода get в коллекциях ES6 ( Map / Set ) возвращает T | undefined когда Array / Object индексных подписей нет.

это осознанное решение. Было бы очень неприятно, если бы этот код был ошибкой:

var a = [];
for (var i =0; i< a.length; i++) {
    a[i]+=1; // a[i] is possibly undefined
}

и было бы неразумно просить каждого пользователя использовать ! . или написать

var a = [];
for (var i =0; i< a.length; i++) {
    if (a[i]) {
        a[i]+=1; // a[i] is possibly undefined
    }
}

Для карты это обычно не так.

Точно так же для ваших типов вы можете указать | undefined для всех своих индексных подписей, и вы получите ожидаемое поведение. но для Array это не разумно. вы можете разветвить библиотеку и внести все необходимые изменения, но мы не планируем изменять объявление в стандартной библиотеке на данный момент.

Я не думаю, что добавление флага для изменения формы объявления - это то, что мы могли бы сделать.

@mhegazy, но для массивов с дырками a[i] на самом деле, возможно, не определено:

let a: number[] = []
a[0] = 0
a[5] =0
for (let i = 0; i < a.length; i++) {
  console.log(a[i])
}

Выход:

undefined
undefined
undefined
undefined
0

Мы по-прежнему весьма скептически относимся к тому, что кто-то получит какую-либо пользу от этого флага на практике. Карты и подобные карты уже могут выбрать | undefined на своих сайтах определения, и принудительное применение EULA-подобного поведения при доступе к массиву не похоже на победу. Скорее всего, нам нужно будет существенно улучшить CFA и шрифтовую защиту, чтобы сделать это приятным на вкус.

Если кто-то хочет изменить свой lib.d.ts и исправить все нисходящие разрывы в своем собственном коде и показать, как выглядит общий diff, чтобы показать, что это имеет какое-то ценностное предложение, мы открыты для этих данных. В качестве альтернативы, если многие люди действительно хотят использовать postfix ! еще, но еще не имеют для этого достаточных возможностей, этот флаг будет вариантом.

Мы по-прежнему весьма скептически относимся к тому, что кто-то получит какую-либо пользу от этого флага на практике. Карты и подобные карты уже могут выбрать | undefined на своих сайтах.

Разве одна из целей TypeScript не заключается в том, чтобы позволить обнаруживать ошибки во время «компиляции», а не полагаться на то, что пользователь помнит / знает, что делает что-то конкретное? Похоже, это идет вразрез с этой целью; требуя от пользователя что-то сделать, чтобы избежать сбоев. То же самое можно сказать и о многих других функциях; они не нужны, если разработчик всегда делает x. Цель TypeScript (предположительно) - упростить работу и устранить эти вещи.

Я столкнулся с этой ошибкой, потому что я включал strictNullChecks в существующем коде, и у меня уже было сравнение, поэтому я получил ошибку. Если бы я писал совершенно новый код, я, вероятно, не осознал бы проблему здесь (система типов сообщала мне, что я всегда получаю значение) и закончил бы с ошибкой во время выполнения. Полагаясь на разработчиков TS, которые помнят (или, что еще хуже, даже знают), что они должны объявлять все свои карты с помощью | undefined кажется, что TypeScript не выполняет то, что люди на самом деле хотят.

Мы по-прежнему весьма скептически относимся к тому, что кто-то получит какую-либо пользу от этого флага на практике. Карты и подобные карты уже могут подписаться на | undefined на их сайтах определения
Разве одна из целей TypeScript не заключается в том, чтобы позволить обнаруживать ошибки во время «компиляции», а не полагаться на то, что пользователь помнит / знает, что делает что-то конкретное?

Собственно цель такова:

1) Статически идентифицируйте конструкции, которые могут быть ошибками.

Здесь обсуждается вероятность ошибки (низкая, по мнению команды TypeScript) и общая продуктивность использования языка. Одним из первых изменений в CFA было снижение паники или улучшение анализа CFA для более разумного определения этих вещей.

Я думаю, что вопрос от команды TypeScript состоит в том, что вместо того, чтобы аргументировать его строгую правильность, предоставить примеры того, где такая строгость при обычном использовании на самом деле выявила бы ошибку, от которой следует остерегаться.

Я немного углубился в рассуждения в этом комментарии https://github.com/Microsoft/TypeScript/issues/11238#issuecomment -250562397

Подумайте о двух типах ключей в мире: те, которые, как вы знаете , имеют соответствующее свойство в каком-то объекте (безопасный), те, о которых вы не знаете, имеют соответствующее свойство в каком-то объекте (опасно).

Вы получите первый тип ключа, «безопасный» ключ, написав правильный код, например

for (let i = 0; i < arr.length; i++) {
  // arr[i] is T, not T | undefined

или

for (const k of Object.keys(obj)) {
  // obj[k] is T, not T | undefined

Второй тип ключей, «опасный» вид, вы получаете из таких вещей, как вводимые пользователем данные, или случайные файлы JSON с диска, или некоторый список ключей, которые могут присутствовать, но могут отсутствовать.

Так что, если у вас есть опасный ключ и индексируется по нему, было бы неплохо иметь здесь | undefined . Но предложение не «Считать опасные ключи опасными», а «Считать все ключи, даже безопасные». И как только вы начинаете относиться к безопасным ключам как к опасным, жизнь становится отстойной. Вы пишете код вроде

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

и TypeScript жалуется на вас, что arr[i] может быть undefined хотя я просто @ #% # проверил это . Теперь у вас появляется привычка писать такой код, и это кажется глупым:

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

Или, может быть, вы напишете такой код:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k].name);
  }
}

и TypeScript говорит: «Эй, это индексное выражение может быть | undefined , поэтому вы покорно исправляете его, потому что вы уже видели эту ошибку 800 раз:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k]!.name); // Shut up TypeScript I know what I'm doing
  }
}

Но вы не исправили ошибку . Вы хотели написать Object.keys(yourObj) или, может быть, myObj[k] . Это наихудший вид ошибки компилятора, потому что он на самом деле не помогает вам ни в одном сценарии - он всего лишь применяет один и тот же ритуал к каждому виду выражения, независимо от того, было ли оно на самом деле более опасным, чем любое другое выражение той же формы.

Я думаю о старом "Вы уверены, что хотите удалить этот файл?" диалог. Если бы этот диалог появлялся каждый раз, когда вы пытались удалить файл, вы очень быстро научились бы нажимать del y когда раньше нажимали del , и ваши шансы не удалить что-то важное сбрасываются до -диалог базового уровня. Если вместо этого диалоговое окно появлялось только тогда, когда вы удаляли файлы, когда они не отправлялись в корзину, теперь у вас есть значимая безопасность. Но мы понятия не имеем (и не можем), безопасны ли ключи ваших объектов или нет, поэтому мы показываем «Вы уверены, что хотите проиндексировать этот объект?» диалог каждый раз, когда вы это делаете, вряд ли найдет ошибки лучше, чем не отображать все.

Статически идентифицируйте конструкции, которые могут быть ошибками.

Возможно, это нужно изменить, чтобы сказать: «Статически определять конструкции, которые с большей вероятностью, чем другие, будут ошибками». : wink :. Мне напоминают, когда мы получаем ошибки, которые по сути сводятся к следующему: «Я использовал * когда хотел использовать / , можете ли вы использовать make * качестве предупреждения?»

Я понимаю, но индекс вне диапазона - это реальная и распространенная проблема; принуждение людей перечислять массивы так, чтобы они не могли этого сделать, было бы неплохо.

Исправление с ! мне тоже не нравится - что, если кто-то придет и внесет такое изменение, что предположение станет неверным? Вы вернулись к исходной точке (потенциальный сбой во время выполнения для чего-то, что компилятор должен уловить). Должны быть безопасные способы перечисления массивов, которые не полагаются ни на ложь о типах, ни на использование ! (например, нельзя ли сделать что-то вроде array.forEach(i => console.log(i.name) ?).

Вы уже сузили типы на основе кода, поэтому теоретически не могли бы вы найти шаблоны, которые безопасны, чтобы сузить тип, чтобы удалить | undefined в этих случаях, используя лучшее из обоих миров? Я бы сказал, что если вы не можете легко передать компилятору, что вы не обращаетесь к действительному элементу, то, возможно, ваша гарантия либо недействительна, либо может быть случайно нарушена в будущем.

Тем не менее, я использую TS только в одном проекте, и в конечном итоге он будет перенесен на Dart, так что это вряд ли что-то изменит для меня. Мне просто грустно, что в целом качество программного обеспечения плохое, и здесь есть возможность помочь устранить ошибки, которые, казалось бы, игнорируются ради удобства. Я уверен, что систему типов можно было бы сделать твердой, а общие неприятности устранить таким образом, чтобы не было этих дыр.

В любом случае, это всего лишь мои 2 цента ... Я не хочу затягивать это - я уверен, что вы понимаете, откуда мы пришли, и вы гораздо лучше можете принимать решения по этому поводу, чем я :-)

Я думаю, что нужно учесть несколько вещей. Существует множество широко используемых шаблонов для перебора массивов, учитывающих количество элементов. Хотя возможен только случайный доступ к индексам в массивах, в реальных условиях это очень необычный шаблон и вряд ли будет статической ошибкой. Хотя существуют современные способы итерации, наиболее распространенными будут следующие:

for (let i = 0; i < a.length; i++) {
  const value = a[i];
}

Если вы предполагаете, что запасные массивы встречаются редко (а это так), то, что value будет | undefined мало поможет. Если есть общий шаблон, в дикой природе, где это рискованно (и, вероятно, ошибка), тогда я думаю, что TypeScript прислушается к этому, но шаблоны, которые обычно используются, должны снова снова против всех значений доступ к индексу быть неопределенным - это явно что-то, что влияет на производительность и, как уже указывалось, может быть выбрано, если вы находитесь в ситуации, когда это потенциально полезно.

Я думаю, что раньше уже велись разговоры об улучшении CFA, чтобы можно было выразить взаимозависимость значений (например, Array.prototype.length относится к значению индекса), чтобы такие вещи, как индекс за пределами границ, могли быть статически проанализированы. Очевидно, что это значительная работа, связанная со всевозможными крайними случаями и соображениями, которые я бы не хотел вдаваться в подробности (хотя, вероятно, Андерс просыпается в холодном поту из-за некоторых подобных вещей).

Таким образом, это становится компромиссом ... Без улучшений CFA усложните 90% кода с помощью отвлекающих маневров, чтобы отловить потенциально 10% плохого кода . В противном случае он инвестирует в серьезные улучшения CFA, которые могут быть связаны с их собственными последствиями стабильности и проблемами снова, обнаруживая то, что было бы небезопасным кодом.

TypeScript может сделать так много, чтобы спасти нас от самих себя.

Все это сосредоточено на массивах, и я согласен, что там вряд ли возникнет проблема, но большинство из первоначальных поднятых проблем (например, у меня) были связаны с картами, где я не думаю, что общий случай - это всегда существующие ключи?

Все это сосредоточено на массивах, и я согласен, что там вряд ли возникнет проблема, но большинство из первоначальных поднятых проблем (например, у меня) были связаны с картами, где я не думаю, что общий случай - это всегда существующие ключи?

Если это ваш тип, добавьте | undefined в подпись индекса. Индексирование в тип без подписи индекса в --noImplicitAny уже является ошибкой.
ES6 Map уже определен с get как get(key: K): V | undefined; .

я переписал все определения массивов и карт, чтобы индексы возвращали | undefined , ни разу не пожалел с тех пор, обнаружил несколько ошибок, это не вызывает никакого дискомфорта, потому что я работаю с массивами косвенно через самодельную библиотеку, которая хранит проверки для undefined или ! внутри него

Было бы здорово, если бы TypeScript мог управлять потоком проверок, как это делает C # (чтобы исключить проверки диапазона индексов для экономии времени процессора), например:

declare var values: number[];
for (let index = 0, length = values.length; index< length; index ++) {
   const value = value[index]; // always defined, because index is within array range and only controlled by it
}

(тем, кто пользуется разреженными массивами - убейте себя раскаленным огнем)

что касается Object.keys , требуется специальный тип, скажем allkeysof T чтобы анализ потока управления выполнял безопасные сужения

Я думаю, что это было бы хорошим вариантом, потому что сейчас мы, по сути, лжем о типе операции индексации, и можно легко забыть добавить | undefined к моим типам объектов. Я думаю, что добавление ! в тех случаях, когда мы знаем, что мы хотим игнорировать undefined s, было бы хорошим способом справиться с операциями индексации, когда эта опция включена.

Есть (как минимум) две другие проблемы с помещением |undefined в определения типа вашего объекта:

  • это означает, что вы можете назначить undefined этим объектам, что определенно не предназначено
  • другие операции, такие как Object.values (или _.values ), потребуют от вас обработки undefined в результатах

tslint сообщает о ложноположительном предупреждении о постоянном условии, поскольку машинописный текст возвращает неверную информацию о типе (= отсутствует | undefined ).
https://github.com/palantir/tslint/issues/2944

Одна из регулярно пропускаемых ошибок из-за отсутствия | undefined в индексировании массива - это этот шаблон при использовании вместо find :

const array = [ 1, 2, 3 ];
const firstFour = array.filter((x) => (x === 4))[0];
// if there is no `4` in the `array`,
// `firstFour` will be `undefined`, but TypeScript thinks `number` because of the indexer signature.
const array = [ 1, 2, 3 ];
const firstFour = array.find((x) => (x === 4));
// `firstFour` will be correctly typed as `number | undefined` because of the `find` signature.

Я бы определенно использовал этот флаг. Да, старые циклы for будут раздражать работать, но у нас есть оператор ! который сообщает компилятору, когда мы знаем, что он определен:

for (let i = 0; i < arr.length; i++) {
  foo(arr[i]!)
}

Кроме того, эта проблема не является проблемой с более новыми, более лучшими циклами for of и есть даже правило TSLint prefer-for-of которое говорит вам не использовать старые циклы for больше.

В настоящее время я чувствую, что система типов несовместима для разработчика. array.pop() требует проверки if или утверждения ! , но доступ через [array.length - 1] этого не делает. ES6 map.get() требует проверки if или утверждения ! , а хеш объекта - нет. Пример @sompylasar тоже хорош.

Другой пример - деструктуризация:

const specifier = 'Microsoft/TypeScript'
const [repo, revision] = specifier.split('@') // types of repo and revision are string
console.log('Repo: ' + repo)
console.log('Short rev: ' + revision.slice(0, 7)) // Error: Cannot call function 'slice' on undefined

Я бы предпочел, чтобы компилятор заставил меня сделать это:

const specifier = 'Microsoft/TypeScript'
const [repo, revision] = specifier.split('@') // types of repo and revision are string | undefined
console.log('Repo: ', repo || 'no repo')
console.log('Short rev:', revision ? revision.slice(0, 7) : 'no revision')

Это настоящие ошибки, которые я видел, которые можно было бы предотвратить с помощью компилятора.

Imo, это не должно принадлежать файлам типизации, а скорее должно быть механизмом системы типов - при доступе к чему-либо с индексной подписью это может быть undefined . Если ваша логика гарантирует, что это не так, просто используйте ! . В противном случае добавьте if и все будет хорошо.

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

Я бы очень хотел, чтобы этот флаг был добавлен. В кодовой базе моей компании произвольный доступ к массиву является редким исключением, а циклы for - это запахи кода, которые мы обычно хотели бы переписать с помощью функций более высокого порядка.

@pelotom, о чем ты тогда беспокоишься (поскольку кажется, что ты в основном

@ aleksey-bykov в основном подписи индекса объекта, которые широко используются в сторонних библиотеках. Я хотел бы получить доступ к свойству на { [k: string]: A } чтобы предупредить меня о том, что результат, возможно, не определен. Я упомянул индексирование массивов только потому, что это было вызвано тем, что флаг слишком раздражает, чтобы работать с ним.

вы знаете, что можете переписать их именно так, как хотите? (учитывая небольшую дополнительную работу)

Да, я мог бы переписать все набранные под них, или я мог бы включить флаг компилятора 😜

продолжайте играть в капитана О ...: вы можете переписать свой lib.d.ts сегодня и стать счастливым обладателем более надежной кодовой базы, или вы можете ждать флага в течение следующих N лет

@ aleksey-bykov как это сделать переписав lib.d.ts ?

declare type Keyed<T> = { [key: string]: T | undefined; }

затем в определении Array в lib.es2015.core.d.ts замените

[n: number]: T;

с участием

[n: number]: T | undefined;

@ aleksey-bykov, может быть, вы пропустили ту часть, где я сказал, что мне наплевать на массивы. Меня волнует, где сторонние библиотеки объявили что-то типа { [k: string]: T } , и я хочу, чтобы доступ к такому объекту возвращал что-то, возможно, неопределенное. Этого нельзя добиться простым редактированием lib.d.ts ; это требует изменения подписей рассматриваемой библиотеки.

у вас есть контроль над сторонними файлами определений? если это так, вы можете исправить их

И теперь мы вернулись к

Да, я мог бы переписать все набранные под них, или я мог бы включить флаг компилятора 😜

Время - плоский круг.

не будь глупцом, ты же не используешь «все на клавиатуре»? это буквально день максимальной работы для типичного проекта, было ли это сделано

Да, мне все время приходится редактировать чужие наборы текста, и мне бы хотелось меньше.

и вы будете через N лет, может быть, сейчас вы можете страдать или вырасти

Спасибо за ваш невероятно конструктивный вклад 👍

конструктивный вклад в это заключается в следующем, этот вопрос необходимо закрыть, потому что:
а. либо решение о том, может ли [x] быть undefined или нет, остается за разработчиками

  • позволяя им держать все это в голове, как они всегда делали раньше
  • или изменив lib.d.ts и 3rd-party.d.ts как было предложено

б. или требуется специальный синтаксис / типы / анализ потока / N лет, чтобы смягчить то, что можно легко сделать руками в #a

Проблема заключается в предложении для (b), за исключением того, что новый синтаксис не предлагается, это просто флаг компилятора.

Все сводится к тому, что тип { [x: string]: {} } почти всегда является ложью; за исключением использования Proxy , не существует объекта, который мог бы иметь бесконечное количество свойств, не говоря уже о каждой возможной строке. Предложение состоит в том, чтобы иметь флаг компилятора, который распознает это. Может оказаться, что это слишком сложно реализовать для того, что было достигнуто; Я оставлю этот вызов разработчикам.

Дело в том, что ни

  • T | undefined
  • ни T

подходит для общего случая

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

T | undefined является правильным для общего случая по причинам, которые я только что назвал. Не обращаю внимания на ваши бессмысленные разговоры о зависимых типах, желаю хорошего дня.

вы можете игнорировать меня сколько угодно, но T | undefined - это превышение для

declare var items: number[];
for (var index = 0; index < items.length; index ++) {
   void items[index];
}

Я бы предпочел иметь там T | undefined по умолчанию и сообщить компилятору, что index является диапазоном числовых индексов items поэтому не выходит, если границы при применении к items ; в простых случаях, таких как набор часто используемых фигур for / while , компилятор может сделать это автоматически; в сложных случаях, извините, может быть undefined s. И да, здесь хорошо бы подошли типы, основанные на значениях; Литеральные строковые типы настолько полезны, почему бы не использовать буквальные логические и числовые типы и типы диапазона / набора диапазонов? Что касается TypeScript, он пытается охватить все, что может быть выражено с помощью JavaScript (в отличие, например, от Elm, который ограничивает это).

это буквально день максимальной работы для типичного проекта, было ли это сделано

@ aleksey-bykov, интересно, что вы испытали после этого изменения? как часто вам нужно использовать ! ? и как часто вы обнаруживаете, что компилятор отмечает фактические ошибки?

@mhegazy, честно говоря, я не заметил большой разницы при переходе от T к T | undefined , и я не обнаружил никаких ошибок, я думаю, моя проблема в том, что я работаю с массивами через служебные функции, которые сохраняют ! в них, поэтому внешний код буквально не действует:

https://github.com/aleksey-bykov/basic/blob/master/array.ts

В каком файле lib можно найти определение типа индекса для объектов? Я обнаружил и обновил Array с [n: number]: T до [n: number]: T | undefined . Теперь я хочу проделать то же самое с объектами.

нет стандартного интерфейса (например, Array для массивов) для объектов с индексной подписью, вам нужно искать точные определения для каждого случая в вашем коде и исправлять их

вам нужно искать точные определения для каждого случая в вашем коде и исправлять их

Как насчет прямого поиска ключа? Например

const xs = { foo: 'bar' }
xs['foo']

Есть ли способ принудительно применить здесь T | undefined вместо T ? В настоящее время я использую эти помощники в своей кодовой базе везде, как безопасные по типу альтернативы поиску по индексам в массивах и объектах:

// TS doesn't return the correct type for array and object index signatures. It returns `T` instead
// of `T | undefined`. These helpers give us the correct type.
// https://github.com/Microsoft/TypeScript/issues/13778
export const getIndex = function<X> (index: number, xs: X[]): X | undefined {
  return xs[index];
};
export const getKeyInMap = function<X> (key: string, xs: { [key: string]: X }): X | undefined {
  return xs[key];
};

@mhegazy Когда я пишу это, я исправляю производственную ошибку на https://unsplash.com, которая могла быть обнаружена с помощью более строгих типов подписи индекса.

я вижу, рассмотрим оператор сопоставленного типа:

const xs = { foo: 'bar' };
type EachUndefined<T> = { [P in keyof T]: T[P] | undefined; }
const xu : EachUndefined<typeof xs> = xs;
xu.foo; // <-- string | undefined

Если флаг типа --strictArrayIndex не подходит, потому что флаги не предназначены для изменения файлов lib.d.ts . Может быть, вы, ребята, сможете выпустить строгие версии файлов lib.d.ts например " lib": ['strict-es6'] ?"

Он может содержать несколько улучшений, а не только строгий индекс массива. Например, Object.keys :

interface ObjectConstructor {
    // ...
    keys(o: {}): string[];
}

Может быть:

interface ObjectConstructor {
    // ...
    keys<T>(o: T): (keyof T)[];
}

Обновление от сегодняшнего SBS: Мы кричали друг на друга 30 минут, но ничего не произошло. 🤷‍♂️

@RyanCavanaugh Что такое SBS, из любопытства?

@radix "

это интригует, потому что решение очевидно:
оба T и T | undefined неверны, единственный правильный способ - заставить индексную переменную знать емкость своего контейнера, либо путем выбора из набора, либо путем включения его в известный числовой диапазон

@RyanCavanaugh, я думал об этом, похоже, следующий трюк охватит 87% всех случаев:

  • values[index] дает T если индекс объявлен в for (HERE; ...)
  • values[somethingElse] дает T | undefined для всех переменных, объявленных вне for

@ aleksey-bykov мы обсуждали кое-что еще более умное - что может существовать реальный механизм защиты типов для " arr[index] был протестирован index < arr.length . Но это не поможет в случае, когда вы pop 'd в середине цикла или передал ваш массив функции, которая удаляла из него элементы. На самом деле не похоже, что люди ищут механизм, который предотвращает ошибки OOB в 82,9% случаев - в конце концов , это уже тот случай, что примерно эта часть кода индексации массивов в любом случае верна.Добавление церемонии для исправления кода без выявления ошибок в неправильных случаях - плохой результат.

не подразумевать, что Алексей когда-либо изменит массив

Недавно я перенес приложение с Elm на Typescript, и неправильно набранные операции индексирования, вероятно, являются одним из самых больших источников ошибок, с которыми я столкнулся, со всеми самыми строгими настройками TS (также такие вещи, как this не привязаны ).

  • вы не можете отслеживать мутации в контейнере
  • вы не можете отслеживать манипуляции с индексами

с этим мало что вы можете гарантировать, если да, то зачем вам даже пытаться, если типичный вариант использования - это тупая итерация по всем элементам внутри < array.length

как я обычно говорил

  • если есть ошибка, она, скорее всего, возникла где-то в пунктах оператора for : инициализация, приращение, условие остановки и, следовательно, не является чем-то, что вы можете проверить, потому что для этого требуется полная проверка типов по Тьюрингу
  • вне предложений for обычно нет места для ошибки

поэтому до тех пор, пока индекс объявлен каким-то образом (мы ничего не можем сказать об этом с уверенностью), разумно справедливо полагать, что индекс верен в теле цикла

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

Это кажется действительно слабым аргументом против этого. Эти случаи уже сломаны - использовать их как предлог, чтобы не исправлять другие случаи, не имеет смысла. Никто здесь не требует, чтобы ни один из этих случаев был правильно обработан, ни чтобы он был на 100% правильным. Нам просто нужны точные типы в действительно распространенных случаях. В TypeScript есть много случаев, которые не обрабатываются идеально; если вы делаете что-то странное, типы могут ошибаться. В этом случае мы не делаем ничего странного, а типы неправильные.

Добавление церемонии в правильный код

Мне любопытно увидеть пример кода, где это «добавление церемонии для исправления кода». Насколько я понимаю, базовый цикл for по массиву легко обнаружить / сузить. Каков реальный код, который не является простым циклом for, когда это становится неудобным, что потенциально не является ошибкой? (либо сейчас, либо может произойти в результате незначительного изменения в будущем). Я не говорю, что их нет; Я просто не могу это представить и не видел примеров (я видел множество примеров с использованием циклов for, но если вы не говорите, что их нельзя сузить, они кажутся неактуальными).

пока не ловит ошибки

Были примеры как рабочего кода, который не может быть скомпилирован из-за этого, так и кода, который генерируется во время выполнения, потому что типы вводят разработчика в заблуждение. Сказать, что это не имеет смысла, - это чепуха.

Почему бы нам просто не сделать это простым и вообще не добавлять _любое_ магическое поведение к циклам for в старом стиле? Вы всегда можете использовать ! чтобы все заработало. Если ваша кодовая база заполнена циклами for старого стиля, не используйте этот флаг. Все современные кодовые базы, с которыми я работал, используют forEach или for of для итерации массивов, и эти кодовые базы выиграют от дополнительной безопасности типов.

мы не делаем ничего странного, и типы неправильные.

Этот абзац, на мой взгляд, является хорошей причиной не использовать эту функцию. Доступ к массиву за пределами допустимого диапазона - это странная вещь; типы являются «неправильными» только в том случае, если вы делаете что-то необычное (OOBing). Подавляющее большинство кода, считываемого из массива, происходит в границах; было бы «неправильно» включать в этот аргумент undefined в таких случаях.

... рабочий код, который не компилируется

Мне ничего не известно - можете ли вы указать на них конкретно?

Почему бы нам просто не сделать это простым и вообще не добавлять какое-либо волшебное поведение к циклам for в старом стиле? Всегда можно использовать! заставить вещи работать.

В чем полезная разница между этим правилом и правилом TSLint, которое гласит: «Все выражения доступа к массиву должны иметь в конце ! »?

@RyanCavanaugh, я предполагаю, что правило TSLint не сможет сузить типы или использовать анализ типа потока управления (например, обернуть доступ в if , выбросить исключение, return ing или continue ing, если он не задан и т. Д.). Речь идет не только о выражениях доступа к массиву, но и о деструктуризации. Как будет выглядеть реализация для примера в https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -336265143? Чтобы обеспечить выполнение ! , по сути, необходимо создать таблицу для отслеживания типов этих переменных как возможных undefined . Что для меня звучит как то, что должна делать программа проверки типов, а не линтер.

@RyanCavanaugh

На мой взгляд, этот абзац - хорошая причина не использовать эту функцию. Доступ к массиву за пределами допустимого диапазона - это странная вещь; типы являются «неправильными» только в том случае, если вы делаете что-то необычное (OOBing).

... рабочий код, который не компилируется

Мне ничего не известно - можете ли вы указать на них конкретно?

В моем случае это не был массив, но он был закрыт как обман, поэтому я предполагаю, что эта проблема должна касаться и их. См. № 11186 для моей исходной проблемы. Я разбирал файл на карту, а затем сравнивал их с undefined . IIRC Я получил ошибку при сравнении, что они не могут быть неопределенными, хотя они могли (и были).

Всегда можно что-то сравнивать с undefined

что позор

const canWeDoIt = null === undefined; // yes we can!

Всегда можно что-то сравнивать с undefined

Это было так давно, боюсь, я точно не помню, в чем была ошибка. У меня определенно была ошибка для кода, который работал нормально (без strictNullChecks ), и это привело меня к тестированию, которое привело к описанному выше случаю.

Если у меня будет время, я посмотрю, смогу ли я снова понять, что это было. Это определенно было связано с этой типизацией.

Большая часть этого обсуждения была сосредоточена на массивах, но, по моему опыту, доступ к объектам - это то место, где эта функция была бы наиболее полезной. Например, этот (упрощенный) пример из моей кодовой базы выглядит разумно и хорошо компилируется, но определенно является ошибкой:

export type Chooser = (context?: Context) => number | string;
export interface Choices {
    [choice: number]: Struct;
    [choice: string]: Struct;
}

export const Branch = (chooser: Chooser, choices: Choices, context?: Context): Struct => {
    return choices[chooser(context)];  // Could be undefined
}

Что касается объектов и простого изменения подписи, чтобы включить | undefined , @types/node делает это для process.env :

    export interface ProcessEnv {
        [key: string]: string | undefined;
    }

но это совершенно не позволяет сузить тип:

process.env.SOME_CONFIG && JSON.parse(process.env.SOME_CONFIG)

дает

[ts]
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

@felixfbecker

Я считаю, что это связано с этой ошибкой: https://github.com/Microsoft/TypeScript/issues/17960

Вы можете обойти это, назначив переменную env переменной, а затем защитив это:

const foo = process.env.SOME_CONFIG
foo && JSON.parse(foo);

Добавление | undefined в некоторые типы узлов было сделано для того, чтобы можно было дополнить этот интерфейс для удобства часто используемыми свойствами для завершения кода. еа

interface ProcessEnv {
  foo?: string;
  bar?: string;
}

Без | undefined в подписи индекса TSC жалуется на Property 'foo' of type 'string | undefined' is not assignable to string index type 'string'. .

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

Было бы здорово, если бы мы могли от этого избавиться.

Пытаюсь собраться с мыслями по этому поводу.

Объекты - это смешанный пакет в JavaScript. Их можно использовать в двух целях :

  • словари ака карты, где ключи неизвестны
  • записи, где ключи известны

Для объектов, используемых в качестве записей, нет необходимости использовать индексные подписи. Вместо этого известные ключи определяются в типе интерфейса. При правильном поиске ключа возвращается соответствующий тип, при поиске неверного ключа выполняется возврат к подписи индекса. Если подпись индекса не определена (чего не должно быть для объектов, используемых в качестве записей), и включен параметр noImplicitAny , при желании это приведет к ошибке.

Для объектов, используемых в качестве словарей (также называемых картами) и массивов, используются индексные подписи, и мы можем включить | undefined в тип значения. Например, { [key: index]: string | undefined } . Все поиски ключей действительны (поскольку ключи неизвестны во время компиляции), и все ключи возвращают один и тот же тип (в этом примере T | undefined ).

Поскольку сигнатуры индекса следует использовать только для шаблона и массивов объектов словаря, желательно, чтобы TypeScript принудительно использовал | undefined в типе значения сигнатуры индекса: если ключи неизвестны, и поиск ключей может возвращать undefined .

Есть хорошие примеры ошибок, которые без этого могут казаться невидимыми, например Array.prototype.find возвращающее undefined , или поиск ключей, например array[0] возвращающий undefined . (Деструктуризация - это просто сахарный синтаксис для поиска ключей.) Можно написать такие функции, как getKey чтобы исправить возвращаемый тип, но мы должны полагаться на дисциплину, чтобы принудительно использовать эти функции во всей кодовой базе.

Если я правильно понимаю, тогда проблема заключается в отражении объектов словаря и массивов, так что при сопоставлении ключей объекта или массива словаря поиск по ключу считается действительным, т.е. не возвращает undefined . В этом случае было бы нежелательно, чтобы типы значений включали undefined . Чтобы исправить это, возможно, удастся использовать анализ потока управления.

Это нерешенная проблема, или я неправильно понимаю?

Какие варианты использования и проблемы я не упоминал?

Есть (по крайней мере) две другие проблемы с помещением | undefined в определения типа вашего объекта:

  • это означает, что вы можете назначить undefined этим объектам, что определенно не предназначено
  • другие операции, такие как Object.values ​​(или _.values), потребуют от вас обработки undefined в результатах

Я считаю это очень важным моментом.

На данный момент я экспериментирую со следующим подходом:

Определить const safelyAccessProperty = <T, K extends keyof T>(object: T, key: K): T[K] | undefined => object[key];

Затем перейдите к таким свойствам, как safelyAccessProperty(myObject, myKey) вместо myObject[myKey] .

@plul Хороший улов. В настоящее время обсуждение сосредоточено на операциях чтения, но определение типа индексатора на самом деле двоякое, и добавление | undefined позволит записывать значения undefined .

Функция safelyAccessProperty вы экспериментируете ( упомянутая выше как getKey от @OliverJAsh), требует дисциплины и / или правила линтера, запрещающего операции индексирования для всех массивов и объектов.

Это можно сделать масштабируемым, если функция предоставляется во всех экземплярах массивов и объектов (каждый тип, который обеспечивает операции индексатора), например, в C ++ std::vector имеет .at() который генерирует исключение во время выполнения для доступа к OOB. , и непроверенный оператор [] который в лучшем случае дает сбой SEGFAULT при доступе к OOB, в худшем случае повреждает память.

Я думаю, что проблема доступа OOB не решается в TypeScript / JavaScript только на уровне определения типа и требует поддержки языка для ограничения потенциально опасных операций индексатора, если эта функция строгости включена.

Двойной характер индексатора можно было бы смоделировать как свойство с операциями get и set качестве функций, но это было бы критическим изменением для всех существующих определений типов индексатора.

Одна идея, которая кажется многообещающей: что, если бы вы могли использовать ?: при определении подписи индекса, чтобы указать, что вы ожидаете, что значения иногда будут отсутствовать при нормальном использовании? Он будет действовать как | undefined упомянутый выше, но без неудобных недостатков. Потребуется запретить явные значения undefined , что, как я полагаю, отличается от обычного ?: .

Это выглядело бы так:

type NewWay = {[key: string]?: string};
const n: NewWay = {};

// Has type string | undefined
n['foo']

// Has type Array<string>
Object.values(n)

// Doesn't work
n['foo'] = undefined;

// Works
delete n['foo'];

По сравнению с предыдущим подходом | undefined :

type OldWay = {[key: string]: string | undefined};
const o: OldWay = {};

// Has type string | undefined
o['foo']

// Has type Array<string | undefined>
Object.values(o)

// Works
o['foo'] = undefined;

// Works
delete o['foo'];

Я пришел сюда из-за отказа от добавления | undefined в DT PR выше, поскольку это сломало бы всех существующих пользователей этого API - можно ли было бы лучше рассматривать это как позволяющее пользователю выбирать, насколько суетливым он хочет быть, скорее чем библиотека?


Я отмечу, что дополнительные свойства также добавляют | undefined , и это меня несколько раз укусило - по сути, TS не различает отсутствующее свойство и свойство, установленное на undefined. Я просто хотел бы, чтобы к { foo?: T, bar?: T } относились так же, как к { [name: 'foo' | 'bar']: T } , как бы то ни было (см. Также комментарии process.env выше)


TS против нарушения здесь симметрии числовых и строковых индексаторов?

foo[bar] && foo[bar].baz() - очень распространенный шаблон JS, он кажется неуклюжим, когда он не поддерживается TS (в смысле напоминания вам, что вам нужно, если вы не добавляете | undefined , и предупреждения, когда он очевидно, не требуется, если вы это сделаете).


Что касается мутирующих массивов во время итерации, нарушающей гарантию выражения защиты, это возможно и с другими охранниками:

class Foo {
    foo: string | number = 123

    bar() {
        this.foo = 'bar'
    }

    broken() {
        if (typeof this.foo === 'number') {
            this.bar();
            this.foo.toLowerCase(); // right, but type error
            this.foo.toExponential(); // wrong, but typechecks
        }
    }
}

но я думаю, что в реальном коде гораздо менее вероятно, что старые циклы изменяют итерацию.

Понятно, что эта функция востребована. Я очень надеюсь, что команда TS найдет какое-то решение. Не просто добавляя | undefined в индексатор, потому что у него есть свои проблемы (уже упоминалось), а более "умным" способом (чтение возвращает T|undefined , для записи требуется T , хороший компилятор проверка на for loop и т. д. хорошее предложение также уже упоминалось.)

Когда мы изменяем и работаем с массивами в нетривиальных условиях, которые трудно проверить с помощью компилятора, у нас все в порядке с ошибкой времени выполнения. Нам просто нужна проверка ошибок в большинстве случаев, и мы можем иногда использовать ! .

Сказав это, если бы эта более строгая обработка массивов была бы реализована, теперь с # 24897 можно было бы реализовать хорошее сужение типа при проверке длины массива с константой. Мы могли бы просто сузить массив до кортежа с остальным элементом.

let arr!: string[];
if (arr.length == 3) {
  //arr is of type [string, string, string]
}

if (arr.length > 3) {
  //arr is of type [string, string, string, string, ...string[]]
}

if (arr.length) {
  //arr is of type [string, ...string[]]
}

if (arr.length < 3) {
  //arr is of type [string?, string?, string?]
  if (arr.length > 0) {
    //arr is of type [string, string?, string?]
  }
}

Это было бы полезно, когда вы индексируете по константе или деструктурируете массив.

let someNumber = 55;
if (arr.length) {
  let el1 = arr[0]; //string
  let el2 = arr[1]; //string | undefined
  let el3 = arr[someNumber]; //string | undefined
}

if(arr.length >= 3){
    let [el1, el2, el3, el4] = arr;
    //el1, el2, el3 are string
    // el4 is string | undefined    
}

if (arr.length == 2){
    let [el1, el2, el3] = arr; //compiler error: "Tuple type '[string, string]' with length '2' cannot be assigned to tuple with length '3'.",
}

Другой вопрос: что мы будем делать с большими числами, например:

if(arr.length >= 99999){
    // arr is [string, string, ... , string, ...string[]]
}

Мы не можем показать тип этого огромного кортежа в сообщениях IDE или компилятора.

Я предполагаю, что у нас может быть какой-то синтаксис для представления «кортежа определенной длины с одинаковым типом для всех элементов». Так, например, кортеж из 1000 строк - это string[10000] а тип суженного массива из приведенного выше примера может быть [...string[99999], ...string[]] .

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

Объекты

Мне всегда нужен тип индекса [key: string (or number, symbol)]: V | undefined , только иногда я забываю о случае undefined . Всякий раз, когда разработчик должен явно сказать компилятору: «Поверьте мне, это действительно тот тип вещей», вы знаете, что это небезопасно.
Правильно (строго) вводить Map.get очень мало смысла, но каким-то образом простые объекты получают бесплатный проход.
Тем не менее, это легко исправить в пользовательской среде, так что это не так уж и плохо. Во всяком случае, у меня нет решения.

Массивы

Возможно, я что-то упускаю, но кажется, что аргумент «вы почти никогда не обращаетесь к массиву небезопасным способом» может быть двояким, особенно с новым флагом компилятора.

Я склонен думать, что все больше и больше людей следуют этим двум передовым методам:

  • Используйте функциональные собственные методы или библиотеки для итерации или преобразования массивов. Здесь нет доступа к кронштейнам.
  • Не изменяйте массивы на месте

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

Это кажется очевидным, и я не думаю, что копирование всего lib.d.ts локально является приемлемым обходным путем.

Когда мы явно индексируем массив / объект, например, чтобы получить первый элемент массива ( array[0] ), мы хотим, чтобы результат включал undefined .

Это возможно путем добавления undefined к подписи индекса.

Однако, если я правильно понимаю, проблема с включением undefined в сигнатуру индекса заключается в том, что при сопоставлении массива или объекта значения будут включать undefined даже если они, как известно, не быть undefined (иначе мы бы не отображали их).

Типы / подписи индексов используются как для поиска по индексу (например, array[0] ), и для сопоставления (например, for loops и Array.prototype.map ), но нам требуются разные типы для каждого из этих случаев.

Это не проблема с Map потому что Map.get - это функция, и поэтому его возвращаемый тип может быть отдельным от внутреннего типа значения, в отличие от индексации в массив / объект, который не является функцией, и поэтому напрямую использует подпись индекса.

Итак, возникает вопрос: как удовлетворить оба случая?

// Manually adding `undefined` to the index signature
declare const array: (number | undefined)[];

const first = array[0]; // number | undefined, as desired :-)
type IndexValue = typeof array[0]; // number | undefined, as desired! :-)

array.map(x => {
  x // number | undefined, not desired! :-(
})

Предложение

Параметр компилятора, который обрабатывает поиск по индексу (например, array[0] ) аналогично тому, как набираются Set.get и Map.get , путем включения undefined в тип значения индекса (не саму подпись индекса). Сама фактическая подпись индекса не будет включать undefined , поэтому функции сопоставления не выполняются.

Пример:

declare const array: number[];

// The compiler option would include `undefined` in the index value type
const first = array[0]; // number | undefined, as desired :-)
type IndexValue = typeof array[0]; // number | undefined, as desired :-)

array.map(x => {
  x // number, as desired :-)
})

Однако это не решит случай перебора массивов / объектов с использованием цикла for , поскольку этот метод использует поиск по индексу.

for (let i = 0; i < array.length; i++) {
  const x = array[i];
  x; // number | undefined, not desired! :-(
}

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

for (let i = 0; i < array.length; i++) {
  const x = array[i]!;
  x; // number, as desired :-)
}

Мы также могли бы предоставить способ согласия или отказа, например, с помощью некоторого синтаксиса для украшения подписи индекса (простите, пожалуйста, двусмысленный синтаксис, который я придумал для примера). Этот синтаксис будет просто способом указать, какое поведение мы хотим для поиска по индексу.

Отказ (опция компилятора по умолчанию включена, при необходимости отказаться):

declare const array: { [index: number]!!: string };

declare const dictionary: { [index: string]!!: string }

Подключайтесь (без опции компилятора, просто подпишитесь там, где это необходимо):

declare const array: { [index: string]!!: string };

declare const dictionary: { [index: string]??: string }

Я не читал ни об этой проблеме, ни о плюсах и минусах, различных предложениях и т. Д. (Просто нашел это в Google после того, как неоднократно удивлялся, что доступ к массиву / объекту не обрабатывается последовательно с помощью строгих проверок на null), но у меня есть связанное предложение: возможность сделать вывод типа массива как можно более строгим, если это специально не переопределено.

Например:

const balls = [1, 2 ,3];

По умолчанию balls будет рассматриваться как [number, number, number] . Это можно изменить, написав:

const balls: number[] = [1, 2 ,3];

Кроме того, доступ к элементу кортежа будет обрабатываться в соответствии со строгими проверками на null. Меня удивляет, что в следующем примере n в настоящее время выводится как number даже при включенных строгих проверках на null.

const balls: [number, number, number] = [1, 2 ,3];
const n = balls[100];

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

@ buu700 Добро пожаловать в TypeScript 3.0: https://blogs.msdn.microsoft.com/typescript/2018/07/30/announcing-typescript-3-0/#richer -tuple-types

Отлично! Что ж, интересное время. Объявление о выпуске было открыто, но еще не прочитано; Я пришел сюда только после того, как столкнулся с ситуацией, когда мне нужно было сделать какое-то странное приведение ( (<(T|undefined)[]> arr).slice(-1)[0] ), чтобы заставить TypeScript (2.9) делать то, что я хотел.

Просто хотел вернуть это предложение: https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -383072468

Это решило бы проблемы с индексированными типами, с которыми я столкнулся. Было бы здорово, если бы это было по умолчанию, но я понимаю, что это сломало бы многие вещи в реальном мире.

@mhegazy @RyanCavanaugh Есть поводу моего предложения? https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

Для меня есть простое решение. Включите флаг, который это проверяет. Тогда вместо:

const array = [1, 2, 3];
for (var i =0; i< array.length; i++) {
    array[i]+=1; // array[i] is possibly undefined
}

Ты сделаешь:

const array = [1, 2, 3];
array.forEach((value, i) => array[i] = value + 1);

Затем, выполняя случайный доступ к индексу, вы должны проверять, является ли результат неопределенным, но не во время итерации перечисляемой коллекции.

Я по-прежнему считаю, что это означает, что проблема не решена.

Как программист, который плохо знаком с TypeScript, я обнаружил, что ситуация с индексированием объектов в строгом режиме не интуитивно понятна. Я ожидал, что результатом поиска будет T | undefined , аналогично Map.get . Во всяком случае, я недавно столкнулся с этим и открыл проблему в библиотеке:

screepers / набранные-screeps # 107

Я, наверное, сейчас его закрою, потому что, похоже, хорошего решения нет. Думаю, я попробую "выбрать" T | undefined , используя небольшую служебную функцию:

export function lookup<T>(map: {[index: string]: T}, index: string): T|undefined {
  return map[index];
}

Здесь были некоторые предложения явно установить T | undefined качестве типа возвращаемого значения операции индексации объекта, однако это, похоже, не работает:

const obj: {[key: string]: number | undefined} = {
  "a": 1,
  "b": 2,
};

const test = obj["c"]; // const test: number

Это VSCode версии 1.31.1

@yawaramin Убедитесь, что в вашем tsconfig.json strictNullChecks включен tsconfig.json (который также включен флагом strict )

Если ваш вариант использования требует произвольной индексации массивов неизвестной длины, я думаю, стоит явно добавить undefined (хотя бы для того, чтобы «задокументировать» эту небезопасность).

const words = ... // some string[] that could be empty
const x = words[0] as string | undefined
console.log(x.length) // TS error

Кортежи работают с небольшими массивами известной длины. Может быть, мы могли бы использовать что-то вроде string[5] в качестве сокращения для [string, string, string, string, string] ?

Весьма в пользу этого варианта. Это заметная дыра в системе типов, особенно когда включен флаг strictNullChecks . Простые объекты постоянно используются в качестве карт в JS, поэтому TypeScript должен поддерживать этот вариант использования.

Попадите в это с разрушением массива параметра функции:

function foo([first]: string[]) { /* ... */ }

Здесь я ожидал бы, что a будет иметь тип string | undefined но это просто string , если я не сделаю

function foo([first]: (string | undefined)[]) { /* ... */ }

Я не думаю, что у нас есть единственный цикл for в нашей базе кода, поэтому я бы с радостью просто добавил флаг в мои параметры компилятора tsconfig (можно было бы назвать strictIndexSignatures ), чтобы переключить это поведение для нашего проекта.

Вот как я работал над этой проблемой: https://github.com/danielnixon/total-functions/

Хороший обходной путь, я действительно надеюсь, что команда TypeScript заблокирует его.

Когда программист делает предположения в письменном коде, а компилятор не может сделать вывод, что он сохранен, это должно привести к ошибке компилятора, если не замолчать ИМХО.

Такое поведение также было бы очень полезно в сочетании с новым необязательным оператором цепочки.

У меня возникла проблема с использованием | undefined с картой сегодня при использовании Object.entries() .

У меня есть тип индекса, который довольно хорошо описывается {[key: string]: string[]} , с очевидной оговоркой, что не все возможные строковые ключи представлены в индексе. Однако я написал ошибку, которую TS не обнаружил при попытке использовать значение, найденное в индексе; не обрабатывал неопределенный случай.

Поэтому я изменил его на {[key: string]: string[] | undefined} как рекомендовано, но теперь это приводит к проблемам с моим использованием Object.entries() . TypeScript теперь предполагает (разумно, исходя из спецификации типа), что индекс может иметь ключи, для которых указано значение undefined, и поэтому предполагает, что результат вызова Object.entries() на нем может содержать неопределенные значения.

Однако я знаю, что это невозможно; единственный раз, когда я должен получить результат undefined ищу ключ, которого не существует, и который не будет указан при использовании Object.entries() . Итак, чтобы сделать TypeScript счастливым, мне нужно либо написать код, у которого нет реальной причины для существования, либо переопределить предупреждения, чего я бы предпочел не делать.

@RyanCavanaugh , я предполагаю, что ваш исходный ответ на это по-прежнему является нынешней позицией команды TS? И как толчок к этому, потому что никто не читает поток и не проверяет, есть ли еще пару лет опыта работы с более мощными примитивами JS-коллекции, и вводит несколько других вариантов для повышения строгости, поскольку что-то изменилось.

(Примеры все еще для меня несколько неубедительны, но эта ветка уже привела все аргументы, другой комментарий ничего не изменит)

Если кто-то хочет изменить свой lib.d.ts и исправить все нисходящие разрывы в своем собственном коде и показать, как выглядит общий diff, чтобы показать, что это имеет какое-то ценностное предложение, мы открыты для этих данных.

@RyanCavanaugh Вот несколько случаев из моей базы кода, когда некоторые сбои во время выполнения дремлют. Обратите внимание, что у меня уже были случаи, когда из-за этого у меня происходили сбои в производстве, и мне нужно было выпустить исправление.

src={savedAdsItem.advertImageList.advertImage[0].mainImageUrl || undefined}
return advert.advertImageList.advertImage.length ? advert.advertImageList.advertImage[0].mainImageUrl : ''
birthYear: profileData.birthYear !== null ? profileData.birthYear : allowedYears[0].value,
upsellingsList.upsellingProducts[0].upsellingProducts[0].selected = true
const latitude = parseFloat(coordinates.split(',')[0])
const advert = Object.values(actionToConfirm.selectedItems)[0]
await dispatch(deactivateMyAd(advert))

В этом случае это будет раздражать, поскольку ArticleIDs extends articleNames[] включает undefined в результирующие значения, в то время как он должен просто разрешать полностью определенные подмножества. Легко исправить, используя ReadonlyArray<articleNames> вместо articleNames[] .

export enum articleNames {
    WEB_AGB = 'web_agb',
    TERMS_OF_USE = 'web_terms-of-use',
}
export const getMultipleArticles = async <ArticleIDs extends articleNames[], ArticleMap = { [key in ArticleIDs[number]]: CmsArticle }>(ids: ArticleIDs): Promise<ArticleMap> => {...}

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

Я немного углубился в рассуждения в этом комментарии

Подумайте о двух типах ключей в мире: те, о которых вы знаете _do_, имеют соответствующее свойство в каком-то объекте (безопасном), те, о которых вы _не_ знаете, что они имеют соответствующее свойство в каком-то объекте (опасно).

Вы получите первый тип ключа, «безопасный» ключ, написав правильный код, например

for (let i = 0; i < arr.length; i++) {
  // arr[i] is T, not T | undefined

или

for (const k of Object.keys(obj)) {
  // obj[k] is T, not T | undefined

Второй тип ключей, «опасный» вид, вы получаете из таких вещей, как вводимые пользователем данные, или случайные файлы JSON с диска, или некоторый список ключей, которые могут присутствовать, но могут отсутствовать.

Так что, если у вас есть опасный ключ и индексируется по нему, было бы неплохо иметь здесь | undefined . Но предложение состоит не в том, чтобы «относиться к _ опасным_ ключам как к опасным», а к «рассматривать _ все_ ключи, даже безопасные, как опасные». И как только вы начинаете относиться к безопасным ключам как к опасным, жизнь становится отстойной. Вы пишете код вроде

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

и TypeScript жалуется на вас, что arr[i] может быть undefined хотя _эй, послушайте, я только что @ #% # проверил это_. Теперь у вас появляется привычка писать такой код, и это кажется глупым:

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

Или, может быть, вы напишете такой код:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k].name);
  }
}

и TypeScript говорит: «Эй, это индексное выражение может быть | undefined , поэтому вы покорно исправляете его, потому что вы уже видели эту ошибку 800 раз:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k]!.name); // Shut up TypeScript I know what I'm doing
  }
}

Но вы не исправили ошибку . Вы хотели написать Object.keys(yourObj) или, может быть, myObj[k] . Это наихудший вид ошибки компилятора, потому что он на самом деле не помогает вам ни в одном сценарии - он всего лишь применяет один и тот же ритуал к каждому виду выражения, независимо от того, было ли оно на самом деле более опасным, чем любое другое выражение той же формы.

Я думаю о старом "Вы уверены, что хотите удалить этот файл?" диалог. Если бы этот диалог появлялся каждый раз, когда вы пытались удалить файл, вы очень быстро научились бы нажимать del y когда раньше нажимали del , и ваши шансы не удалить что-то важное сбрасываются до -диалог базового уровня. Если вместо этого диалоговое окно появлялось только тогда, когда вы удаляли файлы, когда они не отправлялись в корзину, теперь у вас есть значимая безопасность. Но мы понятия не имеем (и не можем_ мы), безопасны ли ключи ваших объектов или нет, поэтому мы показываем «Вы уверены, что хотите проиндексировать этот объект?» диалог каждый раз, когда вы это делаете, вряд ли найдет ошибки лучше, чем не отображать все.

Я согласен с аналогией с диалоговым окном удаления файла, однако я думаю, что эта аналогия также может быть расширена до принуждения пользователя к проверке чего-то, что, возможно, является неопределенным или нулевым, поэтому это объяснение действительно не имеет смысла, потому что, если это объяснение верно, опция strictNullChecks вызовет такое же поведение, например, получение некоторого элемента из DOM с помощью document.getElementById .

Но это не так, многие пользователи TypeScript хотят, чтобы компилятор поднял флаг для такого кода, чтобы эти крайние случаи могли быть обработаны соответствующим образом, вместо того, чтобы выдавать ошибку Cannot access property X of undefined которую очень и очень трудно отследить.

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

Разговор о неправильном доступе к массиву или объектам маловероятен, есть ли у вас какие-либо данные для резервного копирования этого утверждения? Или это просто основано на произвольном инстинктивном ощущении?

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

Анализ типов на основе потока управления TypeScript может быть улучшен, чтобы признать этот случай безопасным и не требующим ! . Если человек может сделать вывод, что это безопасно, то и компилятор сможет.
Я знаю, что это может быть нетривиально реализовать в компиляторе.

Анализ типов на основе потока управления TypeScript может быть улучшен, чтобы признать этот случай безопасным.

На самом деле не может.

declare function someFunc(arr: number[], i: number): void;
let arr = [1, 2, 3, 4];
for (let i = 0; i < arr.length; i++) {
  someFunc(arr, arr[i]);
}

Передает ли эта функция undefined someFunc при втором проходе цикла или нет? Я мог бы написать много вещей в someFunc что привело бы к появлению undefined позже.

Как насчет этого?

declare function someFunc(arr: number[], i: number): void;
let arr = [1, 2, 3, 4];
let alias = arr;
for (let i = 0; i < arr.length; i++) {
  someFunc(alias, arr[i]);
}

@fabb позвольте мне привести еще один пример:

`` ''
$ node

const arr = []
неопределенный
arr [7] = 7
7
обр
[<7 пустых элементов>, 7]
for (let i = 0; i <arr.length; i ++) {
... console.log (arr [i])
...}
неопределенный
неопределенный
неопределенный
неопределенный
неопределенный
неопределенный
неопределенный
7
undefined

@RyanCavanaugh, раз уж вы здесь, как насчет того, чтобы вывести item :: T для arr :: T[] в for (const item of arr) ... , и в противном случае вывести arr[i] :: T | undefined при использовании некоторого --strict-index ? Как насчет дела, которое меня волнует, obj[key] :: V | undefined но Object.values(obj) :: V[] за obj :: { [key: string]: V } ?

@yawaramin Если вы используете разреженные массивы, Typescript уже не работает правильно. Это исправит флаг --strict-index . Это компилирует:

const arr = []
arr[7] = 7

for (let i = 0; i < arr.length; i++) {
    console.log(Math.sqrt(arr[i]));
}

@RyanCavanaugh Есть один очень распространенный пример, который я могу показать вам, когда пользователь склонен к неправильному доступу к массиву.

const getBlock = (unitNumber: string): string => unitNumber.split('-')[0]

Приведенный выше код не должен проходить корректную проверку компилятора в strictNullChecks , потому что при некоторых случаях использования getBlock будет возвращено значение undefined, например getBlock('hello') , в таких случаях я серьезно хочу скомпилируйте флаг повышения, чтобы я мог аккуратно обрабатывать неопределенные случаи, не взрывая мое приложение.

И это также относится ко многим распространенным идиомам, таким как доступ к последнему элементу массива с помощью arr.slice(-1)[0] , доступ к первому элементу arr[0] и т. Д.

В конечном счете, я хочу, чтобы TypeScript раздражал меня такими ошибками, а не имел дело с разорванными приложениями.

Передает ли эта функция неопределенное значение someFunc при втором проходе цикла или нет? Есть много вещей, которые я мог бы написать в someFunc, что привело бы к появлению undefined позже.

@RyanCavanaugh Да, JavaScript не поддерживает неизменяемость. В этом случае может потребоваться либо ! , либо массив ReadonlyArray : someFunc(arr: ReadonlyArray<number>, i: number) .

@yawaramin Для разреженных массивов тип элемента, вероятно, должен включать undefined если TypeScript не может сделать вывод, что он используется как кортеж. В коде, на который ссылается @danielnixon (https://github.com/microsoft/TypeScript/issues/13778#issuecomment-536248028), кортежи также обрабатываются особым образом и не включают undefined в возвращаемый тип элемента. поскольку компилятор гарантирует доступ только к установленным индексам.

это осознанное решение. Было бы очень неприятно, если бы этот код был ошибкой:

var a = [];
for (var i =0; i< a.length; i++) {
    a[i]+=1; // a[i] is possibly undefined
}

Эй, я узнал этот синтаксис; я пишу такие петли, может быть, раз в год!

Я обнаружил, что в большинстве случаев, когда я действительно индексирую массив, я действительно хочу впоследствии проверить undefined .

Опасения усложнить работу с этим конкретным делом кажутся преувеличенными. Если вы просто хотите перебирать элементы, вы можете использовать for .. of . Если вам по какой-то причине нужен индекс элемента, используйте forEach или переберите entries . В общем, очень редко вам действительно нужен цикл for, основанный на индексе.

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

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

Пт, 25 октября 2019 г., в 11:59. Brunnerh [email protected] написал:

это осознанное решение. было бы очень неприятно, если бы этот код
быть ошибкой:

var a = []; for (var i = 0; i <a.length; i ++) {
а [я] + = 1; // a [i] возможно не определено
}

Эй, я узнал этот синтаксис; я пишу такие петли, может быть, раз в год!

Я обнаружил, что в большинстве случаев, когда я действительно индексирую массив, я
на самом деле хочу потом проверить undefined.

Опасения усложнить работу с этим конкретным делом кажутся преувеличенными.
Если вы просто хотите перебрать элементы, которые можно использовать для .. of. Если
вам по какой-то причине нужен индекс элемента, используйте forEach или перебирайте
записи. В общем, крайне редко вам действительно нужен
цикл for на основе индекса.

Если есть более веские причины, по которым кто-то хочет сохранить статус-кво, я бы
хотелось бы их видеть, но не смотря на это: это несоответствие в системе
и, кажется, многие высоко оценят наличие флажка для его исправления.

-
Вы получили это, потому что прокомментировали.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/microsoft/TypeScript/issues/13778?email_source=notifications&email_token=ACAJU3DQ7U6Y3MUUM26J4JDQQM62XA5CNFSM4C6KEKAKYY3PNVWWK3TUL52HS443DFMVREXDDNWWK3TUL52HS443DFMVREX5WWWK3TUL52HS443DFMVREXX5XX4XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX6
или отказаться от подписки
https://github.com/notifications/unsubscribe-auth/ACAJU3EWVM3CUFG25UF5PGDQQM62XANCNFSM4C6KEKAA
.

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

@brunnerh Я согласен с вами, в настоящее время мне даже не нужно использовать циклы for если это не для настройки производительности, что происходит в 0% случаев в кодовой базе моей компании, потому что большую часть времени меняет карту / filter / reduce to цикл for редко улучшает производительность, настоящая причина всегда заключается в неэффективной логике, сетевых проблемах и соединениях с базой данных.

Я удивлен, что никто еще не говорил о as const .

const test = [1, 2, 3] as const;

(test[100]).toFixed(5);
// Tuple type 'readonly [1, 2, 3]' of length '3' has no element at index '100'.

В более общем плане, и это не совсем связано с первоначальным сообщением об этой проблеме, я использовал следующие шаблоны защитного программирования в течение последних месяцев, и они хорошо сработали (для меня)

const xs: Array<number | undefined> = [1,2,3];

// for objects but kind of related as well
Record<string, User | undefined>

interface Something {
  [key: string]: User | undefined
}

Несмотря на то, что для него нет коротких обозначений (например, ? ), я считаю, что это нормально. Сказать компилятору самому, что вы не уверены, что значение определено, - это нормально.

@martpie as const - это здорово, если вы можете его использовать.

Есть как минимум две причины, по которым я бы предпочел не использовать Array<T | undefined> :

  1. Это еще одна вещь, которую вы должны помнить каждый раз, когда используете массив. Кроме того, вы больше не можете просто использовать неявную типизацию, что неплохо.
  2. Он изменяет подписи forEach , map и filter , которые не передают undefined качестве аргумента элемента (если этот индекс не установлен таким образом явно). В зависимости от того, как часто вы используете эти функции, работа с которыми может быть неприятной.

Я также хочу отметить, что теперь это вызывает много ложных срабатываний в eslint / typescript:

const a: string[] = [];
const foo = a[1000];
if (foo) { // eslint says this is an unnecessary conditional
  console.log(foo.length);
}

Eslint (правильно) делает вывод из типа, что это ненужная проверка, потому что foo никогда не может быть нулевым. Так что теперь я не только должен сознательно не забывать выполнять нулевую проверку того, что я возвращаю из массива, я также _ также_ должен добавить строку отключения eslint! И доступ к вещам за пределами цикла for, подобный этому, является практически единственным способом, которым мы когда-либо занимаемся доступом к массиву, поскольку (как, вероятно, большинство разработчиков TS в наши дни), мы используем функции forEach / map / filter / etc при цикле по массивам.

Я действительно думаю, что нам просто нужно иметь новый флаг компилятора, который по умолчанию установлен на true, если strict истинно, и если людям это не нравится, они могут отказаться. В любом случае мы добавили новые строгие проверки компилятора в последних версиях.

Это, скорее всего, источник _only_ ошибок prod во время выполнения, которые я видел в недавней памяти, которые были отказом системы типов.

Учитывая эргономику теперь доступного дополнительного оператора цепочки, может быть, пора пересмотреть решение _не_ делать это поведение доступным с помощью флага?

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

Пример:

// `things` is `Thing[]`, but is empty, i.e., `[]`
const { things } = data; 

// We are accessing `things[-1]`, which is obviously `undefined`, 
// but TypeScript thinks `latestThing` is a `Thing`
const latestThing = things[things.length - 1];

// TypeError: Cannot read property 'foo' of undefined
return latestThing.foo; 

Возможно, это проблема с дизайном нашего API, но действительно кажется, что TS должен понимать, что, когда вы обращаетесь к чему-то в массиве, этого на самом деле может не быть.

Изменить: просто хочу уточнить, что я имел в виду «наш API», как в «API, созданном командой, над которой я работаю».

да, все в значительной степени согласны с тем, что это очень плохое дизайнерское решение, современное далеким временам, когда TS был полностью небезопасен.

В качестве обходного пути я использую небольшой грязный прием. Я просто добавляю в package.json простой сценарий постустановки:

{
...
  "scripts": {
    "postinstall": "sed -i 's/\\[n: number\\]: T;/[n: number]: T | undefined;/g' node_modules/typescript/lib/lib.es5.d.ts",
    ...
  },
...
}

Конечно, с окнами это не сработает, но это лучше, чем ничего.

Все другие проблемы, которые возникают рядом с этой, похоже, перенаправляются сюда, поэтому я предполагаю, что это лучшее место, чтобы спросить: является ли сокращенный синтаксис для необязательных индексных подписей за пределами таблицы? Как указывает @martpie , вы должны написать interface Foo { [k: string]: Bar | undefined; } что менее эргономично, чем interface Foo { [k: string]?: Bar; } .

Можем ли мы получить поддержку оператора ?: ? Если нет, то почему? То есть эргономического преимущества было достаточно, чтобы добавить функцию для определения одного свойства - разве это не «достаточно полезно» для индексных подписей?

type Foo = { [_ in string]?: Bar } тоже работает. Не так красиво, но довольно кратко. Вы также можете создать свой собственный тип Dict если хотите. Хотя не стал бы возражать против расширения ?:

Это начинает казаться одним из тех разговоров "Javascript: The Bad Parts":

ts type Foo1 = { [_ in string]?: Bar } // Yup type Foo2 = { [_: string]?: Bar } // Nope interface Foo3 { k?: Bar } // Yup interface Foo4 { [_: string]?: Bar } // Nope

Использование T | undefined для подписи типа действительно бесполезно. Нам нужен способ сделать так, чтобы оператор индекса [n] имел тип T|undefined , но, например, использование map в массиве не должно давать нам T|undefined ценности, потому что в этой ситуации мы должны знать, что они существуют.

@radix Для фактических функций в массиве, я не думаю, что это будет проблемой, потому что все они имеют свои собственные определения типов, которые выводят правильный тип: например, map : https://github.com/microsoft /TypeScript/blob/master/lib/lib.es5.d.ts#L1331

Единственное распространенное использование кода, которое конструкция | undefined представляет реальную регрессию опыта, - это циклы for ... of . К сожалению (с точки зрения данного вопроса), это довольно распространенные конструкции.

@riggs Я думаю, вы говорите о том, чтобы interface Array<T> { } имел [index: number]: T | undefined , но @radix , вероятно, говорит о том, что кажется текущей рекомендацией, а именно использовать Array<T | undefined> в ваш собственный код.

Последнее плохо по нескольким причинам, не в последнюю очередь из-за того, что вы не контролируете типы других пакетов, но у первого также есть некоторые проблемы, а именно то, что вы можете присвоить массиву undefined , и что он дает undefined даже в заведомо безопасных случаях. 🤷‍♂️

Ах да, мое недоразумение. Я действительно имел в виду только использование определения [index: number]: T | undefined . Я полностью согласен с тем, что определение типа как Array<T | undefined> - ужасный обходной путь, который вызывает больше проблем, чем решает.

Есть ли изящный способ переопределить lib.es5.d.ts или сценарий после установки - лучший способ?

@ nicu-chiciuc https://www.npmjs.com/package/patch-package Полностью вписывается в набор инструментов еретика вместе с https://www.npmjs.com/package/yalc ✌️

Есть ли изящный способ переопределить lib.es5.d.ts или сценарий postinstall - лучший способ?

В нашем проекте у нас есть файл global.d.ts который мы используем для 1) добавления определений типов для встроенных API, которых еще нет в определениях типов по умолчанию в typescript (например, API, связанные с WebRTC, которые постоянно меняются и несовместимы между браузерами. ) и 2) переопределение некоторых определений типов по умолчанию в машинописном тексте (например, переопределение типа Object.entries чтобы он возвращал массивы, содержащие unknown вместо any ).

Я не уверен, можно ли использовать этот подход для переопределения типов массивов машинописного текста для решения этой проблемы, но, возможно, стоит попробовать.

Вышеупомянутое работает, когда объединение объявлений дает желаемый результат, но для упрощения они пересекают интерфейсы, но здесь менее ограничительный вариант - это то, что вы хотите.

Вместо этого вы можете попробовать скопировать все файлы lib.*.d.ts которые вы используете, в свой проект, включенные в tsconfig.json files или include , а затем отредактировать их. к чему вы хотите. Поскольку они включают /// <reference no-default-lib="true"/> , вам не понадобится никакой другой магии, но я не уверен, нужно ли вам удалять /// <reference lib="..."/> они имеют, из их зависимостей. Это, конечно, плохо по всем очевидным причинам ремонтопригодности.

@butchler Мы также используем этот подход, но казалось, что переопределение оператора индексации невозможно, хотя мы успешно переопределили некоторые другие функции массива ( filter с защитой типов).

У меня странный случай с переопределением оператора индексации:

function test() {
  const arr: string[] = [];

  const [first] = arr;
  const zero = arr[0];

  const str1: string = first;
  const str2: string = zero;
}

Screenshot 2020-02-05 at 10 39 20 AM

Второе присваивание ошибочно (как и должно), а первое - нет.
Еще более странно то, что наведение курсора на first во время его деструктуризации показывает, что он имеет тип string | undefined но наведение курсора на него во время его назначения показывает, что он имеет тип string .
Screenshot 2020-02-05 at 10 40 25 AM
Screenshot 2020-02-05 at 10 40 32 AM

Есть ли другое определение типа для деструктуризации массива?

Ищу решение этой проблемы, поскольку ошибки, связанные с отсутствием индексов, были частым источником ошибок в моем коде.

Указание типа, такого как { [index: string]: string | undefined } , не является решением, так как это приводит к путанице при вводе итераторов, таких как Object.values(x).forEach(...) которые никогда не будут включать значения undefined .

Я хотел бы видеть, как TypeScript генерирует ошибки, когда я не проверяю undefined после выполнения someObject[someKey] , но не при выполнении Object.values(someObject).forEach(...) .

@danielnixon Это не решение, это обходной путь. Ничто не мешает вам или другому разработчику по ошибке (если это можно так назвать) использовать встроенные инструменты языка для тех же целей. Я имею в виду, что для этого я использую fp-ts, но эта проблема все еще актуальна и требует исправления.

Хотя я могу следить за аргументами «против» от @RyanCavanaugh :

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

Я хотел бы сказать несколько коротких мыслей по этому поводу. По сути, TypeScript нацелен на то, чтобы сделать разработку… ну ... более безопасной. Чтобы запомнить первую цель TypeScript:

1. Статически идентифицируйте конструкции, которые могут быть ошибками.

Если мы посмотрим на компилятор, у нас уже есть некоторая магия, стоящая за выводом типа. В конкретном случае действительно оправдание: «мы не можем вывести правильный тип для этой конструкции», и это совершенно нормально. Со временем у нас появляется все меньше и меньше случаев, когда компиляция не может определить типы. Другими словами, для меня это просто вопрос времени (и потратить на это время), чтобы получить более сложный вывод типов.

С технической точки зрения конструкции похожи на:

const x = ['a', 'b', 'c']
console.log(x[3]) // type: string, reality: undefined

нарушает первую цель TypeScript. Но этого не происходит, если TypeScript знает более точные типы:

const x = ['a', 'b', 'c'] as const
console.log(x[3]) // compile error: Tuple type 'readonly ["a", "b", "c"]' of length '3' has no element at index '3'.ts(2493)

С практической точки зрения это компромисс yet . Эта проблема и несколько закрытых проблем показывают, что сообщество пользуется большим спросом на это изменение: сторонники ATM 238 против 2 проголосовавших против. Конечно, жаль, что приведенный выше цикл for не делает вывод о "правильном" типе, но я почти уверен, что большинство голосовавших могут жить с ! и новым ? как знак внимания и заставьте его в безопасных футлярах. Но с другой стороны получить нужные типы на «опасном» доступе.

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

Это не предлагаемое решение, а просто эксперимент: я попытался изменить lib.es5.d.ts внутри моего node_modules в существующем проекте, который использует TypeScript, просто чтобы посмотреть, как бы это было, если бы у нас _did_ был компилятор вариант для этого. Я изменил Array и ReadonlyArray :

interface ReadonlyArray<T> {
  ...
  [n: number]: T | undefined; // was just T
}

interface Array<T> {
  ...
  [n: number]: T | undefined; // was just T
}

Поскольку это очень большой проект, это вызвало несколько сотен ошибок типа. Я просмотрел всего несколько из них, чтобы понять, с какими проблемами я столкнулся и насколько сложно было бы их решить, если бы для этого был вариант компилятора. Вот несколько проблем, с которыми я столкнулся:

  1. Это вызвало ошибки типов не только в нашей кодовой базе, но и в одной из наших зависимостей: io-ts. Поскольку io-ts позволяет вам создавать типы, которые вы используете в своем собственном коде, я не думаю, что можно применять эту опцию только к вашей собственной кодовой базе, а не применять ее также к типам io-ts. Это означает, что io-ts и, возможно, некоторые другие библиотеки все равно придется обновить для работы с этой опцией, даже если она была представлена ​​только как опция компилятора. Сначала я подумал, что создание этого параметра компилятора сделает это менее спорным, но если люди, которые все же решат использовать эту опцию, начнут жаловаться кучке разных авторов библиотеки на несовместимость, это может быть даже более спорным, чем просто TS 4.0. ломка сдачи.

  2. Иногда мне приходилось добавлять дополнительные защитные ограждения, чтобы исключить возможность неопределенности. Это не обязательно плохо, и в этом весь смысл этого предложения, но я упоминаю его только для полноты картины.

  3. У меня была ошибка типа внутри цикла for (let i = 0; i < array.length; i++) который перебирал произвольный Array<T> . Я не мог просто добавить защиту типа для проверки undefined , потому что сама T может включать undefined . Единственными решениями, которые я мог придумать, были: A) использовать утверждение типа или @ts-ignore чтобы заглушить ошибку типа, или B) вместо этого использовать цикл for-of. Лично я не считаю это слишком плохим (вероятно, есть несколько случаев, когда использование цикла for-of для итерации по массиву в любом случае не лучше), но это может быть спорным.

  4. Во многих случаях существующий код уже делал какое-то утверждение о значении .length а затем обращался к элементу в массиве. Теперь это вызывало ошибки типа, несмотря на проверку .length , поэтому мне пришлось либо изменить код, чтобы он не зависел от проверки .length либо просто добавить избыточную проверку !== undefined . Было бы очень хорошо, если бы TypeScript мог каким-то образом разрешить использование проверки .length чтобы избежать необходимости проверки !== undefined . Я полагаю, что на самом деле реализовать это было бы нетривиально.

  5. Некоторый код использовал A[number] для получения типа элементов универсального типа массива. Однако теперь это возвращало T | undefined вместо просто T , вызывая ошибки типа в другом месте. Я сделал помощника, чтобы обойти это:

    type ArrayValueType<A extends { [n: number]: unknown }> = (
      A extends Array<infer T> ? T :
      A extends ReadonlyArray<infer T> ? T :
      A[number] // Fall back to old way of getting array element type
    );
    

    но даже с этим помощником, облегчающим переход, это все равно большое критическое изменение. Возможно, TypeScript мог бы иметь какой-то особый случай для A[number] чтобы избежать этой проблемы, но было бы неплохо по возможности избегать таких странных особых случаев.

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

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

@butchler Я также столкнулся с некоторыми из этих проблем, я начал рассматривать something[i] как something(i) , то есть я не могу просто использовать что-то вроде if (Meteor.user() && Meteor.user()._id) {...} , поэтому я не • ожидать такого подхода к индексации массивов; Сначала мне нужно получить значение, которое я хочу проверить, из массива. Я пытаюсь сказать, что полагаясь на TS, чтобы понять, что проверка свойства length и выполнение некоторых утверждений, основанных на этом, может создать слишком большую нагрузку на систему типов. Одна вещь, которая заставила меня отложить переход (помимо того факта, что в некоторых других библиотеках также есть ошибки, как вы сказали), заключается в том, что деструктуризация массива еще не работает правильно, и деструктурированное значение будет не T | undefined а T (см. Другой мой комментарий).
Помимо этого, я думаю, что переопределение lib.es5.d.ts - хороший подход. Даже если что-то изменится в будущем, отмена изменения не добавит дополнительных ошибок, но гарантирует, что некоторые крайние случаи уже учтены.
Мы уже начали использовать patch-package для изменения определений некоторых типов в react и этот подход, кажется, работает нормально.

Я также столкнулся с некоторыми из этих проблем, я начал рассматривать что-то [i] как что-то (i), то есть я не могу просто использовать что-то вроде if (Meteor.user () && Meteor.user () ._ id) { ...}, поэтому я не ожидаю, что у меня будет такой подход для индексации массивов; Сначала мне нужно получить значение, которое я хочу проверить, из массива.

Да, я тоже использовал этот подход, когда проводил свой эксперимент. Например, код, который ранее был написан как:

if (array[i]) {
  array[i].doSomething(); // causes a type error with our modified Array types
}

пришлось изменить на что-то вроде:

const arrayValue = array[i]
if (arrayValue) {
  arrayValue.doSomething();
}

Я пытаюсь сказать, что полагаясь на TS, чтобы понять, что проверка свойства length и выполнение некоторых утверждений, основанных на этом, может создать слишком большую нагрузку на систему типов.

Наверное, правда. На самом деле может быть проще написать codemod для автоматического переписывания кода, который зависит от утверждений о .length чтобы использовать какой-либо другой подход, чем сделать TypeScript достаточно умным, чтобы делать выводы о типах на основе утверждений о .length 😛

Даже если что-то изменится в будущем, отмена изменения не добавит дополнительных ошибок, но гарантирует, что некоторые крайние случаи уже учтены.

Это хороший момент. Хотя включение undefined в типы индексов является огромным изменением, изменение в другом направлении не является критическим изменением. Код, который может обрабатывать получение T | undefined также должен иметь возможность обрабатывать T без каких-либо изменений. Это означает, например, что библиотеки могут быть обновлены для обработки случая T | undefined если есть флаг компилятора, и по-прежнему использоваться проектами, у которых этот флаг компилятора не включен.

Игнорируя массивы и на мгновение сосредотачиваясь на Record<string, T> , мой личный список желаний состоит в том, что запись разрешает только T но чтение может быть T|undefined .

declare const obj : Record<string, T>;
declare const t : T;
obj["k"] = t; //ok
obj["k"] = undefined; //error, undefined not assignable to T

//T|undefined inferred,
//since we don't know if "k2" is an "ownProperty" of obj
const v = obj["k2"];

Единственный способ сделать вышеперечисленное эргономичным - это какой-то зависимый набор текста и защита зависимого типа. Поскольку у нас их нет, добавление такого поведения вызовет всевозможные проблемы.

//Shouldn't just be string[]
//Should also be something like (keyof valueof obj)[],
//A dependent type
const keys = Object.keys(obj);

Возвращаясь к массивам, проблема в том, что подпись индекса для массивов не имеет того же предназначения, что и Record<number, T> .

Итак, вам потребуются совершенно разные охранники зависимого типа, такие как,

for (let i=0; i<arr.length; ++i) {
  //i is not just number
  //i should also be something like keyof valueof arr 
}

Итак, подпись индекса для массивов на самом деле не Record<number, T> . Это больше похоже на Record<(int & (0 <= i < this["length"]), T> (целочисленный тип с диапазоном и числами)


Итак, в то время как исходный пост просто говорит о массивах, заголовок, кажется, предлагает подписи индекса «просто» string или «просто» number . И это две совершенно разные дискуссии.

TL; DR Исходный пост и заголовок говорят о разных вещах, реализация любого из них сильно зависит (ха) от зависимой типизации.

Учитывая, что существуют такие функции, как forEach , map , filter и т. поддержка цикла по массиву с помощью обычного цикла for. У меня такое чувство, что попытка достичь этого слишком сложна. Например, поскольку i не является константой, что произойдет, если кто-то изменит значение i внутри цикла? Конечно, это крайний случай, но им нужно с этим справиться (надеюсь) интуитивно.

Однако исправление подписей индекса должно быть относительно простым (иш), как показали приведенные выше комментарии.

4. Во многих случаях существующий код уже делал какое-то утверждение о значении .length а затем обращался к элементу в массиве. Теперь это вызывало ошибки типа, несмотря на проверку .length , поэтому мне пришлось либо изменить код, чтобы он не зависел от проверки .length либо просто добавить избыточную проверку !== undefined . Было бы очень хорошо, если бы TypeScript мог каким-то образом разрешить использование проверки .length чтобы избежать необходимости проверки !== undefined . Я полагаю, что на самом деле реализовать это было бы нетривиально.

@butchler
Из любопытства, сколько из них обращались с изменяющейся переменной, а не с фиксированным номером? Потому что в последнем случае вы, вероятно, используете массив как кортеж, для которого TS отслеживает длину массива. Если вы часто используете массивы в качестве кортежей, мне было бы любопытно, как их повторный ввод в виде реальных кортежей влияет на количество ошибок.

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

Я столкнулся с одним случаем, когда я исправил ошибку типа, просто добавив as const в литерал массива, чтобы преобразовать тип переменной в кортеж. Я совершенно не знаю, насколько это распространено в нашей кодовой базе, поскольку я просмотрел только около 10 ошибок из нескольких сотен.

Я тоже сегодня столкнулся с проблемой.
Мы получаем доступ из массива через вычисленный индекс. Этот индекс может быть вне допустимого диапазона.
Итак, в нашем коде есть что-то вроде этого:

const noNext = !items[currentIndex + 1];

В результате noNext определяется как false . Что неверно. Это может быть правдой.
Я также не хочу определять items как Array<Item | undefined> потому что это дает неверное ожидание.
Если есть индекс, он никогда не должен быть undefined . Но если вы используете неправильный индекс, это _is_ undefined .
Конечно, описанное выше, вероятно, можно было бы решить, используя вместо этого проверку .length или явно указав noNext как boolean .
Но, в конце концов, это то, что меня беспокоит с тех пор, как я начал использовать TypeScript и никогда не понимал, почему | undefined не включен по умолчанию.

Поскольку я ожидаю, что большинство из них будет использовать typescript-eslint, есть ли какое-либо правило, которое могло бы обеспечить, что значения должны быть проверены после индексации, прежде чем их можно будет использовать? Это может быть полезно до того, как будет реализована поддержка TS.

На случай, если это может быть кому-то интересно. В предыдущем комментарии я упомянул, что подход исправления определения индексации Array в lib.es5.d.ts имеет странное поведение при деструктуризации массива, поскольку казалось, что это изменение не повлияло на него. Кажется, что это зависит от параметра target в tsconfig.json и работает правильно, когда это es5 (https://github.com/microsoft/TypeScript/issues/37045 ).

Для нашего проекта это не проблема, поскольку мы используем параметр noEmit а транспиляция выполняется с помощью meteor . Но для проектов, где это может быть проблемой, решение, которое может сработать, - это safe-array-destructuring (https://github.com/typescript-eslint/typescript-eslint/pull/1645).
Это все еще черновик, и он может не иметь ценности, когда вся проблема будет исправлена ​​в компиляторе TypeScript, но если вы думаете, что это может быть полезно, не стесняйтесь решать любые проблемы / улучшения в правиле typescript-eslint PR

К сожалению, исправление с помощью eslint в вашем PR поддерживает только кортежи, а не массивы. Основная проблема и основное влияние, которое я оказываю на это, - это деструктуризация массива.

const example = (args: string[]) => {
  const [userID, nickname] = args
}

Я думаю, что вся эта проблема зашла в сторону, с которой я не согласен. Я не думаю, что должны быть принудительные проверки внутри forEach, map и т. Д. Я также стараюсь избегать любого использования for, когда это возможно, поэтому рассуждения, позволяющие избежать этого, для меня менее понятны. Тем не менее, я по-прежнему считаю, что это очень важно для поддержки деструктуризации массива.

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

Разве это не плохо? Деструктуризация массива - удивительная особенность. Проблема в том, что набор текста не точен. Отключение этой функции на самом деле не лучшее решение. На мой взгляд, использование индексов является уродливым (сложнее для чтения и понимания) кодом и более подвержено ошибкам.

Это более уродливо, но заставляет разработчика проверять, определен ли элемент или нет. Я пытался придумать более элегантные решения, но, похоже, именно у него меньше всего минусов. По крайней мере, для нашего варианта использования.

@caseyhoward Как отмечалось ранее в этом выпуске, это вызывает нежелательное поведение с различными функциями Array.prototype:

x.forEach( (i: string) => { ... } )  // Error because i has type string | undefined

Это не требует исправления в функциях array.prototype. Это серьезная проблема для деструктуризации массива!

Снова наткнулся на это сегодня. Я до сих пор думаю , что правильный компромисс это , который я хотел бы получить обратную связь о.

Другой вариант использования этой проблемы - я столкнулся с этой проблемой при использовании typescript-eslint и включении no-unnecessary-condition . Раньше при доступе к массиву по индексу для выполнения некоторой операции с элементом в этом индексе мы использовали необязательную цепочку, чтобы гарантировать, что этот индекс определен (в случае, если индекс выходит за границы), как в array[i]?.doSomething() . Однако с проблемой, описанной в этой проблеме, no-unnecessary-condition помечает эту необязательную цепочку как ненужную (поскольку тип не допускает значения NULL в соответствии с машинописным текстом), а автоисправление удаляет необязательную цепочку, что приводит к ошибкам времени выполнения при доступе к массиву в индекс i фактически не определен.

Без этой функции мое приложение стало очень глючным, так как я имею дело с многомерным массивом, я всегда должен напоминать себе о доступе к элементам, используя xs[i]?.[j] вместо xs[i][j] , также я должен явно приводить элемент, к которому осуществляется доступ, например const element = xs[i]?.[j] as Element | undefined чтобы обеспечить безопасность типов.

Встреча с этим была моей первой большой глупостью в Typescript. В остальном язык я нахожу удивительно надежным, и это меня подвело, и довольно громоздко добавлять «as T | undefined» к доступам к массивам. Очень хотелось бы, чтобы одно из предложений было реализовано.

Ага, здесь то же самое. Это заставило нас развернуть наши собственные типы с ( | undefined ) для обеспечения безопасности типов. В основном для доступа к объектам (вероятно, это отдельная открытая проблема), но та же логика применяется к доступу к индексам массивов.

Не уверены, что вы имеете в виду, можете ли вы связать проблему или показать пример?

В среду, 29 апреля 2020 г., в 2:41 Кирилл Грошков [email protected]
написал:

Ага, здесь то же самое. Это заставило нас развернуть наши собственные типы с (|
undefined) для обеспечения безопасности типов. В основном для доступа к объектам (вероятно, это
отдельная открытая проблема), но та же логика применяется к доступу к индексам массива.

-
Вы получили это, потому что прокомментировали.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-621096030 ,
или отказаться от подписки
https://github.com/notifications/unsubscribe-auth/AAAGFJJT37N54I7EH2QLBYDRO7Y4NANCNFSM4C6KEKAA
.

@alangpierce

Одна идея, которая кажется многообещающей: что, если бы вы могли использовать ?: при определении подписи индекса, чтобы указать, что вы ожидаете, что значения иногда будут отсутствовать при нормальном использовании? Он будет действовать как | undefined упомянутый выше, но без неудобных недостатков. Потребуется запретить явные значения undefined , что, как я полагаю, отличается от обычного ?: .

Это интересная идея, указывающая в правильном направлении. Однако я бы изменил логику: по умолчанию подписи индекса должны включать | undefined . Если вы хотите сообщить компилятору, что неопределенный случай никогда не может возникнуть в вашем коде (чтобы вы никогда не получили доступ к недопустимым ключам), вы можете добавить !: в подпись, например:

type AlwaysDefined = {[key: string]!: string};
const t: AlwaysDefined = {};

t['foo'] // Has type string

и вариант по умолчанию без !: будет выглядеть так, как будто мы его знаем, но будет безопаснее:

type MaybeUndefined = {[key: string]: string};
const t: MaybeUndefined = {};

t['foo'] // Has type string | undefined

Таким образом, у вас есть безопасность по умолчанию, и точно так же вам не нужно явно включать undefined что позволило бы случайно написать undefined .

Извините, если это уже было предложено, я не смог прочитать все комментарии в этой ветке.

В общем, я действительно думаю, что поведение по умолчанию, которое мы имеем сейчас, не соответствует ожиданиям пользователей при использовании TypeScript. Лучше перестраховаться, чем сожалеть, особенно когда ошибку может обнаружить компилятор так же легко, как эту.

Я не мог заставить index.d.ts работать с моим проектом и действительно не хотел иметь с ним дело.

Я создал этот небольшой хак, который заставляет охранять шрифты:

 const firstNode = +!undefined && nodes[0];

Хотя тип заканчивается как 0 | T , он по-прежнему выполняет свою работу.

Разве const firstNode = nodes?.[0] сработает?

@ricklove, по какой причине вы предпочли +!undefined простому 1 ?

Разве const firstNode = nodes?.[0] сработает?

Нет, поскольку машинописный текст (на мой взгляд, это неправильно) не рассматривает его как необязательный (см. # 35139).

Согласно документации Flow, поведение с индексными подписями такое же, как в настоящее время с TypeScript:

Когда тип объекта имеет свойство индексатора, предполагается, что доступ к свойствам имеет аннотированный тип, даже если объект не имеет значения в этом слоте во время выполнения. Как и в случае с массивами, ответственность за обеспечение безопасного доступа лежит на программисте.

var obj: { [number]: string } = {};
obj[42].length; // No type error, but will throw at runtime

Таким образом, в этом отношении и TS, и Flow требуют, чтобы пользователь думал о неопределенных ключах вместо того, чтобы компилятор им помогал :(

Было бы преимуществом TS, если бы компилятор не позволял пользователям делать подобные ошибки. Читая все комментарии в этой ветке, кажется, что подавляющее большинство здесь хотели бы иметь эту функцию. Команда TS все еще на 100% против?

Кажется глупым, что TS делает все возможное, чтобы предотвратить доступ к неопределенным членам, кроме этого. Это, безусловно, самая распространенная ошибка, которую я исправлял в нашей кодовой базе.

[перенос обсуждения сюда из # 38470]
Принятие всех аргументов в пользу того, что это нецелесообразно для массивов ..
Я думаю, что это обязательно нужно решить для кортежей:

// tuple 
var str = '';
var num = 100
var aa = [str, num] as const

// Awesome
var shouldBeString = aa[0] // string
var shouldBeNumber = aa[1] // number
var shouldError = aa[10000]; // type error

// Not so awesome 
var foo = aa[num] // string | number

почему бы не сделать foo string | number | undefined ?

Передача этого кортежам не должна влиять на большинство пользователей, а пользователям, на которых это влияет, необходимо включить strictNullChecks и объявить свои массивы как константы ...
Очевидно, они ищут более строгие меры безопасности.

может также помочь с этими

var notString1 = aa[Infinity]; // no error, not undefined
var notString2 = aa[NaN]; // no error, not undefined

Изначально это вызвало мою ошибку выполнения, поскольку number превратилось в NaN , а затем вернуло undefined из кортежа ..
Все это было безопасным по типу

Не уверен, что кортежи действительно такие разные. Они должны учитывать типы отдыха (например, [str, ...num[]] ), и технически aa[Infinity] - это совершенно допустимый Javascript, поэтому я мог видеть, что становится сложно выделить для них особый случай.

Ваш пост также заставил меня задуматься о том, как бы он выглядел, если бы мы получили некоторую поддержку для обработки возвращаемых индексов как неопределенных. Если, как в вашем примере, aa[num] действительно вводится как string | number | undefined , мне придется писать for (let i = 0; i < 2; i++) { foo(aa[i]!); } даже если я знаю, что индекс останется в границах? Для меня, когда строгие проверки на null помечают что-то, что я написал, мне нравится иметь возможность исправить это, либо набрав что-то лучше в первую очередь, либо используя средства защиты во время выполнения - если мне нужно прибегнуть к ненулевому утверждению, я обычно рассматривать это как провал с моей стороны. Однако в данном случае я не вижу выхода, и это меня беспокоит.

Если tuple[number] всегда набирается как T | undefined , как бы вы поступили в случае, когда вы знаете, что индекс ограничен правильно?

@ thw0rted Я не могу вспомнить, когда в последний раз использовал "классический" цикл for в Typescript ( .map и .reduce ftw). Вот почему вариант компилятора для этого был бы отличным imo (который может быть отключен по умолчанию). Многие проекты никогда не сталкиваются с чем-то похожим на тот, который вы указали, и используют только подписи индексов для деструктуризации массива и т. Д.

(мой оригинальный комментарий: https://github.com/microsoft/TypeScript/issues/13778#issuecomment-517759210)

О, честно говоря, я также редко индексирую с помощью цикла for. Я широко использую Object.entries или Array#map , но время от времени мне нужно передавать ключи для индексации объекта или (что еще реже) индексов в массив или кортеж. Цикл for , безусловно, был надуманным примером, но он поднимает вопрос о том, что любой вариант ( undefined или нет) имеет недостатки.

но возникает вопрос, что любой вариант ( undefined или нет) имеет недостатки.

Да, и было бы неплохо, если бы пользователь TypeScript мог выбирать, какой недостаток он предпочитает: wink:

О, честно говоря, я также редко индексирую с помощью цикла for. Я широко использую Object.entries или Array#map , но время от времени мне нужно передавать ключи для индексации объекта или (что еще реже) индексов в массив или кортеж.

В тех случаях, когда это необходимо, вы можете использовать Array#entries :

for (const [index, foo] of array.entries()) {
    bar(index, foo)
}

Я лично не использую Array#forEach часто, и я никогда не использую Array#map для итерации (хотя я использую его все время для отображения). Я предпочитаю, чтобы мой код был плоским, и я ценю возможность выйти из цикла for ... of.

Не уверен, что кортежи действительно такие разные. Они должны учитывать типы отдыха (например, [str, ...num[]] ), и технически aa[Infinity] - это совершенно допустимый Javascript, поэтому я мог видеть, что становится сложно выделить для них особый случай.

Ваш пост также заставил меня задуматься о том, как бы он выглядел, если бы мы получили некоторую поддержку для обработки возвращаемых индексов как неопределенных. Если, как в вашем примере, aa[num] действительно вводится как string | number | undefined , мне придется писать for (let i = 0; i < 2; i++) { foo(aa[i]!); } даже если я знаю, что индекс останется в границах? Для меня, когда строгие проверки на null помечают что-то, что я написал, мне нравится иметь возможность исправить это, либо набрав что-то лучше в первую очередь, либо используя средства защиты во время выполнения - если мне нужно прибегнуть к ненулевому утверждению, я обычно рассматривать это как провал с моей стороны. Однако в данном случае я не вижу выхода, и это меня беспокоит.

Если tuple[number] всегда набирается как T | undefined , как бы вы поступили в случае, когда вы знаете, что индекс ограничен правильно?

Я не уверен, что столько for(let i..) перебирают кортежи ..
Если кортеж имеет один тип, тогда пользователь может использовать массив,

По моему опыту, кортежи отлично подходят для смешанных типов, и если это общий случай, то вы все равно проверяете тип.

var str = '';
var num = 100
var aa = [str, num] as const
for (let i = 0; i < aa.length; i++) {
 aa[i] // needs some type check anyway to determine if 'string' or 'number'
}

Если предположить, что это предложенное изменение произойдет, что мешает людям это сделать?

// this sucks
for (let i = 0; i < 2; i++) { 
   foo(aa[i]!); // ! required
}

// this would work
for (let i = 0 as 0 | 1; i < 2; i++) { 
   foo(aa[i]); //  ! not required 
}

вы можете использовать keyof typeof aa вместо 0 | 1 когда они исправят эту проблему

Конечно, при выполнении арифметики i не будет никакой безопасности типов,
но это действительно позволяет людям выбирать это для обеспечения безопасности типов по сравнению с массивом по умолчанию "Не строго типобезопасный, но удобный"

Я думаю, что наличие операторов ?? и ?. означает, что произвольный доступ к Array s больше не является чрезмерно громоздким .
Но также, вероятно, гораздо чаще

  • итерация (например, с .map или .forEach , где нет необходимости в " T | undefined ", обратный вызов никогда не выполняется с индексами за пределами границ) или
  • пройти по циклам Array in, некоторые из которых могут быть статически определены как безопасные (почти for...of .., за исключением _sparse array_, for...in я считаю безопасным).

Кроме того, переменная может считаться «безопасным индексом», если она сначала проверяется с помощью <index> in <array> .


_Уточнение_: я говорю об исходной причине того, что Array<T> не имеет типа индекса [number]: T | undefined (а вместо этого просто [number]: T ), которая заключалась в том, что его было бы слишком громоздко использовать.
Я имею в виду, что это уже не так, поэтому можно добавить undefined .

@ vp2177 Проблема не в функциях (да, ?. работает), проблема в том, что компилятор не предупреждает нас об этом и не дает себе выстрелить в ногу.

Может быть, с этим справится правило линтинга, но все же.

да, ?. работает

Позволю себе не согласиться. ?. будет работать для доступа к свойствам индексированного значения, но компилятор все равно скажет, что значение определено независимо от того, есть оно или нет (см. # 35139).

В дополнение к этому, если вы используете eslint и хотите использовать no-unnecessary-condition , такие обращения будут помечены как ненужные и удалены, потому что компилятор говорит, что он никогда не является неопределенным, и это сделает ?. ненужным.

@martpie Извините, я думаю, вы неправильно поняли мой комментарий, добавил пояснение.

Я имею в виду ... кажется хорошей идеей иметь --strictIndexChecks или аналогичный, который позволяет людям выбирать этот тип поведения. Тогда это не будет критическим изменением для всех, и это дает более строгую среду шрифтов.

Я действительно считаю, что Flow работает таким образом по умолчанию для индексов и не имеет вышеупомянутых проблем, но прошло некоторое время с тех пор, как я его использовал, поэтому я могу ошибаться.

@bradennapier Flow не использует T | undefined ни в сигнатуре индекса для Array s (к сожалению, как и TypeScript).

Ранее я предлагал подход, который может сделать всех счастливыми. Надеюсь, можно повторить это другими словами.

В основном поведение подписи индекса по умолчанию будет изменено на

  • Включать | undefined при чтении из массива или объекта
  • Не включайте | undefined при записи (потому что нам нужны только значения T в нашем объекте)

Определение было бы таким:

type MaybeUndefined = {[key: string]: string};
const t: MaybeUndefined = {};

const x = t['foo'] // Has type string | undefined
t['foo'] = undefined // ERROR! 
t['foo'] = "test" // Ok

Для людей, которые не хотят, чтобы undefined включался в тип, они могут либо отключить его с помощью параметра компилятора, либо отключить его только для некоторых типов с помощью оператора ! :

type AlwaysDefined = {[key: string]!: string};
const t: AlwaysDefined = {};

const x = t['foo'] // Has type string
t['foo'] = undefined // ERROR! 
t['foo'] = "test" // Ok

Чтобы не нарушать существующий код, также может быть лучше ввести новую опцию компилятора, которая приводит к включению undefined (как показано с типом MaybeUndefined выше). Если этот параметр не указан, все типы подписи индекса ведут себя так, как если бы в объявлении использовался оператор ! .

Возникает еще одна идея: можно использовать один и тот же тип в некоторых местах кода с двумя разными поведениями (включая undefined или нет). Можно определить новое сопоставление типов:

type MakeDefined<T> = {[K in keyof T]!: T[K]}

Будет ли это хорошим расширением TypeScript?

Мне нравится эта идея, но я подозреваю, что нам нужно сначала получить https://github.com/microsoft/TypeScript/issues/2521 - что, не поймите меня неправильно, я все еще считаю, что мы обязательно должны , но я ' я не затаив дыхание.

@bradennapier Flow не использует T | undefined ни в сигнатуре индекса для Array s (к сожалению, как и TypeScript).

Хм, а как насчет объектных индексов? Я никогда не сталкивался с необходимостью в массивах, но совершенно уверен, что это заставляет вас проверять при использовании объектов.

@bradennapier Индексы объектов? Например, o[4] где o - это тип object ? TypeScript не позволяет вам этого делать ...

выражение типа '4' не может использоваться для индексации типа '{}'
Свойство '4' не существует для типа '{}'. Ts (7053)

@RDGthree, чтобы не быть белым рыцарем для Microsoft всего, но вы, очевидно, пропустили этот фрагмент от mhegazy в первом ответе:

За исключением strictNullChecks, у нас нет флагов, изменяющих поведение системы типов. флаги обычно включают / выключают отчеты об ошибках.

а чуть позже Райан Кавано написал:

Мы по-прежнему весьма скептически относимся к тому, что кто-то получит какую-либо пользу от этого флага на практике. Карты и подобные карты уже могут подписаться на | undefined на их сайтах определений, и применение EULA-подобного поведения при доступе к массиву не похоже на победу. Скорее всего, нам нужно будет существенно улучшить CFA и шрифтовую защиту, чтобы сделать это приятным на вкус.

Итак, в основном - они прекрасно осведомлены о том, что запрашивается флаг, и пока они не были убеждены, что им стоит потрудиться ради довольно сомнительной выгоды, которую, как предполагалось, люди могли бы получить от этого. Честно говоря, я не удивлен, поскольку все продолжают приходить сюда с почти теми же предложениями и примерами, которые уже были рассмотрены. В идеале вы должны просто использовать for of или методы для массивов и Map вместо объектов (или, что менее эффективно, значения объектов 'T | undefined`).

Я здесь, потому что я поддерживаю популярный пакет DT, и люди все время просят добавить |undefined к таким вещам, как карты заголовков http, что вполне разумно, за исключением того момента, когда он нарушит практически все существующие способы использования. и тот факт, что он делает другие безопасные способы использования, такие как Object.entries() намного хуже в использовании. Помимо жалоб на то, что ваше мнение лучше, чем мнение создателей TypeScript, какой на самом деле ваш вклад здесь?

Саймон, возможно, я неправильно прочитал ваш комментарий, но это звучит как аргумент в пользу исходного предложения. Отсутствие возможности «последней мили» - иногда , в зависимости от контекста, обрабатывать свойства как | undefined , но не в других случаях. Поэтому я провел аналогию с аптредом # 2521.

В идеальном сценарии я мог бы объявить массив или объект таким образом, чтобы, учитывая

ts const arr: Array<T>; const n: number; const obj: {[k: K]: V}; const k: K;

Я могу как-нибудь закончить

  • arr[n] типы как T | undefined
  • arr[n] = undefined - ошибка
  • У меня есть доступ к некоторой итерации на arr которая дает мне значения, набранные как T , а не T | undefined
  • obj[k] типы как V | undefined
  • obj[k] = undefined - ошибка
  • Object.entries() например, должен дать мне кортежи [K, V] и ничего не может быть неопределенным

На мой взгляд, именно асимметричный характер проблемы определяет эту проблему.

Я бы сказал, что утверждение о том, что это проблема разработчиков, использующих циклы for, не принимает во внимание разработчиков, использующих деструктуризацию массивов. Он сломан в Typescript и не будет исправлен из-за этой проблемы. Он обеспечивает неточный набор текста. Осуществляемая деструктуризация массива IMO - гораздо большая проблема, чем те, которые все еще используют для циклов.

const example = (args: string[]) => {
  const [userID, duration, ...reason] = args
  // userID and duration is AUTOMATICALLy inferred to be a string here. 
 // However, if for whatever reason args is an empty array 
// userID is actually `undefined` and NOT a `string`. 

// This is valid but it should not be because userID could be undefined
userID.toUpperCase()
}

Я не хочу уходить слишком далеко от исходной точки проблемы, но вам действительно следует объявить этот примерный метод, используя тип кортежа для аргумента функции. Если бы функция была объявлена ​​как ([userId, duration, ...reason]: [string, number, ...string[]]) => {} тогда вам вообще не пришлось бы об этом беспокоиться.

Я имею в виду, что я мог бы просто использовать это, но это не совсем решение. Точно так же любой здесь может просто привести индексирование массива цикла for, а также отключить всю эту проблему.

for (let i = 0; i < array.length; i++) {
  const value = array[i] as string | undefined
}

Проблема в том, что Typescript не предупреждает об этом. Как разработчик, мы должны не забыть вручную преобразовать их как undefined, что является плохим поведением для инструмента и требует дополнительных усилий для разработчиков. Также, чтобы быть справедливым, на самом деле актерский состав должен был выглядеть примерно так:

const example = (args: [string | undefined, string | undefined, ...string[] | ...undefined[]]) => {

}

Это совсем не хорошо. Но, как я уже сказал выше, основная проблема даже не в том, чтобы вводить его вот так, это то, что TS вообще не предупреждает об этом. Это приводит к тому, что разработчики забывают об этом, чтобы отправить код, крутой CI не обнаружил проблем с tsc merge и deploy. TS дает чувство уверенности в коде, а наличие неточного набора текста дает ложное чувство уверенности. => Ошибки во время выполнения!

Это не "приведение", это правильный ввод ваших формальных параметров. Я хотел сказать, что если вы введете свой метод правильно, то любой, кто называл его небезопасно со старой подписью ( args: string[] ), теперь будет получать ошибки во время компиляции, как и должно , когда они не гарантируют передать правильное количество аргументов. Если вы хотите, чтобы люди не могли передать все необходимые аргументы и самостоятельно проверять их во время выполнения, на самом деле очень легко написать как (args: [string?, number?, ...string[]]) . Это уже отлично работает и не имеет отношения к этой проблеме.

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

Невозможно обойтись без неточного ввода для деструктуризации массива.

Я не хочу уходить слишком далеко от исходной точки проблемы

Примечание. Хотя я согласен с вами, они должны рассматриваться как отдельные проблемы, но каждый раз, когда кто-то открывает проблему, показывающую проблемы с деструктуризацией массива, она закрывается, и пользователи перенаправляются сюда. # 38259 # 36635 # 38287 и т. Д. Я не думаю, что мы отклоняемся от темы этого номера.

Некоторые из моих разочарований по поводу этой проблемы можно было бы разрешить с помощью простого синтаксиса, применяя | undefined к типу, который в противном случае был бы выведен из доступа к объекту. Например:

const complexObject: { [key: string]: ComplexType } = { ... };
const maybeKey = 'two';

// From:
const maybeValue = complexObject[maybeKey] as
  | (Some<Complex<Type>> & Pick<WithPlentyOf, 'Utility'> & Types)
  | undefined;

// To:
const maybeValue2 = complexObject[maybeKey] as ?;

Таким образом, более строгая проверка типов остается включенной. Приведенное ниже работает, но требует дублирования (и быстро добавляет аналогичное количество визуального шума):

const maybeValue2 = complexObject[maybeKey] as
  | typeof complexObject[typeof maybeKey]
  | undefined;

Я полагаю, что подобный синтаксис типа «утверждение типа может быть» ( as ? ) также упростил бы реализацию правила require-safe-index-signature-access eslint, которое не сильно сбивает с толку новых пользователей. (Например, «Вы можете исправить ошибку lint, добавив as ? в конце.») Правило может даже включать безопасный автофиксатор, так как он может только привести к сбою компиляции, но не к проблемам во время выполнения.

function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

Я не думаю, что есть какое-то применение этого, кроме этой неожиданной функции TS, хотя хе-хе

function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

Спасибо за ответ. Хотя такая функция будет работать (и почти наверняка будет оптимизирована большинством JS-движков), я бы предпочел принять упомянутый дополнительный синтаксический шум ( as ComplexType | undefined ), чем сохранить в скомпилированном JS-файле оболочку этого метода. "где бы он ни использовался.

Другой вариант синтаксиса (который будет менее специфичным для конкретного случая использования, чем as ? выше) может заключаться в том, чтобы разрешить использование ключевого слова infer в утверждениях типа в качестве заполнителя для предполагаемого в противном случае типа, например:

const maybeValue = complexObject[maybeKey] as infer | undefined;

В настоящее время объявления infer разрешены только в предложении extends условного типа ( ts(1338) ), поэтому ключевое слово infer имеет значение в контексте as [...] Утверждения типа

Это все еще может быть разумным для принудительного использования infer | undefined через правило eslint, будучи достаточно общим для других случаев, например, когда результирующий тип может быть уточнен с помощью типов утилит:

// Strange example, maybe from an unusual compiler originally written in JS:
if(someRegularExpression.test(maybeKey)) {
  /**
  * If we are inside this code block and this `maybeKey` exists on `partialObject`, 
  * we know its `regexSuccess` must also be defined, though this knowledge 
  * cannot be encoded using TypeScript's type system.
  */
  const maybeValue = partialObject[maybeKey] as (Required<Pick<infer, 'regexSuccess'>> & infer) | undefined;
  // ...
}
// Here we don't know if `regexSuccess` is defined on `maybeValue2`:
const maybevalue2 = partialObject[maybeKey] as infer | undefined;
function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

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

Наше текущее поведение таково, что выражение типа Array<string>[number] интерпретируется как «Тип, который возникает при индексировании массива по числу». Вам разрешено писать

const t: Array<string>[number] = "hello";

как обходной способ написать const t: string .

Если этот флаг существовал, что должно было произойти?

Если Array<string>[number] равно string | undefined , тогда у вас есть проблема со стабильностью записи:

function write<T extends Array<unknown>>(arr: T, v: T[number]) {
    arr[0] = v;
}
const arr = ["a", "b", "c"];
// Would be OK
write(arr, undefined);

Если Array<string>[number] равно string , то у вас та же проблема, которая описана при чтении:

function read<T extends Array<unknown>>(arr: T): T[number] {
    return arr[14];
}
const arr = ["a", "b", "c"];
// Would be OK
const k: string = read(arr);

Кажется слишком сложным добавить синтаксис типа как для «Тип чтения индекса», так и для «Тип записи индекса». Мысли?

Я должен добавить, что изменение значения Array<string>[number] действительно проблематично, поскольку это будет означать, что этот флаг изменяет интерпретацию файлов объявлений. Это нервно, потому что это похоже на вид флага, который может включить значительное количество людей, но может привести к возникновению / не возникновению ошибок в вещах, опубликованных из DefinficTyped, поэтому нам, вероятно, придется убедиться, что все строится чисто в обе стороны. Количество флагов, которые мы можем добавить с таким поведением, очевидно, чрезвычайно мало (мы не можем CI протестировать 64 различных конфигурации каждого пакета типов), поэтому я думаю, что нам придется оставить Array<T>[number] как T просто чтобы избежать этой проблемы.

@RyanCavanaugh В вашем примере с write(arr, undefined) вызов write будет принят, но не перестанет ли компилироваться присвоение arr[0] = v; ?

Если функция write приведенная выше, была получена из библиотеки и была JS, которая была скомпилирована без флага, то она была бы несостоятельной. Однако это уже существующая проблема, потому что библиотеки могут быть скомпилированы с любыми флагами, которые они выбирают. Если библиотека была скомпилирована без строгих проверок на null, то функция в ней могла бы вернуть string , тогда как на самом деле она должна возвращать string|undefined . Но в наши дни большинство библиотек, похоже, реализуют нулевые проверки, иначе люди жалуются. Точно так же, как только этот новый флаг появится, мы надеемся, что библиотеки начнут его реализовывать, и в конечном итоге в большинстве библиотек он будет установлен.

Кроме того, хотя я не могу найти никаких конкретных примеров в голове, мне определенно пришлось установить skipLibCheck , чтобы мое приложение скомпилировалось. Это довольно стандартный параметр tsconfig в нашем шаблоне при создании нового репозитория TS, потому что у нас было так много проблем с ним.

Лично (и, может быть, эгоистично), я нормально берусь за крайние случаи, если это делает мой код в целом более безопасным. Я все еще верю, что при доступе к массивам по индексу я столкнулся бы с нулевыми указателями чаще, чем с другими крайними случаями, хотя меня можно было убедить в обратном.

Если массив[число] - строка | undefined, то у вас есть проблема со стабильностью записи:

На мой взгляд, Array<string>[number] всегда должно быть string|undefined . Это реальность, если я проиндексирую массив с любым числом, я получу либо тип элемента массива, либо undefined. Вы не можете быть более конкретным, если не кодируете длину массива, как это было бы с кортежами. Ваш пример для записи не будет проверять тип, потому что вы не можете назначить string|undefined индексу массива.

Кажется слишком сложным добавить синтаксис типа как для «Тип чтения индекса», так и для «Тип записи индекса». Мысли?

Кажется, это именно то, что должно быть, поскольку это две разные вещи. Никакой массив никогда не будет иметь индекса, поэтому вы всегда можете получить значение undefined. Тип Array<string>[number] будет string|undefined . Чтобы указать, что вы хотите от T в Array<T> , можно использовать служебный тип (название не очень хорошее): ArrayItemType<Array<string>> = string . Это не помогает с типами Record , для которых может потребоваться что-то вроде RecordValue<Record<string, number>, string> = string .

Я согласен с тем, что здесь нет отличных решений, но я почти уверен, что предпочел бы правильность чтения индекса.

Я не особо уверен в необходимости этого для массивов, поскольку многие другие языки (включая «безопасные», такие как Rust) оставляют бремя проверки границ для пользователя, и поэтому я и многие разработчики уже привыкли это делать. . Кроме того, в этих случаях синтаксис имеет тенденцию делать это довольно очевидным, потому что они _ всегда_ используют обозначение скобок, например foo[i] .

Тем не менее, я настоятельно рекомендую добавлять это к объектам, индексируемым по строкам, потому что очень сложно определить, правильна ли подпись foo.bar (из-за явного определения поля bar ), или, возможно, undefined (потому что это часть подписи индекса). Если этот случай (который, на мой взгляд, имеет большое значение) будет решен, то случай с массивом, вероятно, станет тривиальным и, вероятно, тоже стоит того.


Кажется слишком сложным добавить синтаксис типа как для «Тип чтения индекса», так и для «Тип записи индекса». Мысли?

Синтаксис средств получения и установки из javascript может быть расширен до определений типов. Например, TypeScript полностью понимает следующий синтаксис:

const foo = {
  _bar: "",
  get bar(): string {
    return this._bar;
  },
  set bar(value: string) {
    this._bar = value;
  }
}

Однако TS в настоящее время «объединяет» их в один тип, поскольку он не отслеживает типы «получение» и «установка» поля по отдельности. Этот подход работает для большинства распространенных случаев, но имеет свои недостатки, такие как требование, чтобы геттеры и сеттеры использовали один и тот же тип, и неправильное назначение типа получателя полю, которое определяет только установщик.

Если бы TS отслеживал "получение" и "установку" отдельно для всех полей (что, вероятно, имеет некоторые реальные затраты на производительность), это решило бы эти причуды, а также предоставило бы механизм для описания функции, описанной в этой проблеме:

type foo = {
  [key: string]: string
}

по сути станет сокращением для:

type foo = {
  get [key: string](): string | undefined;
  set [key: string](string): string;
}

Я должен добавить, что изменение значения массива[число] действительно проблематично, поскольку это будет означать, что этот флаг изменяет интерпретацию файлов объявлений.

Если в сгенерированных файлах объявлений всегда использовался полный синтаксис «геттер + сеттер», это было бы _ только_ проблемой для рукописных файлов. Решением здесь может быть применение текущих правил к оставшемуся сокращенному синтаксису _ только в файлах определений_. Это, безусловно, решит проблемы совместимости, но также увеличит умственные затраты на чтение файлов .ts сравнению с файлами .d.ts .


В стороне:

Конечно, это не касается _все_ тонких оговорок, связанных с геттерами / сеттерами:

foo.bar = "hello"

// TS assumes that bar is now a string, which technically isn't guaranteed when
// custom setters and getters are used.
const result: string = foo.bar

Это еще один крайний случай, и, вероятно, стоит подумать, входит ли он вообще в рамки целей TS ... но в любом случае его, вероятно, можно было бы решить отдельно, поскольку мое предложение здесь согласуется с текущим поведением TypeScript.

Я действительно чувствую, что мне просто нужно заглядывать через каждую пару страниц комментариев и снова ставить ссылку на # 2521.

Кажется слишком сложным добавить синтаксис типа как для «Тип чтения индекса», так и для «Тип записи индекса». Мысли?

Неа! И по крайней мере (щелкает ссылку ...) 116 других здесь согласны со мной. Я был бы так счастлив, по крайней мере, иметь возможность печатать читает и пишет по-другому. Это еще один отличный вариант использования этой функции.

Я был бы _ так счастлив_ иметь хотя бы возможность печатать читает и пишет по-разному. Это просто еще один отличный вариант использования этой функции.

Я считаю, что целью было спросить, должен ли быть способ ссылки на типы чтения и записи, например, должно ли быть что-то вроде Array<string>[number]= для ссылки на тип записи?

Я был бы рад разрешению как # 2521, так и этой проблемы, если произойдут две вещи:

  • Во-первых, введите геттеры и сеттеры по-разному, как указано в # 2521.
  • Затем создайте флаг, как описано в этой проблеме, так что «тип записи» Array<string>[number] равен string а «тип чтения» - string | undefined .

Я (ошибочно?) Предполагаю, что первое заложит основу для второго, см. Https://github.com/microsoft/TypeScript/issues/13778#issuecomment -630770947. У меня нет особой потребности в явном синтаксисе для определения «типа записи».

Быстрый мета-вопрос @RyanCavanaugh : было бы более полезным иметь мое довольно конкретное предложение выше в качестве отдельной проблемы с целью более четкого обсуждения и отслеживания его сильных / слабых сторон / затрат (особенно, если затраты на выполнение будут нетривиальными)? Или это только добавит шума?

@RyanCavanaugh

const t: Array<string>[number] = "hello";

как обходной способ написать const t: string .

Я думаю, это то же самое для кортежей?

const t : [string][number] = 'hello' // const t: string

однако кортежи знают о границах и правильно возвращают undefined для более конкретных типов чисел:

const t0: [string][0] = 'hello' // ok
const t1: [string][1] = 'hello' // Type '"hello"' is not assignable to type 'undefined'.

// therefore this is already true!
const t2: [string][0|1] // string | undefined 

почему тип number действовать иначе?

Похоже, у вас есть механизмы для этого: https://github.com/microsoft/TypeScript/issues/38779

Это удовлетворило бы мои потребности без добавления дополнительного флага.

Если это кому-то поможет, вот плагин ESLint, который отмечает небезопасный доступ к массиву и деструктуризацию массива / объекта. Он не отмечает безопасное использование, такое как кортежи и (не записываемые) объекты.

https://github.com/danielnixon/eslint-plugin-total-functions

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

@RyanCavanaugh

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

Просто любопытно, не могли бы вы рассказать нам, что вам не нравится ? Возможно, мы сможем разобраться, если вы расскажете об этом подробнее.

Это не должно быть вариантом, это должно быть по умолчанию .

То есть, если система типов TypeScript должна описывать во время компиляции типы, которые значения JavaScript могут принимать во время выполнения:

  • индексирование Array конечно, может вернуть undefined в JavaScript (индекс вне границ или разреженный массив), поэтому правильная подпись чтения будет T | undefined .
  • "превращение" индекса в Array в undefined также возможно с помощью ключевого слова delete .

Поскольку TypeScript 4 предотвращает

const a = [1, 2]
delete a[1]

есть хороший случай также предотвратить a[1] = undefined .
Это говорит о том, что Array<T>[number] действительно должно отличаться для чтения и записи.

Это может быть «сложно», но это позволит более точно моделировать возможности выполнения в JavaScript;
Для чего был создан TypeScript, верно?

Нет смысла обсуждать идею заставить | undefined возвращать поведение по умолчанию - они никогда не выпустят версию Typescript, которая просто взорвет миллионы строк устаревшего кода из-за обновления компилятора. Подобное изменение было бы самым серьезным изменением в любом проекте, за которым я когда-либо следил.

Однако я согласен с остальной частью сообщения.

Нет смысла обсуждать идею заставить | undefined возвращать поведение по умолчанию - они _ никогда_ не выпустят версию Typescript, которая просто взорвет миллионы строк устаревшего кода из-за обновления компилятора. Подобное изменение было бы самым серьезным изменением в любом проекте, за которым я когда-либо следил.

Однако я согласен с остальной частью сообщения.

Нет, если это вариант конфигурации.

Нет, если это вариант конфигурации.

Это справедливо, но, по крайней мере, я ожидал бы длительного периода "опережения", когда он отключен по умолчанию, давая людям много времени (6 месяцев? Год? Больше?) Для преобразования устаревшего кода, прежде чем он станет готовым. -дефолт. Например, добавьте флаг в версии 4.0, установите его по умолчанию в версии 5.0 и тому подобное.

Но на данный момент это далекое будущее, потому что у нас даже нет поддержки, которую стоит сделать в отношении обязательной функции (разные типы для сеттеров / получателей). Так что я поддерживаю утверждение, что нет смысла долго-долго обсуждать поведение по умолчанию.

Нет смысла обсуждать идею заставить | undefined возвращать поведение по умолчанию - они _ никогда_ не выпустят версию Typescript, которая просто взорвет миллионы строк устаревшего кода из-за обновления компилятора. Подобное изменение было бы самым серьезным изменением в любом проекте, за которым я когда-либо следил.

Однако я согласен с остальной частью сообщения.

К вашему сведению @ thw0rted я предложил здесь компромисс: https://github.com/microsoft/TypeScript/issues/38779

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

Для согласованности "strict" что-то вроде Array<T> должно рассматриваться как "a list of type T that is infinitly long with no gaps" любое время, когда это нежелательно, вы должны использовать кортежи ..

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

Если вы согласны, пожалуйста, проголосуйте за

Я не согласен с тем, что для этого нужна совершенно новая опция / конфигурация.

Я не думаю, что это должно быть поведение по умолчанию, когда параметр strict отключен, но когда я включаю параметр strict, BE STRICT и применяю их даже в более старых проектах. Включение строгого - это то, что люди выбирают для максимально строгого / точного набора текста. Если я включу строгую опцию, я хотел бы быть уверен в своем коде, и это лучшая часть TS. Но такой неточный набор даже при включенном строгом режиме - просто головная боль.

В целях сохранения читабельности этой беседы я скрыл ряд косвенных комментариев, которые не добавляли значимого вклада в беседу. В общем, вы можете предположить, что комментарии в следующем ключе были сделаны ранее в предыдущих 212 комментариях и не нуждаются в повторении:

  • TypeScript должен впервые внести непрактично большое критическое изменение в поведение проверки - нет, мы не делали этого раньше и не собираемся делать это в будущем, пожалуйста, более активно работайте над долгосрочными целями дизайна. проекта, прежде чем приехать сюда, чтобы выступить за беспрецедентные изменения
  • Аргументы основаны на идее, что никто не принял во внимание тот факт, что флаг конфигурации существует - мы знаем, что размещение вещей за флагом означает, что они не должны быть включены по умолчанию, это не чье-либо первое родео.
  • Что за задержка, просто сделай это уже! - ну, теперь, когда вы так выразились, я убежден

Здесь есть очень реальные практические и технические проблемы, которые необходимо решить, и нас разочаровывает то, что мы не можем выполнять значимую работу в этой цепочке, потому что она заполнена людьми, которые заходят, чтобы предположить, что что-либо, находящееся за флагом, не имеет реальных последствий или что любая ошибка, которая может be ts-ignore 'd не является бременем обновления.

@RyanCavanaugh

спасибо за ваше резюме.

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

Я удалил свой комментарий и, если хотите, могу удалить и этот комментарий. Но как понять первую цель проекта:

1. Статически идентифицируйте конструкции, которые могут быть ошибками.

(Источник: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals)

Я не понимаю ваш процитированный комментарий, потому что неконтролируемый доступ к индексу может привести к ошибке во время выполнения. Если в JavaScript вы ожидаете подобных вещей, в TypeScript вы получаете опасную и ложную уверенность. Неправильные типы хуже, чем any .

Хорошо, теперь, когда вы так выразились, я убежден

Конечно, вы являетесь одним из основных сопровождающих и благодарим вас, команду и участников за ts. Но это уже не только о тебе. Сравните голосов за: 365 и голосов против: 6! Всего 6! Это показывает огромную потребность в безопасности типов.

Но поговорим о решениях. Есть ли какое-нибудь решение, которое команда примет или может придумать?

Не могли бы вы подробнее рассказать о том, что не так с ts-ignore в этом случае? Я имею в виду, что это можно автоматизировать с помощью таких инструментов, как codemod, и не меняет поведение текущего кода. Конечно, это не красивый обходной путь, но что ж, это один из возможных способов внести это изменение без флагов.

Что вы думаете об автоматической публикации исправленной версии ts, например 4.0.0-breaking ? Он вводит некоторую (большую?) Работу по устранению конфликтов, но позволяет каждому протестировать изменения и подготовить базу кода (не только для этого запроса функции). Это можно сделать в течение ограниченного периода времени, например, 3-6 месяцев. Мы будем первыми, кто воспользуется этой версией.

больше. Сравните голосов за: 365 и голосов против: 6! Всего 6! Это показывает огромную потребность в безопасности типов.
- @Bessonov

ха-ха.
Не то чтобы я согласен со всем сообщением @RyanCavanaugh .. но ... давай ..
там что за миллионы? Пользователи TS там?!? 365 хотят, чтобы этой функции было достаточно, чтобы комментировать и голосовать в этой теме ...

Уведомлений от github нет, но всем, кто попытается поиграть с неопределенным индексом из этой проблемы, взгляните на черновик PR над моим комментарием.

@RyanCavanaugh большое спасибо за то, что дали нам возможность поиграть с ним. Я запустил его на довольно небольшой (~ 105 файлов ts (x)) кодовой базе и немного поигрался с ним. Я не нашел ни одной важной проблемы. Одна строка, которая была изменена с:

const refHtml = useRef(useMemo(() => document.getElementsByTagName('html')[0], []))

к:

const refHtml = useRef(useMemo(() => document.getElementsByTagName('html')[0] ?? null, []))

На следующей неделе я попробую это сделать на среднем проекте.

@ lonewarrior556 это в 60 раз больше, и он на первой странице, если сортировать по голосам :)

@Bessonov Я думаю, вам стоит попробовать его в кодовой базе компилятора Typescript, это, вероятно, вызовет массовые поломки из-за нетривиального использования циклов for.

У вас даже могут быть флаги для включения / выключения для обеспечения обратной совместимости.

Каждый добавляемый вами флаг - это еще одна матрица конфигурации, которую необходимо протестировать и поддержать. По этой причине TypeScript хочет свести флаги к минимуму.

@MatthiasKunnen Если бы это была небольшая особенность

Флаг - это неправильный подход к этому, отчасти из-за комбинаторной
проблема. Мы должны просто назвать этот TypeScript v5 ... такой подход
превращает количество проверяемых комбинаций с 2 ​​^ N до N ...

В субботу, 15 августа 2020 г., в 15:56, Майкл Буркман [email protected]
написал:

@MatthiasKunnen https://github.com/MatthiasKunnen Если бы это были какие-то
небольшая особенность крайнего случая, тогда я согласен, что это веская причина не
добавьте для этого флаг. Но прямой доступ к массиву проявляется в коде довольно
часто, и является как явной дырой в системе типов, так и
ломающее изменение, чтобы исправить, поэтому я чувствую, что флаг будет оправдан.

-
Вы получаете это, потому что подписаны на эту беседу.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-674408067 ,
или отказаться от подписки
https://github.com/notifications/unsubscribe-auth/AAADY5AXEY65S5HDGNGIPZDSA2O3FANCNFSM4C6KEKAA
.

Произошла неожиданная неопределенная ошибка, из-за которой мое приложение было закрыто для сотен пользователей, ошибка, которая была бы обнаружена во время сборки, если бы эта проверка типа была на месте. TypeScript оказался для нас прекрасным способом создания более надежного программного обеспечения, но его огромная полезность подрывается этим единственным упущением.

Как насчет следующих 10 человек, которые захотят оставить здесь комментарий, вместо этого просто протестируйте черновик PR № 39560?

Это очень раздражает, если вы хотите включить правило ESLint @typescript-eslint/no-unnecessary-condition потому что оно затем жалуется на все экземпляры

if (some_array[i] === undefined) {

Он думает, что это ненужное условие (потому что Typescript говорит, что это так!), Но это не так. Я действительно не хочу добавлять // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition всей моей кодовой базе.

Если для ленивых программистов на undefined , например

if (some_array[?i] === undefined) {

(случайное предложение синтаксиса; альтернативы приветствуются)

@Timmmm, пожалуйста, прочтите комментарий чуть выше того, который вы сделали. Вы можете попробовать сборку, в которой реализована версия этого предложения - вы можете сообщить нам, если она решит вашу проблему eslint .

@ the0rted Я прочитал этот комментарий. Он не реализует версию моего предложения. Может, тебе стоит просто прочитать это еще раз.

Извините, когда я сказал «это» предложение, я имел в виду возможность в OP, т.е. обработку array_of_T[i] как T | undefined . Я вижу, что вы спрашиваете о синтаксисе, чтобы пометить определенный оператор индекса как «возможно, неопределенный», но если вы используете реализацию в PR Райана, вам это не понадобится, потому что все операторы индекса будут «возможно, не определено» . Разве это не удовлетворило бы вашу потребность?

Да, будет - если / когда он приземлится, я обязательно им воспользуюсь. Но из этого обсуждения у меня сложилось впечатление, что многие люди сопротивлялись этому, потому что он добавляет дополнительный флаг и делает код, который выглядит так, как будто он должен работать, более сложным (простой пример цикла for).

Поэтому я хотел предложить альтернативное предложение, но, видимо, люди его ненавидят. : - /

Приветствуются все благие намерения, не поймите неправильно @Timmmm. Я думаю, что отрицательные голоса только говорят о том, что на данный момент это уже хорошо проторенная почва в этой ветке.

Для меня добавление флага, который повсеместно повышает безопасность типов, обходится гораздо дешевле, чем введение нового синтаксиса подписки.

Я не упоминал об этом выше, но синтаксис для одноразового переопределения уже существует: if ((some_array[i] as MyType | undefined) === undefined) . Это не так лаконично, как новое сокращение, но, надеюсь, это не та конструкция, которую вам придется использовать очень часто.

_Изначально опубликовано @osyrisrblx в https://github.com/microsoft/TypeScript/issues/40435#issuecomment -690017567_

Хотя это кажется гораздо более безопасным вариантом, было бы неплохо отказаться от этого поведения с отдельным синтаксисом для тех редких случаев, когда вы на 100% уверены, что он всегда определяется.

Может быть, !: всегда может быть определен?

interface X {
    [index: string]!: number; // -> number
}

interface Y {
    [index: string]: number; // -> number | undefined
}

Это предложение неверно характеризует проблему. Проблема не в понимании TypeScript самого типа. Проблема возникает из-за _доступа_ к данным, где новый синтаксис уже был предложен ранее в этом потоке.

Вы можете попробовать это в бета-версии TypeScript 4.1, которая скоро выйдет ™ (или сегодня вечером, если вам это нужно прямо сейчас!)

Извините, если это предлагалось раньше, но может ли {[index: string]?: number} // -> number | undefined поддерживаться? Это согласуется с синтаксисом необязательных свойств интерфейса - требуется по умолчанию, возможно, не определено с помощью «?».

Параметр компилятора в 4.1 - это круто, но было бы неплохо иметь более детальный контроль.

Если вы хотите попробовать это в своем репо (мне потребовалось немного разобраться):

  1. Установить typescript @ next yarn (add|upgrade) typescript@next
  2. Добавить флаг (для меня в tsconfig.json) "noUncheckedIndexedAccess": true

В процессе включения этого правила в моем проекте я обнаружил интересную ошибку:

type MyRecord = { a: number; b: string };

declare const myRecord: MyRecord;

declare const key: 'a' | 'b';
const value = myRecord[key]; // string | number ✅

// ❌ Unexpected error
// Type 'MyRecord[Key] | undefined' is not assignable to type 'MyRecord[Key]'
const fn = <Key extends keyof MyRecord>(key: Key): MyRecord[Key] => myRecord[key];

В этом случае я не ожидал, что myRecord[key] вернет тип MyRecord[Key] | undefined , потому что key ограничен keyof MyRecord .

Обновление : подана проблема https://github.com/microsoft/TypeScript/issues/40666

Это, вероятно, ошибка / недосмотр!

В процессе включения этого правила в моем проекте я обнаружил интересную ошибку:

type MyRecord = { a: number; b: string };

declare const myRecord: MyRecord;

declare const key: 'a' | 'b';
const value = myRecord[key]; // string | number ✅

// ❌ Unexpected error
// Type 'MyRecord[Key] | undefined' is not assignable to type 'MyRecord[Key]'
const fn = <Key extends keyof MyRecord>(key: Key): MyRecord[Key] => myRecord[key];

В этом случае я не ожидал, что myRecord[key] вернет тип MyRecord[Key] | undefined , потому что key ограничен keyof MyRecord .

Я бы сказал, что это ошибка. Обычно, если keyof Type включает только строковые / числовые литералы (в отличие от фактического включения string или number ), тогда Type[Key] где Key extends keyof Type Я бы подумал, что не должно включать undefined .

перекрестные ссылки на # 13195, в котором также рассматриваются различия и сходства между фразами «здесь нет собственности» и «здесь есть свойство, но это undefined »

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

  • # 41612

К вашему сведению: благодаря этому флагу я обнаружил две ошибки в своей кодовой базе, большое спасибо!
Немного раздражает, что проверки arr.length > 0 недостаточно для защиты arr[0] , но это небольшое неудобство (я могу переписать проверку, чтобы осчастливить tsc) по сравнению с дополнительной безопасностью, ИМО.

@rubenlg Я согласен насчет arr.length . Чтобы просто проверить первый элемент, я переписал наш код, например

const firstEl = arr[0];
if (firstEl !== undefined) {
  ...
}

Но есть несколько мест, где мы делаем if (arr.length > 2) или больше, что немного неудобно. Однако я не думаю, что проверка .length любом случае будет полностью типобезопасной, так как вы можете просто изменить ее:

const a: number[] = [];

a.length = 1;

if (a.length > 0) {
    const b: number = a[0];
    console.log(b);
}

Печатает undefined .

Однако я не думаю, что проверка .length любом случае будет полностью безопасна по типу, поскольку вы можете просто изменить ее:

const a: number[] = [];

a.length = 1;

if (a.length > 0) {
    const b: number = a[0];
    console.log(b);
}

Печатает undefined .

По сути, это был бы разреженный массив, который не подходит для такого рода вещей. Всегда есть нестандартные или хитрые вещи, которые можно сделать, чтобы обойти проверку типов.

Всегда есть способ опровергнуть предыдущее предположение, и в этом проблема. Анализ всех возможных путей выполнения был бы слишком дорогостоящим.

const a: number[] = [1]
if (a.length > 0) {
    a.pop();
    console.log(a[0])
}
Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

Roam-Cooper picture Roam-Cooper  ·  3Комментарии

kyasbal-1994 picture kyasbal-1994  ·  3Комментарии

jbondc picture jbondc  ·  3Комментарии

MartynasZilinskas picture MartynasZilinskas  ·  3Комментарии

DanielRosenwasser picture DanielRosenwasser  ·  3Комментарии