Освоение защиты типов в TypeScript: повышение безопасности вашего кода

Защита типов в TypeScript — это мощная функция, которая позволяет разработчикам сузить тип переменной во время выполнения. Вам следует использовать защиту типов, чтобы обеспечить безопасность типов в вашем коде.

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

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

Понимание защиты типов в TypeScript

Защита типа (Type Guard) — это процесс проверки типа переменной в TypeScript. Существуют различные способы реализации защиты типов, включая использование операторов typeof, instanceof и in или создание пользовательских средств защиты типов.

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

В TypeScript существует 5 типов защиты типов:

  • typeof
  • in
  • instanceof
  • Тип защиты сужения равенства
  • Пользовательский тип защиты, определяемый пользователем

Мы подробно обсудим каждую из этих техник на примерах.

Использование typeof Type Guard в TypeScript

Оператор typeof — это встроенная защита типа в TypeScript, которая проверяет тип переменной во время выполнения. Вы должны использовать это ключевое слово перед именем переменной.

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

const firstName = 'Max'

typeof firstName
// It will return "string"

Одним из наиболее распространенных случаев использования typeofзащиты типа является проверка примитивных типов, таких как number, string, boolean, undefined, function, objectи symbol.

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

const printInfo = (value: string | number) => {
    if (typeof value === 'number') {
        console.log(`Student ID: ${value}`)
    } else {
        console.log(`Student name: ${value}`)
    }
}

printInfo(23)
// Student ID: 23

printInfo('Max')
// Student name: Max

В этом примере у меня есть функция printInfo, которая принимает один параметр. Параметр value может быть либо a, string либо a number.

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

Если value это число, функция выводит сообщение, указывающее, что это идентификатор студента, а также фактическое значение value.

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

Использование in оператора в качестве защиты типа

В TypeScript вы можете использовать этот inоператор в качестве средства защиты типа, чтобы определить, имеет ли объект определенное свойство или подпись индекса. Это вернет логическое значение.

Если свойство существует в этом объекте, оно вернет true, в противном случае — false. Вот почему мы можем использовать этот оператор с условными операторами.

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

interface Circle {
    kind: 'circle'
    radius: number
}

interface Square {
    kind: 'square'
    sideLength: number
}

type Shape = Circle | Square

const calculateArea = (shape: Shape) => {
    if ('radius' in shape) {
        // shape is now narrowed down to Circle type
        return Math.PI * shape.radius ** 2
    } else {
        // shape is now narrowed down to Square type
        return shape.sideLength ** 2
    }
}

У меня есть два интерфейса Circle, Squareкоторые описывают свойства круга и квадрата соответственно. Функция calculateArea принимает shape параметр, который может быть либо Circle либо Square.

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

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

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

Использование instanceof оператора в качестве защиты типа

Оператор instanceof в TypeScript — это защита, позволяющая нам проверить, является ли объект экземпляром определенного класса или функции-конструктора.

Если объект является экземпляром класса, он вернет true, в противном случае false — . Он будет соответствовать, если объект содержит аналогичные свойства и методы этого конкретного класса или функции-конструктора .

class Car {
    startEngine() {
        console.log('Starting car engine...')
    }
}

class Truck {
    startEngine() {
        console.log('Starting truck engine...')
    }

    loadCargo() {
        console.log('Loading cargo...')
    }
}

const useVehicle = (vehicle: Car | Truck) => {
    vehicle.startEngine()

    if (vehicle instanceof Truck) {
        vehicle.loadCargo()
    }
}

const car = new Car()
const truck = new Truck()

useVehicle(car)
// Starting car engine...

useVehicle(truck)
// Starting truck engine...
// Loading cargo...

В этом примере у нас есть два класса Car и Truck. Оба класса имеют startEngine метод, но только у Truck класса есть дополнительный loadCargo метод.

Он useVehicle принимает параметр vehicle, который может быть либо Car либо Truck. Внутри этой функции мы вызываем startEngine метод для vehicle параметра, независимо от того, является ли он Car или Truck. Потому что у них обоих есть этот метод.

Но если вы попытаетесь вызвать loadCargo метод, TypeScript выдаст ошибку. Потому что этот метод доступен только в Track классе. Вот почему вам нужно проверить, является ли параметр vehicle экземпляром класса, Truck используя instanceof оператор.

Тип защиты равенства

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

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

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

interface Circle {
    kind: 'circle'
    radius: number
}

interface Square {
    kind: 'square'
    sideLength: number
}

type Shape = Circle | Square

const calculateArea = (shape: Shape) => {
    if (shape.kind === 'circle') {
        console.log('Circle radius: ', shape.radius)
    } else {
        console.log('Square length: ', shape.sideLength)
    }
}

У нас есть 2 интерфейса Circle и Square. У них обоих есть одно общее свойство kind: . Мы также использем уникальное значение для этого свойства. Внутри Circle интерфейса kind свойство имеет значение « круг », а внутри Square интерфейса — значение « квадрат ».

Функция calculateArea() принимает параметр, который может быть либо типом Circle, либо Square. Вы можете получить доступ к kind свойству из этого параметра формы, поскольку оба интерфейса имеют это свойство.

Если оно имеет значение circle, мы знаем, что это Circle, и можем безопасно получить доступ к этому radius свойству. В противном случае это должен быть знак, Square и мы сможем безопасно получить доступ к sideLength собственности.

type Shape = 'circle' | 'square'

const calculateArea = (shape: Shape) => {
    if (shape === 'circle') {
        console.log("It's a circle")
    } else {
        console.log("It's a square")
    }
}

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

Пользовательский тип защиты, определяемый пользователем

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

В этом примере у нас есть два интерфейса Person и Company, которые имеют общее свойство name. Мы определяем функцию защиты пользовательского типа isCompany, которая проверяет, имеет ли данный объект свойство industry или нет, и возвращает логическое значение.

interface Person {
    name: string
}

interface Company {
    name: string
    industry: string
}

const isCompany = (obj: Person | Company): obj is Company => {
    return (obj as Company).industry !== undefined
}

const printDetails = (obj: Person | Company) => {
    console.log(`Name: ${obj.name}`)

    if (isCompany(obj)) {
        console.log(`Industry: ${obj.industry}`)
    }
}

const person: Person = { name: 'Alice' }
const company: Company = { name: 'ABC Ltd.', industry: 'Technology' }

printDetails(person)
// Name: Alice

printDetails(company)
// Name: ABC Ltd.
// Industry: Technology

Эта isCompany функция принимает объект с типом объединения Person | Company. Внутри функции я проверяю, имеет ли данный объект свойство, industry используя !==оператор. Если свойство существует, мы возвращаем true, в противном случае false— .

В printDetails функции я беру объект типа Person | Company в качестве аргумента. Мы используем isCompany функцию защиты типа, чтобы сузить тип этого объекта. Если isCompany возвращается true, мы можем безопасно получить доступ к industry свойству.

Когда следует использовать защиту типа в TypeScript?

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

Вам следует использовать защиту типа в TypeScript в следующих сценариях:

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

Преимущества использования защиты типов TypeScript

Использование защиты типов в TypeScript может дать несколько преимуществ для безопасности типов и качества кода:

  • Улучшенная безопасность типов: средства защиты типов помогают гарантировать, что переменные имеют ожидаемый тип, снижая риск ошибок во время выполнения, вызванных неожиданными типами. Это делает ваш код более устойчивым и надежным.
  • Лучшее качество кода: средства защиты типов делают ваш код более читабельным и самодокументируемым, предоставляя больше информации о типах переменных. Это может помочь другим разработчикам легче понять ваш код, что приведет к повышению качества кода и его удобства сопровождения.
  • Сокращение времени отладки. Обнаруживая ошибки типа во время компиляции, а не во время выполнения, средства защиты типов могут сократить время и усилия, необходимые для отладки кода. Это может сэкономить вам и вашей команде драгоценное время и ресурсы.
  • Улучшение опыта разработки. Использование средств защиты типов может помочь вам обнаружить ошибки на ранних этапах процесса разработки, что приведет к более плавному и приятному процессу разработки. Это также может помочь улучшить вашу общую производительность.

Заключение

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

Я обсудил несколько способов реализации защиты типов в вашем проекте TypeScript. Большую часть времени мы используем встроенные средства защиты, такие как typeof, in оператор, instanceof оператор и т. д.

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