TypeScript. Начало работы, часть 2 из 7
20.02.2023
Теги: JavaScript • TypeScript • Web-разработка • Теория • ТипыДанных
Файл tsconfig.json
Во второй части рассмотрим три группы настроек — Strict Checks, Linter Checks и Advanced.
На самом деле, TypeScript из коробки мало чем отличается от JavaScript. Поэтому если изначально не изменить настройки проекта, то большая часть преимуществ языка не будет задействована. Если выразить смысл второй части одним предложением — надо открыть tsconfig.json
и установить флаг strict
в секции compilerOptions
в значение true
.
Группа Strict Checks
К опциям этой группы относятся alwaysStrict
, noImplicitAny
, strictNullChecks
, strictFunctionTypes
, strictPropertyInitialization
, noImplicitThis
, strictBindCallApply
и strict
. Все опции по умолчанию имеют значение false
, а для того, чтобы было строго — нужно установить противоположное значение.
Опция strict
Значение по умолчанию — false
.
При установке значения true
— будут активированы абсолютно все флаги группы Strict Checks. У такого подхода есть как минимум один недостаток — неочевидность. Устанавливая true
для strict
, нет наглядного представления, какие именно проверки включены и какие опции вообще существуют.
{ "compilerOptions": { // Общие настройки компиляции "target": "es2015", "moduleResolution": "node16", "module": "es2015", "outDir": "build", "sourceMap": true, "removeComments": true, "newLine": "lf", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, // Группа Strict Checks "alwaysStrict": true, "noImplicitAny": true, "strictNullChecks": true, "strictPropertyInitialization": true, "strictFunctionTypes": true, "noImplicitThis": true, "strictBindCallApply": true } }
Опция alwaysStrict
Значение по умолчанию — false
. Если включена опция strict
, тогда true
.
Компилятор будет парсить код в strict mode
и добавлять use strict
в выходные javascript файлы.
use strict
.
Опция noImplicitAny
Значение по умолчанию — false
. Если включена опция strict
, тогда true
.
Для начала несколько слов про any
. Это специальный тип, который назначается всем переменным, если их тип не был задан явно и не может быть выведен компилятором автоматически. Данный тип создан для обратной совместимости с JavaScript. С точки зрения TypeScript, все переменные в JavaScript — это any
, поскольку в нём нет системы типов.
// тип задан явно
let foo: number = 5;
// тип выведен
let bar = 'hello';
// тип не указан и не может быть выведен
// value будет неявно объявлено как any
function baz(value) {
// ошибка останется незамеченной
console.log(value.subtr(3));
}
Опция noImplicitAny
будет выдавать ошибки при компиляции — чтобы разработчик не забывал указывать типы. Однако это не означает, что теперь вовсе нельзя использовать any
. Это означает лишь, что в подобных ситуация разработчик должен явно писать any
, если по каким-то причинам не получается правильно описать тип переменной.
// явно помечаем код как потенциально небезопасный через any
function baz(value: any) {
console.log(value.subtr(3));
}
Явное присутствие any
напоминает разработчику о том, что код не доработан и эти места должны быть улучшены со временем.
Опция strictNullChecks
Значение по умолчанию — false
. Если включена опция strict
, тогда true
.
В JavaScript есть значения undefined
и null
, в TypeScript для этих значений есть одноимённые типы. По иерархии считается, что все остальные типы происходят от них. Таким образом по принципу наследования вместо переменной любого типа – string
, boolean
, number
– можно передать значение undefined
или null
.
// неожиданный результат если value будет undefined или null
function foo(value: number) {
return value * 2;
}
foo(5);
// по умолчанию можно так
foo(null);
foo(undefined);
На практике это может вызывать неудобства в виде написания дополнительных проверок на c. С опцией strictNullChecks
компилятор не позволит передавать undefined
или null
туда, где ожидаются переменные других типов, если это не разрешено явно.
function foo(value: number) {
return value * 2;
}
foo(5);
// вызовы функции невозможны
foo(null);
foo(undefined);
Это позволяет не обрабатывать ситуации с undefined
и null
в тех участках кода, где возникновение данных значений невозможно. При этом в тем местах, где возникновение подобных ситуаций возможно, компилятор не позволит забыть о проверках.
// «?» разрешает undefined, а «null» разрешает null
function foo(value?: number | null) {
if (value === null || value === undefined) {
return 0;
}
return value * 2;
}
Опция strictPropertyInitialization
Значение по умолчанию — false
. Если включена опция strict
, тогда true
.
Опция позволяет контролировать, чтобы объявленные свойства класса всегда были инициализированы.
class User {
name: string;
// email не инициализирован ни здесь, ни в конструкторе
// компилятор подскажет, что нужно установить значение
email: string;
constructor(name: string) {
this.name = name;
}
}
Опция является своего рода дополнением к опции strictNullChecks
, поэтому работает только вместе с ней.
Опция strictFunctionTypes
Значение по умолчанию — false
. Если включена опция strict
, тогда true
.
Опция включает более строгую проверку сигнатур функций.
type StringOrNumberFunc = (value: string | number) => void;
function foo(value: string) {
console.log(value);
}
// string | number не эквивалентны string
const bar: StringOrNumberFunc = foo;
Опция noImplicitThis
Значение по умолчанию — false
. Если включена опция strict
, тогда true
.
При использовании this
проверяет, что контекст выполнения известен (см. здесь).
class SomeClass {
multiplier = 5;
createSomeFunction(value: number) {
return function () {
// контекст потерян — здесь this не является объектом класса SomeClass
return value * this.multiplier;
};
}
}
Объект this
неизвестен, так как function
не пробрасывает контекст автоматически. В данном случае это можно исправить, заменив function
на стрелочную функцию. Рассмотрим еще один пример независимой функции, которая по каким-то причинам должна использовать внешний this
.
function sayHello(name: string) {
console.log(this.helloWord + ' ' + name);
}
В данном случае this
также неизвестен. Выполнить функцию с внешним this
можно с помощью bind
, call
, apply
. А научить функцию понимать контекст можно следующим образом.
// укажем тип this первым «аргументом» — фактически,
// это не будет считаться аргументом функции
function sayHello(this: HelloStyle, name: string) {
console.log(this.helloWord + ' ' + name);
}
// объявляем варианты приветствий
interface HelloStyle {
helloWord: string;
}
class HawaiiStyle implements HelloStyle {
helloWord = 'Aloha';
}
class RussianStyle implements HelloStyle {
helloWord = 'Привет,';
}
sayHello.bind(new HawaiiStyle())('World');
sayHello.call(new RussianStyle(), 'World');
sayHello.apply(new RussianStyle(), ['World']);
Опция strictBindCallApply
Значение по умолчанию — false
. Если включена опция strict
, тогда true
.
Опция включает более строгую проверку сигнатур при использовании методов bind
, call
, apply
.
function foo(value: string) {
console.log(value);
}
foo.call(undefined, 'hello'); // правильно
foo.call(undefined, false); // ошибка
Группа Linter Checks
Название группы говорит само за себя — подразумевается, что флаги этой группы проверяют не соответствие типов, а качество кода. И действительно, некоторые правила секции Linter Checks можно заменить на аналогичные правила ESLint.
{ "compilerOptions": { // Общие настройки компиляции "target": "es2015", "moduleResolution": "node16", "module": "es2015", "outDir": "build", "sourceMap": true, "removeComments": true, "newLine": "lf", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, // Группа Strict Checks "alwaysStrict": true, "noImplicitAny": true, "strictNullChecks": true, "strictPropertyInitialization": true, "strictFunctionTypes": true, "noImplicitThis": true, "strictBindCallApply": true, // Группа Linter Checks "noImplicitReturns": true, "noFallthroughCasesInSwitch": false, "noUnusedLocals": false, "noUnusedParameters": false, } }
false
вместо true
. Дело в том, что если следовать рекомендациям по настройке ESLint для проекта на TypeScript, то первую опцию группы Linter Checks нужно включить — потому что сответствующее правило ESLint отключено. А последние три опции нужно отключить — потому что соответсвующие правила ESLint включены. Если три последние опции будут включены, мы будем получать два предупреждения — от компилятора TypeScript и от линтера ESLint.
Опция noImplicitReturns
Значение по умолчанию — false
.
Опция проверяет, чтобы все ветки функции возвращали значение. Существует аналогичное правило ESLint consistent-return.
function isBlue(color: 'blue' | 'black') {
if (color === 'blue') {
return true;
}
}
Опция noFallthroughCasesInSwitch
Значение по умолчанию — false
.
Опция проверяет наличие break
в операторе switch/case
. Существует аналогичное правило ESLint no-fallthrough.
switch (foo) {
case 1:
bar();
// забыли break
case 2:
baz();
}
Опция noUnusedLocals
Значение по умолчанию — false
.
Опция проверяет код на наличие неиспользуемых переменных. Существует аналогичное правило ESLint no-unused-vars.
const createKeyboard = (model: number) => {
const defaultModel = 23;
return { type: 'keyboard', model };
};
Опция noUnusedParameters
Значение по умолчанию — false
.
Опция проверяет код на наличие неиспользуемых аргументов функций и методов. Существует аналогичное правило ESLint no-unused-vars.
const createKeyboard = (model: number) => {
const defaultModel = 23;
return { type: 'keyboard', model: defaultModel };
};
Группа Advanced
Это довольно странная группа опций — первые две опции было бы логично включить в группу Linter Checks, а остальные — в группу Strict Checks.
{ "compilerOptions": { // Общие настройки компиляции "target": "es2015", "moduleResolution": "node16", "module": "es2015", "outDir": "build", "sourceMap": true, "removeComments": true, "newLine": "lf", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, // Группа Strict Checks "alwaysStrict": true, "noImplicitAny": true, "strictNullChecks": true, "strictPropertyInitialization": true, "strictFunctionTypes": true, "noImplicitThis": true, "strictBindCallApply": true, // Группа Linter Checks "noImplicitReturns": true, "noFallthroughCasesInSwitch": false, // есть правило eslint "noUnusedLocals": false, // есть правило eslint "noUnusedParameters": false, // есть правило eslint // Группа Advanced "allowUnreachableCode": false, "allowUnusedLabels": true, // есть правило eslint "suppressExcessPropertyErrors": false, "suppressImplicitAnyIndexErrors": false, "noStrictGenericChecks": false } }
Первые две опции allowUnreachableCode
и allowUnusedLabels
по умолчанию имеют значение true
, однако для строгости должны быть false
— ровно наоборот.
Последние три опции по умолчанию имеют значение false
и должны быть false
для строгости — их не нужно трогать, это минимальная типобезопасность TypeScript из коробки.
Опция allowUnreachableCode
Значение по умолчанию — true
.
Опция запрещает недосягаемый код — то есть, написанный после операторов return
, throw
, break
, continue
. Существует аналогичное правило ESLint no-unreachable.
function foo(value: number) {
if (value > 5) {
return true;
} else {
return false;
}
// недосягаемый код
return true;
}
Опция allowUnusedLabels
Значение по умолчанию — true
.
Опция запрещает неиспользуемые лэйблы. Существует аналогичное правило ESLint no-unused-labels.
// УДАЛИТЬ, все равно метки никто не использует
OUTER_LOOP:
for (const student of students) {
if (checkScores(student.scores)) {
continue;
}
doSomething(student);
}
Опция suppressExcessPropertyErrors
Значение по умолчанию — false
, изменять противопоказано.
Флаг проверяет, чтобы объект не мог содержать свойства, которые не были описаны в его структуре.
interface Point {
x: number;
y: number;
}
const p: Point = {
x: 1,
y: 3,
z: 10, // свойство z не объявлено в интерфейсе Point
};
Опция suppressImplicitAnyIndexErrors
Значение по умолчанию — false
, изменять противопоказано.
Опция проверяет, что невозможно обращаться к свойствам объекта, которые в нём не объявлены, используя скобочную нотацию.
interface User {
login: string;
email: string;
}
const user: User = {
login: 'hello',
email: 'hello@example.com',
};
// теперь обратиться к name нельзя
const username = user['name'];
Опция noStrictGenericChecks
Значение по умолчанию — false
, изменять противопоказано.
Опция позволяет сделать компилятор «более лояльным» при работе с generics
.
type Foo = <T, U>(x: T, y: U) => [T, U];
type Bar = <S>(x: S, y: S) => [S, S];
function baz(foo: Foo, bar: Bar) {
bar = foo;
foo = bar; // ошибка
}
Дополнительно
- Раскладываем tsconfig.json по полочкам, часть 1
- Раскладываем tsconfig.json по полочкам, часть 2
- Руководство по TypeScript
- Intro to the TSConfig Reference
- Centralized Recommendations for TSConfig bases
Поиск: JavaScript • TypeScript • Web-разработка • Теория • Типы данных