Серия: Как понять и использовать Javascript и TypeScript

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

typeof Type Guard

JavaScript предоставляет оператор typeof, поэтому разработчик может написать код для проверки того, какой тип значения имеет переменная или параметр, и должен ли код продолжать выполнять определенную ветвь функциональности. Это происходит во время выполнения и может помочь разработчику написать лучший защитный код. TypeScript понимает некоторые ответы, которые дает typeof, и использует их с помощью так называемого статического анализа. Статический анализ — это процесс, который выполняется без запуска кода разработчиком, при этом компилятор TypeScript проверяет код и определяет, есть ли какие-либо проблемы с кодом, до выполнения и/или развертывания. Давайте посмотрим на следующий надуманный пример ниже:

function addAll(values: number | number[]): number {
  let total = 0;
  
  if (typeof values === "object") {
    for (const n of values) {
      total = total + n;
    }
  } else if (typeof values === "number") {
    total = total + values;
  } else {
    // do nothing
  }
  return total;
}

В функции addAll мы получаем параметр с именем values, который может быть числом или объектом, и функция вернет нам число. Внутри функции мы проверяем тип параметра values, если это число, у нас есть условная ветвь кода, которая будет выполнять какое-то простое сложение, если это объект, у нас есть условная ветвь кода, которая будет перебирать объект и сложим в нем все значения, получив таким образом сумму.

Что произойдет, если мы позволим values также быть null? Оказывается, приведенный выше код не будет работать во время статического анализа, потому что параметр values пройдет проверку typeof для object и, таким образом, попытается перебрать нулевое значение.

Это подводит нас к следующей форме сужения, которая называется Сужение правды.

Сужение правды

Самый простой способ подумать об этом — определить, является ли значение переменной или параметра true, реальным значением какого-либо вида или false, другими словами, одно из этих значений 0, false, NaN, "", null, undefined or 0n. Давайте добавим в приведенную выше функцию опцию типа null и исправим код с помощью уменьшения достоверности.

function addAll(values: number | number[] | null): number {
  let total = 0;
  
  if (values && typeof values === "object") {
    for (const n of values) {
      total = total + n;
    }
  } else if (typeof values === "number") {
    total = total + values;
  } else {
    // do nothing
  }
  return total;
}

Добавив к параметру параметр типа null и добавив проверку правдивости в четвертой строке кода. Приведенный выше код теперь пройдет процесс статического анализа в TypeScript.

instanceof Сужение

В JavaScript есть оператор, который во многом похож на оператор typeof, называемый оператором instanceof. Оператор instanceof проверяет, является ли значение экземпляром другого значения. TypeScript также может использовать instanceof в качестве защиты типа. Давайте посмотрим пример…

function printDate(value: string | Date) {
  if (value instanceof Date) {
    console.log("the date is: ", value.toUTCString())
  } else {
    console.log("the date is: ", value)
  }
}

Точно так же, как мы видели с сужением typeof, сужение instanceof можно использовать для выполнения соответствующей условной ветки кода.

Сужение равенства

Как и в предыдущих примерах, TypeScript может использовать операторы равенства ===, !==, == и != в блоках if или операторах switch для сужения путей выполнения на основе результатов этих проверок типа на равенство. Следует отметить, что сужение на равенство не только проверяет, совпадают ли значения, проверка во время выполнения, но также проверяет, совпадают ли типы, проверка статического анализа. Вот пример:

function add(firstNumber: number, secondNumber: number | null) {
  let total: number;
  if (firstNumber === secondNumber) {
    total = firstNumber + secondNumber;
  } else {
    total = firstNumber;
  }
  return total;
}

Сужение с помощью оператора in

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

//////////////////////////////////////////
// Type Alias example
//////////////////////////////////////////
type Bike = { pedal: () => void };
type Truck = { haul: () => void };
function go(vehicle: Bike | Truck) {
  if ("pedal" in vehicle) {
    return vehicle.pedal();
  }
  return vehicle.haul();
}

//////////////////////////////////////////
// Interface example
//////////////////////////////////////////
interface Bike {
  pedal: () => void;
}
interface Truck {
  haul: () => void;
}
function drive(vehicle: Bike | Truck) {
  if ("pedal" in vehicle) {
    return vehicle.pedal();
  }
  return vehicle.haul();
}

Определяемые пользователем типы защиты и предикаты типов

Комбинируя определяемые пользователем средства защиты типов и предикаты типов, мы можем напрямую контролировать, как типы изменяются в нашем коде и как это влияет на выполнение нашего кода. Давайте начнем с определения пользовательской защиты типа, в приведенном ниже примере vehicle is Bike — это наш предикат типа.

function isBike(vehicle: Bike | Truck): vehicle is Bike {
  return (vehicle as Bike).pedal !== undefined;
}

Теперь, когда мы используем isBike в блоке кода, TypeScript сможет сузить переменную, переданную в isBike, если тип переменной совместим с сигнатурой функции isBike’s.

let vehicle = getBike()
if (isBike(vehicle)) {
  vehicle.pedal();
} else {
  vehicle.haul();
}

Следовательно, в приведенном выше примере, как только TypeScript видит isBike, он знает, что транспортное средство — это Bike в ветке if и Truck в ветке else, потому что определяемая пользователем защита типа isBike принимает только эти два типа.

Размеченные объединения, тип never и исчерпывающая проверка

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

type Cola = {
  kind: "cola";
  name: "Homemade Cola";
  mgramsOfCaffine: 34;
  gramsOfSugar: 39;
}
type CreamSoda = {
  kind: "creamsoda";
  name: "Homemade Cream Soda";
  mgramsOfCaffine: 0;
  gramsOfSugar: 49;
}
type RootBeer = {
  kind: "rootbeer";
  name: "Homemade Root Beer";
  mgramsOfCaffine: 0;
  gramsOfSugar: 40;
}
type Soda = Cola | CreamSoda | RootBeer

В приведенном выше примере типSoda представляет собой Размеченный союз. Это может быть один из трех возможных типов: Cola, CreamSoda или RootBeer. Значение, которое каждый тип имеет для литерального члена kind, сообщает компилятору, какой у нас тип Soda.

function makeSoda(cola: Cola) {
  switch(cola.kind) {
    case "rootbeer":
    case "creamsoda":
      addSugar(cola.gramsOfSugar);
      break;
    case "cola":
      addCaffiene(cola.mgramsOfCaffine);
      addSugar(cola.gramsOfSugar);
      break;
    default:
      exhaustiveSodaAssertion(c);
      break;
  }
}
function exhaustiveSodaAssertion(value: never): never {
  throw new Error("Receive an unexpected Soda type")
}

При сужении, как в приведенной выше функции makeSoda, вы можете исключать варианты, пока их не останется. Как только это произойдет, TypeScript будет использовать тип never для представления состояния, которого не должно быть. В случае с оператором switch мы обрабатываем все известные случаи, любые другие случаи, которые могут быть созданы позднее, попадут в случай по умолчанию, это называется Исчерпывающая проверка. Если случай вызывает exhaustiveSodaAssertion, другими словами, мы создали новый тип, но не учли его должным образом в нашей функции makeSoda, это вызовет ошибку компиляции. Таким образом, используя Размеченные объединения, тип never и Исчерпывающую проверку, мы реализовали код, который обрабатывает все известные типы и защищает наш код от потенциальных ошибок в будущем.

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