Главная Категории Контакты Поиск

Строгая инициализация свойств в TypeScript

Повышение безопасности использования класса в TypeScript с помощью строгой инициализации свойств.

TypeScript ·30.10.2019·читать 4 мин 🤓·Автор: Alex Myzgin

Устанавливая флаг strictPropertyInitialization в файле .tsconfig, TypeScript начнет выдавать ошибки, если мы не инициализируем все свойства классов при построении. Давай рассмотрим, как можно исправить ошибки, определив свойства непосредственно или в теле конструктора. И если мы не можем выполнить инициализацию напрямую, но уверены, что свойство будет назначено во время выполнения, можно использовать оператор утверждения определённого присваивания !, чтобы попросить TypeScript игнорировать это свойство.

Например, у нас есть класс Books с одним свойством titles, его тип - массив строк. Под нашим классом Books мы создаем новый экземпляр этого класса через new.

class Books {
  titles: string[];

  constructor() {}
}
const books = new Books();

const shortTitles = books.titles.filter(
  title => title.length < 5
);

В этом примере, очевидно, что массив titles не был инициализирован и он неопределенен undefined.

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

Если мы откроем терминал (командную строку) и попытаемся запустить сгенерированный JavaScript, то получим ошибку Cannot read property 'filter' of undefined.

Базовая настройка TypeScript проекта.

В TypeScript 2.7 появился новый флаг strictPropertyInitialization, который предупредит нас об этих типах проблем во время компиляции. Чтобы это работало, нам также нужно включить флаг strictNullChecks. Добавляем это в файл .tsconfig:

{
  "compilerOptions": {
    "strictPropertyInitialization": true,
    "strictNullChecks": true
  }
}

strictPropertyInitialization - включит строгую проверку инициализации свойств в классах. strictNullChecks - включит строгие проверки на ноль.

Также, можно включить все строгие проверки, просто добавив флаг strict. Он включит все строгие опции проверки типов.

{
  "compilerOptions": {
    "strict": true
  }
}

Если вернемся к нашему файлу, то увидим, что в нашем массиве titles есть проблема. Теперь TypeScript говорит о том, что он видит массив titles как массив строк, но тип массива строк отличается от типа undefined.

class Books {
  titles: string[]; // string[] !== undefined

  constructor() {}
}

Поскольку TypeScript нигде не видит инициализацию titles, он выдает ошибку - Property 'titles' has no initializer and is not definitely assigned in the constructor.. Вот почему нам нужно включить флаг strictNullChecks - он позволит разделить undefined, null и все другие типы.

Для простого исправления можем добавить | undefined, к объявлению типа.

class Books {
  titles: string[] | undefined;

  constructor() {}
}

Теперь ошибка переместилась в books.titles, поскольку, мы сказали что titles могут быть undefined. Чтобы это исправить, нам нужно сделать проверку на то, что titles определен. Например, обернуть его в if блок.

class Books {
  titles: string[] | undefined;

  constructor() {}
}
const books = new Books();

if (books.titles) {
  const shortTitles = books.titles.filter(title => title.length < 5);
}

Всё отлично работает! Включив этот флаг, мы защищены от потенциальных ошибок во время выполнения, но, добавление проверок на undefined везде, где мы используем экземпляры этого класса, может стать громоздким. Другой вариант - инициализировать свойство непосредственно в классе значением по умолчанию. Убираем undefined, так как знаем, что titles будет инициализирован с самого начала.

class Books {
  titles: string[] = ["Eloquent JavaScript", "JavaScript: The Good Parts"];

  constructor() {}
}
const books = new Books();

const shortTitles = books.titles.filter(title => title.length < 5);

Мы также можем инициализировать titles в constructor(). Это также устранит ошибку, так как мы по-прежнему гарантируем, что после инициализации все свойства этого класса будут определены.

class Books {
  titles: string[];

  constructor() {
    this.titles = ["Eloquent JavaScript", "JavaScript: The Good Parts"];
  }
}

// или более лаконичный синтаксис через `public`

class Books {
  constructor(
    public titles: string[] =
      ["Eloquent JavaScript", "JavaScript: The Good Parts"]
  ) {}
}

Также, мы можем использовать библиотеку внедрения зависимостей, которая, как мы знаем, инициализирует свойства всех этих классов во время выполнения. В этом случае можем указать восклицательный знак !, после имени свойства. Это называется оператор утверждения определённого присваивания (definite assignment assertion operator). Это своего рода способ сообщить TypeScript, что данное свойство будет инициализировано.

class Books {
  titles!: string[];

  constructor() {}
}

Имей в виду, что теперь мы вернулись к исходной точке в отношении защиты во время компиляции, которую получаем из TypeScript. books.titles.filter всё ещё выдаст ошибку. Тем не менее, добавив ! явно для каждого свойства, заставит разработчиков задуматься о небезопасном использовании добавляемых ими свойств.

Заключение

Давай подытожим! Мы посмотрели, как включить флага strictPropertyInitialization, который даёт нам больше уверенности при использовании свойств экземпляра и устраняет много потенциальных ошибок во время выполнения.

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

class Books {
  titles!: string[];
  language: string = "JavaScript";
  isLoading: boolean;

  constructor() {
    this.isLoading = true;
  }
}

Website, name & logo
Copyright © 2019. Alex Myzgin