TypeScript. Начало работы, часть 4 из 7

26.03.2023

Теги: JavaScriptTypeScriptWeb-разработкаТеорияТипыДанных

Основы TypeScript

1. Базовые типы

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

  • number — тип для числовых значений
  • string — тип для строковых значений
  • boolean — тип для булевых значений
  • any — тип для отключения проверки типа
  • symbol — тип для уникального значения
  • null — тип для значения null
  • undefined — тип для значения undefined
  • never — представляет отсутствие значения

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

let age: number = 30; 
let hello: string = 'hello world';
let isAdmin: boolean = true;

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

let hello: string = 'hello world';
hello = 23;

В первой строке компилятор увидит, что переменной hello присваивается строка, поэтому для нее будет использоваться тип string. Однако на второй строке компилятор уже выдаст ошибку, поскольку hello переменной уже определен тип string. А новое значение предполагает тип number.

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

let sum: any;
sum = 1200;
sum = 'тысяча двести';
let result: number = sum / 12;
console.log(result); // NaN — строку нельзя разделить на число

Переменная sum может хранит любое значение, однако деление может работать только с числами.

let sum: any;
sum = 1200;
if (typeof sum === 'number') {
    let result: number = sum / 12;
    console.log(result);
} else{
    console.log('invalid operation');
}

Оператор typeof может возвращать строки number, string, boolean, symbol, undefined, object, function.

2. Объединение типов

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

let id: number | string;
id = '9df149f1af8eb62e72414b8b32aa0347';
console.log(id); // 9df149f1af8eb62e72414b8b32aa0347
id = 12345;
console.log(id);  // 12345

Подобным образом можно использовать объединения для определения параметров функции.

function printUniqueId(id: number | string) {
    console.log(`Unique id ${id}`);
}

let id: string | number;
id = '9df149f1af8eb62e72414b8b32aa0347';
printUniqueId(id);
id = 12345;
printUniqueId(id);

3. Пересечение типов

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

type Person = {
    name: string;
    age?: number;
};

type HasIdentifier = {
    id: number;
};

type HasPosition = {
    position: string;
};

type Employee = Person & HasIdentifier & HasPosition;

const employee: Employee = {
    id: 12345,
    name: 'Сергей',
    age: 36,
    position: 'developer',
};

4. Псевдонимы типа

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

type Identifier = number | string;

const idOne: Identifier = '9df149f1af8eb62e72414b8b32aa0347';
console.log(idOne); // 9df149f1af8eb62e72414b8b32aa0347

const idTwo: Identifier = 12345;
console.log(idTwo); // 12345
type Identifier = number | string;

function printUniqueId(id: Identifier) {
    console.log(`Unique id ${id}`);
}

const idOne: Identifier = '9df149f1af8eb62e72414b8b32aa0347';
printUniqueId(idOne); // 9df149f1af8eb62e72414b8b32aa0347

const idTwo: Identifier = 12345;
printUniqueId(idTwo); // 12345

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

type Person = {
    name: string;
    age: number;
}

const personOne: Person = { name: 'Сергей', age: 36 };
const personTwo: Person = { name: 'Николай', age: 41 };

function printPerson(user: Person) {
    console.log(`Name: ${user.name}  Age: ${user.age}`);
}

printPerson(personOne);
printPerson(personTwo);

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

type Person = {
    id: number;
    name: string;
    email: string;
    age?: number;
};
const person: Person = {
    id: 12345,
    name: 'Сергей Иванов',
    email: 'ivanov@mail.ru',
    age: 32,
};

type Employee = Person & {
    position: string;
};
const employee: Employee = {
    id: 12345,
    name: 'Сергей Иванов',
    email: 'ivanov@mail.ru',
    position: 'developer',
};

5. Тип any

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

let anything: any = 'hello';
console.log(anything);   // сейчас anything — это string
anything = 20; 
console.log(anything);   // сейчас anything — это number

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

let x;  // тип any
x = 10;
x = 'hello';

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

6. Тип null

Переменная типа null может принимать единственное литеральное значение null. Тип null является подтипом всех типов, поэтому его единственное значение null совместимо со всеми остальными типами данных.

let anyValue: any = null; // OK
let numberValue: number = null; // OK
let stringValue: string = null; // OK
let booleanValue: boolean = null; // OK
let undefinedValue: undefined = null; // OK

В то время как тип null совместим со всеми типами, с ним самим совместимы лишь типы undefined и any.

let anyValue: any = null; // OK
let numberValue: number = null; // OK
let stringValue: string = null; // OK
let booleanValue: boolean = null; // OK
let undefinedValue: undefined = null; // OK

let nullValue: null;
nullValue = anyValue; // OK
nullValue = numberValue; // Error
nullValue = stringValue; // Error
nullValue = booleanValue; // Error
nullValue = undefinedValue; // OK

Когда тип данных указывается не явно, а в качестве значения используется null, выведение типов определяет принадлежность к типу any.

let identifier = null; // identifier: any

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

let anyValue: any = null; // OK
let numberValue: number = null; // Error
let stringValue: string = null; // Error
let booleanValue: boolean = null; // Error
let undefinedValue: undefined = null; // Error

let nullValue: null;
nullValue = anyValue; // OK
nullValue = numberValue; // Error
nullValue = stringValue; // Error
nullValue = booleanValue; // Error
nullValue = undefinedValue; // Error

7. Тип undefined

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

Переменная типа undefined может принимать единственное литеральное значение undefined. Тип null является подтипом всех типов, поэтому его единственное значение undefined совместимо со всеми остальными типами данных.

let anyValue: any = undefined; // OK
let numberValue: number = undefined; // OK
let stringValue: string = undefined; // OK
let booleanValue: boolean = undefined; // OK
let nullValue: null = undefined; // OK

В то время как тип undefined совместим со всеми типами, с ним самим совместимы лишь типиs null и any.

let anyValue: any = undefined; // OK
let numberValue: number = undefined; // OK
let stringValue: string = undefined; // OK
let booleanValue: boolean = undefined; // OK
let nullValue: null = undefined; // OK

let undefinedValue: undefined;
undefinedValue = anyValue; // OK
undefinedValue = numberValue; // Error
undefinedValue = stringValue; // Error
undefinedValue = booleanValue; // Error
undefinedValue = nullValue; // OK

Когда тип данных указывается не явно, а в качестве значения используется undefined, выведение типов определяет принадлежность к типу any.

let identifier = undefined; // identifier: any

При активном флаге strictNullChecks, тип undefined является подтипом только одного типа any.

let anyValue: any = undefined; // OK
let numberValue: number = undefined; // Error
let stringValue: string = undefined; // Error
let booleanValue: boolean = undefined; // Error
let nullValue: null = undefined; // Error

let undefinedValue: undefined;
undefinedValue = anyValue; // OK
undefinedValue = numberValue; // Error
undefinedValue = stringValue; // Error
undefinedValue = booleanValue; // Error
undefinedValue = nullValue; // Error

8. Тип never

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

const logger = () => {
    while (true) {
        console.log('The server is up and running');
    }
}

9. Функции и типы

В TypeScript функции определяются с помощью ключевого слова function. Функция может иметь параметры, для каждого параметра указывается его тип. Функция может возвращать значение, тип значения указывается после списка параметров. Если тип возвращаемого значения не указан — он будет выводиться неявно. Если функция ничего не возвращает, то указывается тип void.

function sum(a: number, b: number): number {
    return a + b;
}
let result = sum(10, 20);
function sum(a: number, b: number): void { // функция ничего не возвращает
    console.log(a + b);
}
sum(10, 20);

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

function getName(firstName: string, lastName: string): string {
    return firstName + ' ' + lastName;
}
let name: string;
name = getName('Иван');  // ошибка, мало параметров
name = getName('Иван', 'Иванов');
name = getName('Иван', 'Иванович', 'Иванов');  // ошибка, много параметров

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

function getName(firstName: string, lastName?: string): string {
    if (lastName) {
        return firstName + ' ' + lastName;
    }
    return firstName;
}
let name: string;
name = getName('Иван', 'Иванов');
console.log(name);  // Иван Иванов
name = getName('Иван');
console.log(name);  // Иван

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

function getName(firstName: string, lastName: string = 'Иванов'): string {
    return firstName + ' ' + lastName;
}
let name
name = getName('Иван', 'Петров');
console.log(name);  // Иван Петров
name = getName('Иван');
console.log(name);  // Иван Иванов

Тип функции

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

function hello() {
    console.log('hello');
};
// здесь ()=>void — это тип переменной message
let message: ()=>void = hello;
message();

В данном случае переменная message представляет любую функцию, которая не принимает параметров и ничего не возвращает. Другой пример — функция, которая принимает параметры типа number и возвращает результат типа number.

function plus(x: number, y: number): number {
    return x + y;
};
function minus(x: number, y: number): number {
    return x - y;
};

let action: (x: number, y: number) => number;

action = plus;
console.log(action(10, 5));  // 15

action = minus;
console.log(action(10, 5));  // 5

Функции как параметры

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

function plus(x: number, y: number): number {
    return x + y;
};
function minus(x: number, y: number): number {
    return x - y;
};

function operation(x: number, y: number, op: (a: number, b: number) => number): number {
    return op(x, y);
}

console.log(operation(10, 5, plus));  // 15
console.log(operation(10, 5, minus));  // 5

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

type MathOperation = (a: number, b: number) => number;

let plus: MathOperation = (x: number, y: number): number => x + y;
let plus: MathOperation = (x: number, y: number): number => x - y;

function operation(x: number, y: number, op: MathOperation): number {
    return op(x, y);
}

console.log(operation(10, 5, plus));  // 15
console.log(operation(10, 5, minus));  // 5

10. Массивы и типы

Массивы определяются с помощью выражения [] и также являются строго типизированными. То есть, если изначально массив содержит строки, то в будущем он сможет работать только со строками.

let numbers: number[] = [10, 20, 30];
let colors: string[] = ['red', 'green', 'blue'];
console.log(numbers[0]); // 10
console.log(colors[1]); // green

Альтернативный способ определения массивов представляет использование конструкции Array<type>, где в угловых скобках указывается тип элементов массива.

let colors: Array<string> = ['red', 'green', 'blue'];
console.log(colors[1]); // green

TypeScript позволяет определять массивы, элементы которых нельзя изменять — с помощью readonle или ReadonlyArray.

const colors: readonly string[] = ['red', 'green', 'blue'];
const colors: ReadonlyArray<string> = ['red', 'green', 'blue'];

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

11. Кортежи и типы

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

let user: [string, number];

Кортеж user представляет собой набор из двух элементов, причем первый элемент имеет тип string, а второй элемент — тип number.

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

let user: [string, number] = ['Сергей', 36];
console.log(user[0]); // Сергей
console.log(user[1]); // 36
let user: [string, number] = ['Сергей', 36];
for (const prop of user) {
    console.log(prop);
}

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

let userOne: [string, number, boolean?] = ['Сергей', 36, true];
let userTwo: [string, number, boolean?] = ['Николай', 41];

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

let mathematics: [string, ...number[]] = ['Математика', 5, 4, 5, 4, 4];
let physics: [string, ...number[]] = ['Физика', 5, 5, 5];

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

const user: readonly [string, number] = ['Сергей', 36]; 
user[1] = 37; // error

12. Перечисление enum

Тип enum или перечисление представляет собой набор логически связанных констант, в качестве значений которых могут выступать как числа, так и строки. По умолчанию, если значения не заданы, то будут использованы значения 0, 1, 2, 3, ….

enum Season {
    Winter,
    Spring,
    Summer,
    Autumn,
};
enum Season {
    Winter = 0,
    Spring = 1,
    Summer = 2,
    Autumn = 3
};

Чтобы лучше понять перечисление — нужно скомпилировать код и посмотеть, что представляет собой Season. Это javascript-объект с числовыми и строковыми ключами и с числовыми и строковыми значениями.

{
    0: 'Winter',
    1: 'Spring',
    2: 'Summer',
    3: 'Autumn',
    Winter: 0,
    Spring: 1,
    Summer: 2,
    Autumn: 3,
}

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

enum Season {
    Winter = 111,
    Spring = 222,
    Summer = 333,
    Autumn = 433,
}
let current: Season = Season.Summer;
console.log(current); // 333

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

enum Season {
    Winter = 'Зима',
    Spring = 'Весна',
    Summer = 'Лето',
    Autumn = 'Осень',
}
const current: Season = Season.Summer;
console.log(current); // Лето

Поиск: JavaScript • TypeScript • Web-разработка • Теория • Типы данных

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.