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

20.02.2023

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

Файл 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 файлы.

Строгий режим появился ECMAScript 5 (ES5). Он добавил новые возможности в язык и изменил некоторые из существующих. Чтобы устаревший код работал, как и раньше, по умолчанию подобные изменения не применяются. Поэтому нужно явно их активировать с помощью специальной директивы 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,
    }
}
В следующей части рассмотрим настройку проекта TypeScript в редакторе VS Code — тогда станет понятно, почему последние три опции имеют значение 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; // ошибка
}

Дополнительно

Поиск: 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.