TypeScript. Начало работы, часть 4 из 7
26.03.2023
Теги: JavaScript • TypeScript • Web-разработка • Теория • ТипыДанных
Основы 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-разработка • Теория • Типы данных