[Перевод] TypeScript 3.0: неизвестные типы

внешний интерфейс JavaScript Программа перевода самородков TypeScript

TypeScript 3.0 представляет новыйunknownтипа, этоanyТип соответствует типу ценной бумаги.

unknownа такжеanyОсновное отличиеunknownТипы будут строже:unknownПрежде чем выполнять большинство операций над значением типа, мы должны выполнить некоторую проверку. пока правильноanyНам не нужно делать никаких проверок перед выполнением операции над значением типа.

Эта статья посвященаunknownвидов практического применения, а также в том числеanyСравнение типов. Если вам нужен более полный пример кода, чтобы понятьunknownСемантика типов, см. Андерса Хейлсберга.оригинальный запрос на вытягивание.

anyТипы

давайте сначала посмотримanyтипы, чтобы мы могли лучше понять импортunknownМотивация типа.

Поскольку TypeScript выпустил свою первую версию в 2012 годуanyТипы существовали всегда. Он представляет все возможные значения JavaScript — примитивные типы, объекты, массивы, функции, ошибки, символы и все остальное, что вы можете определить.

В TypeScript любой тип может быть классифицирован как любой тип. Это позволяетanyТипы становятся частью системы типовверхний тип(также известен какглобальный супертип).

Вот некоторые из значений, которые мы присваиваемanyПример кода типа:

let value: any;

value = true;             // OK
value = 42;               // OK
value = "Hello World";    // OK
value = [];               // OK
value = {};               // OK
value = Math.random;      // OK
value = null;             // OK
value = undefined;        // OK
value = new TypeError();  // OK
value = Symbol("type");   // OK

anyТипы, по сути, являются аварийным люком для системы типов. Это дает нам как разработчикам большую свободу: TypeScript позволяет намanyЗначение типа выполняет любую операцию без предварительной проверки.

В приведенном выше примере переменнаяvalueопределяется как типany. Из-за этого TypeScript считает все следующее корректным для типов:

let value: any;

value.foo.bar;  // OK
value.trim();   // OK
value();        // OK
new value();    // OK
value[0][1];    // OK

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

Но что, если бы вы могли иметь типы верхнего уровня, которые по умолчанию были бы безопасными? ЭтоunknownПричина прихода.

unknownТипы

как и все типы можно классифицировать какany, все типы также можно классифицировать какunknown. Это делаетunknownстать еще одним типом верхнего уровня системы типов TypeScript (еще одинany).

Это тот же набор примеров присваивания, который мы видели ранее, на этот раз с использованием типаunknownПеременные:

let value: unknown;

value = true;             // OK
value = 42;               // OK
value = "Hello World";    // OK
value = [];               // OK
value = {};               // OK
value = Math.random;      // OK
value = null;             // OK
value = undefined;        // OK
value = new TypeError();  // OK
value = Symbol("type");   // OK

правильноvalueВсе присваивания переменных считаются корректными по типу.

Когда мы пытаемся преобразовать тип вunknownЧто происходит, когда значение присваивается переменной другого типа?

let value: unknown;

let value1: unknown = value;   // OK
let value2: any = value;       // OK
let value3: boolean = value;   // Error
let value4: number = value;    // Error
let value5: string = value;    // Error
let value6: object = value;    // Error
let value7: any[] = value;     // Error
let value8: Function = value;  // Error

unknownТипы могут быть присвоены толькоanyтип иunknownсам тип. Интуитивно это имеет смысл: только контейнеры, которые могут хранить значения любого типа, могут хранитьunknownТипы значений. Ведь мы не знаем переменныхvalueКакой тип значения хранится в .

Теперь давайте посмотрим, когда мы попытаемсяunknownЧто происходит, когда операция выполняется над значением . Вот та же операция, которую мы видели раньше:

let value: unknown;

value.foo.bar;  // Error
value.trim();   // Error
value();        // Error
new value();    // Error
value[0][1];    // Error

БудуvalueТип переменной установлен наunknownПосле этого эти операции больше не считаются типокорректными. Путем измененияanyПечататьunknownТипа, наше значение по умолчанию было изменено с Разрешить все на Разрешение почти ничего.

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

уменьшитьunknownДиапазон типов

Мы можем комбинировать по-разномуunknownТипы сужаются до более конкретных типов, включаяtypeofоператор,instanceofОператоры и защитные функции пользовательского типа. Все эти типы методов сужения вносят свой вклад в TypeScript.Анализ типов на основе потока управления.

Следующий пример иллюстрируетvalueкак через дваifБолее конкретный тип получается в ветке оператора:

function stringifyForLogging(value: unknown): string {
  if (typeof value === "function") {
    // Within this branch, `value` has type `Function`,
    // so we can access the function's `name` property
    const functionName = value.name || "(anonymous)";
    return `[function ${functionName}]`;
  }

  if (value instanceof Date) {
    // Within this branch, `value` has type `Date`,
    // so we can call the `toISOString` method
    return value.toISOString();
  }

  return String(value);
}

Помимо использованияtypeofилиinstanceofВ дополнение к оператору мы также можем использовать функцию защиты пользовательского типа, сужающуюunknownДиапазон типов:

/**
 * A custom type guard function that determines whether
 * `value` is an array that only contains numbers.
 */
function isNumberArray(value: unknown): value is number[] {
  return (
    Array.isArray(value) &&
    value.every(element => typeof element === "number")
  );
}

const unknownValue: unknown = [15, 23, 8, 4, 42, 16];

if (isNumberArray(unknownValue)) {
  // Within this branch, `unknownValue` has type `number[]`,
  // so we can spread the numbers as arguments to `Math.max`
  const max = Math.max(...unknownValue);
  console.log(max);
}

несмотря на то чтоunknownValueбыл классифицирован какunknowntype, обратите внимание, как он все еще извлекается в ветке ifnumber[]Типы.

правильноunknownТипы используют утверждения типа

В предыдущем разделе мы видели, как использоватьtypeof,instanceofи пользовательские функции защиты типа, чтобы убедить компилятор TypeScript в том, что значение имеет определенный тип. Это безопасный и рекомендуемый способ обозначить «неизвестный» тип как более конкретный тип.

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

const value: unknown = "Hello World";
const someString: string = value as string;
const otherString = someString.toUpperCase();  // "HELLO WORLD"

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

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

const value: unknown = 42;
const someString: string = value as string;
const otherString = someString.toUpperCase();  // BOOM

этоvalueЗначение переменной — число, но давайте предположим, что это строка, и используем утверждение типа.value as string. Поэтому используйте утверждения типов с осторожностью!

в союзном типеunknownТипы

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

В союзном типеunknownТипы поглощают любой тип. Это означает, что если любой тип компонентаunknown, тип объединения также будет эквивалентенunknown:

type UnionType1 = unknown | null;       // unknown
type UnionType2 = unknown | undefined;  // unknown
type UnionType3 = unknown | string;     // unknown
type UnionType4 = unknown | number[];   // unknown

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

type UnionType5 = unknown | any;  // any

так почемуunknownМожет поглощать любой тип (anyкроме типа)? давай подумаемunknown | stringэтот пример. Этот тип может представлять любое неизвестное или строковое значение. Как мы узнали ранее, все типы значений можно определить какunknownтипа, который также включает в себя всеstringтипа, следовательно,unknown | stringсредства иunknownНабор значений с самим типом. Таким образом, компилятор может упростить тип объединения доunknownТипы.

крестового типаunknownТипы

В перекрестном типе любой тип может быть поглощенunknownТипы. Это означает объединение любого типа сunknownПересечение не меняет тип результата:

type IntersectionType1 = unknown & null;       // null
type IntersectionType2 = unknown & undefined;  // undefined
type IntersectionType3 = unknown & string;     // string
type IntersectionType4 = unknown & number[];   // number[]
type IntersectionType5 = unknown & any;        // any

Давайте рассмотримIntersectionType3:unknown & stringТип указывает, что все могут быть одновременно назначеныunknownа такжеstringзначение типа. Поскольку каждый тип может быть присвоенunknownтипа, поэтому в типе пересечения содержитсяunknownне изменит результат. нам останется толькоstringТипы.

Использовать тип какunknownоператор значения

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

Вы можете использовать тип какunknownДля значения используются только четыре оператора равенства и неравенства:

  • ===
  • ==
  • !==
  • !=

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

Пример: изlocalStorageчитать JSON в

Вот как мы используемunknownРеальные примеры типов.

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

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

Вот как мы реализуем эту функцию:

type Result =
  | { success: true, value: unknown }
  | { success: false, error: Error };

function tryDeserializeLocalStorageItem(key: string): Result {
  const item = localStorage.getItem(key);

  if (item === null) {
    // The item does not exist, thus return an error result
    return {
      success: false,
      error: new Error(`Item with key "${key}" does not exist`)
    };
  }

  let value: unknown;

  try {
    value = JSON.parse(item);
  } catch (error) {
    // The item is not valid JSON, thus return an error result
    return {
      success: false,
      error
    };
  }

  // Everything's fine, thus return a success result
  return {
    success: true,
    value
  };
}

тип возвращаемого значенияResultЯвляетсяотмеченный тип соединения. В других языках это также может называтьсяMaybe,OptionилиOptional. Мы используемResultнаглядно моделировать успешные и неуспешные исходы операций.

tryDeserializeLocalStorageItemВызывающий функцию пытается использоватьvalueилиerrorсвойство должно быть проверено в первую очередьsuccessАтрибуты:

const result = tryDeserializeLocalStorageItem("dark_mode");

if (result.success) {
  // We've narrowed the `success` property to `true`,
  // so we can access the `value` property
  const darkModeEnabled: unknown = result.value;

  if (typeof darkModeEnabled === "boolean") {
    // We've narrowed the `unknown` type to `boolean`,
    // so we can safely use `darkModeEnabled` as a boolean
    console.log("Dark mode enabled: " + darkModeEnabled);
  }
} else {
  // We've narrowed the `success` property to `false`,
  // so we can access the `error` property
  console.error(result.error);
}

осторожность,tryDeserializeLocalStorageItemФункция не может просто вернутьnullчтобы указать, что десериализация не удалась по следующим причинам:

  1. nullvalue является допустимым значением JSON. Поэтому мы не можем различать пары значенийnullПроизошла ли десериализация или вся операция завершилась сбоем из-за отсутствия параметров или синтаксических ошибок.
  2. Если мы вернемся из функцииnull, мы не можем вернуть ошибку одновременно. Следовательно, вызывающая сторона нашей функции не знает, почему операция не удалась.

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

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


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.