Насколько строгим является строгий режим Typescript?

JavaScript TypeScript
Насколько строгим является строгий режим Typescript?

предисловие

«Мама, я хочу писать на TypeScript», «Так много ошибок, хватит, малыш?»

"use strict"инструкцииJavaScript 1.8.5 (ECMAScript5)Добавить новое.

До сих пор разработчики внешнего интерфейса в основном включали строгий режим для ввода кода по умолчанию.

Ну ты знаешьTypescriptЕсть ли у него на самом деле свой строгий режим?

1. Typescriptправила строгого режима

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

имя правила объяснять
noImplicitAny Переменные или параметры функции не могут иметь неявные значения.anyТипы.
noImplicitThis не положеноthisКонтекст определяется неявно.
strictNullChecks не положеноnullилиundefinedвозможность.
strictPropertyInitialization Проверяет определенные свойства до и после инициализации внутри конструктора.
strictBindCallApply правильноbind, call, applyБолее строгая проверка типов.
strictFunctionTypes Строго контравариантное сравнение аргументов функций.

2. noImplicitAny

Это правило не позволяет переменным или параметрам функций иметь неявные значения.anyТипы. См. следующий пример:

// Javascript/Typescript 非严格模式
function extractIds (list) {
  return list.map(member => member.id)
}

Приведенный выше пример неверенlistограничение типа,mapзацикленныйitemпараметрmember. пока вTypescriptВ строгом режиме произойдет следующая ошибка:

// Typescript 严格模式
function extractIds (list) {
  //              ❌ ^^^^
  //                 Parameter 'list' implicitly
  //                 has an 'any' type. ts(7006)
  return list.map(member => member.id)
  //           ❌ ^^^^^^
  //              Parameter 'member' implicitly
  //              has an 'any' type. ts(7006)
}

Правильное написание должно быть:

// Typescript 严格模式
interface Member {
  id: number
  name: string
}

function extractIds (list: Member[]) {
  return list.map(member => member.id)
}

1.1 Как обрабатывать собственные события браузера?

Браузер поставляется с событиями, такими какe.preventDefault(), это ключевой код, который предотвращает поведение браузера по умолчанию.

это вTypescriptВ строгом режиме будет сообщено об ошибке:

// Typescript 严格模式
function onChangeCheckbox (e) {
  //                    ❌ ^
  //                       Parameter 'e' implicitly
  //                       has an 'any' type. ts(7006)
  e.preventDefault()
  const value = e.target.checked
  validateCheckbox(value)
}

Если вам нужно использовать его в обычном режимеWeb API, вам необходимо определить расширение глобально. Например:

// Typescript 严格模式
interface ChangeCheckboxEvent extends MouseEvent {
  target: HTMLInputElement
}

function onChangeCheckbox (e: ChangeCheckboxEvent) {
  e.preventDefault()
  const value = e.target.checked
  validateCheckbox(value)
}

1.2 Сторонние библиотеки также должны определять типы

Обратите внимание, что если вы импортируете не-Typescriptбиблиотеку, это также вызывает ошибку, потому что тип импортируемой библиотекиany.

// Typescript 严格模式
import { Vector } from 'sylvester'
//                  ❌ ^^^^^^^^^^^
//                     Could not find a declaration file 
//                     for module 'sylvester'.
//                     'sylvester' implicitly has an 'any' type. 
//                     Try `npm install @types/sylvester` 
//                     if it exists or add a new declaration (.d.ts)
//                     file containing `declare module 'sylvester';`
//                     ts(7016)

Это может быть рефакторинг проектаTypescriptБольшая беда для версии, нужно конкретно определить тип интерфейса сторонней библиотеки

3. noImplicitThis

Это правило не позволяетthisКонтекст определяется неявно. См. следующий пример:

// Javascript/Typescript 非严格模式
function uppercaseLabel () {
  return this.label.toUpperCase()
}

const config = {
  label: 'foo-config',
  uppercaseLabel
}

config.uppercaseLabel()
// FOO-CONFIG

В нестрогом режимеthisнаправлениеconfigобъект.this.labelпросто ищиconfig.label.

но,thisСсылки на функции могут быть неоднозначными:

// Typescript严格模式
function uppercaseLabel () {
  return this.label.toUpperCase()
  //  ❌ ^^^^
  //     'this' implicitly has type 'any' 
  //     because it does not have a type annotation. ts(2683)
}

Если выполнять в одиночкуthis.label.toUpperCase(), потому чтоthisконтекстconfigбольше не существует и сообщить об ошибке, потому чтоlabelнеопределенный.

Один из способов решения этой проблемы — избегатьthisИспользовать функцию без контекста:

// Typescript严格模式
const config = {
  label: 'foo-config',
  uppercaseLabel () {
    return this.label.toUpperCase()
  }
}

Лучшим подходом было бы написать интерфейс, определяющий все типы, вместоTypescriptсделать вывод:

// Typescript严格模式
interface MyConfig {
  label: string
  uppercaseLabel: (params: void) => string
}

const config: MyConfig = {
  label: 'foo-config',
  uppercaseLabel () {
    return this.label.toUpperCase()
  }
}

4. strictNullChecks

Это правило не позволяетnullилиundefinedвозможность. См. следующий пример:

// Typescript 非严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  return article.meta
}

TypescriptВ нестрогом режиме нет проблем с записью таким образом. Но строгий режим доставит вам немного хлопот:

«Ты не можешь этого сделать, еслиfindНе совпало значение? ":

// Typescript严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  return article.meta
  //  ❌ ^^^^^^^
  //     Object is possibly 'undefined'. ts(2532)
}

«Я звезда, ты звезда!»

Таким образом, вы бы изменили его, чтобы он выглядел так:

// Typescript严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  if (typeof article === 'undefined') {
    throw new Error(`Could not find an article with id: ${id}.`)
  }

  return article.meta
}

"Это вкусно!"

5. strictPropertyInitialization

Это правило будет проверять свойства, которые были определены до и после инициализации внутри конструктора.

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

(strictPropertyInitialization, это вонючее имя очень похожеReactМного произвольных атрибутов в исходном коде)

См. следующий пример:

// Typescript非严格模式
class User {
  username: string;
}

const user = new User();

const username = user.username.toLowerCase();

Если включен строгий режим, проверка типов сообщит о дальнейших ошибках:

class User {
  username: string;
  //    ❌  ^^^^^^
  //     Property 'username' has no initializer
  //     and is not definitely assigned in the constructor
}

const user = new User();
/
const username = user.username.toLowerCase();
 //                 ❌         ^^^^^^^^^^^^
//          TypeError: Cannot read property 'toLowerCase' of undefined

Есть четыре решения.

Сценарий №1: Разрешитьundefined

дляusernameопределения свойств обеспечиваютundefinedТипы:

class User {
  username: string | undefined;
}

const user = new User();

usernameсвойства могут бытьstring | undefinedтипа, но пишите так,Необходимо убедиться, что значениеstringТипы:

const username = typeof user.username === "string"
  ? user.username.toLowerCase()
  : "n/a";

это не слишкомTypescript.

Сценарий № 2: Явная инициализация значений свойств

Этот метод немного неуклюж, но он работает:

class User {
  username = "n/a";
}

const user = new User();

// OK
const username = user.username.toLowerCase();

Сценарий №3: присвоить значение в конструкторе

Наиболее полезным решением являетсяusernameКонструктор добавляет параметры, затем назначьте егоusernameАтрибуты.

Таким образом, всякий раз, когдаnew User(), оба должны предоставлять значения по умолчанию в качестве параметров:

class User {
  username: string;

  constructor(username: string) {
    this.username = username;
  }
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

Может такжеpublicМодификатор еще больше упрощает:

class User {
  constructor(public username: string) {}
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

Сценарий № 4: явное утверждение присваивания

В некоторых случаях свойства инициализируются косвенно (с помощью вспомогательных методов или библиотек внедрения зависимостей).

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

class User {
  username!: string;

  constructor(username: string) {
    this.initialize(username);
  }

  private initialize(username: string) {
    this.username = username;
  }
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

сквозьusernameдобавляет явное утверждение присваивания, мы сообщаем средству проверки типов:username, он может ожидать, что свойство будет инициализировано, даже если не может обнаружить само свойство.

6. strictBindCallApply

Это правило будетbind, call, applyОпределяйте типы более строго.

Что ты имеешь в виду? См. следующий пример:

// JavaScript
function sum (num1: number, num2: number) {
  return num1 + num2
}

sum.apply(null, [1, 2])
// 3

Когда вы не помните тип параметра, тип и номер параметра не проверяются в нестрогом режиме.При запуске кодаTypescriptи среда (вероятно, браузер) не выдает ошибку:

// Typescript非严格模式
function sum (num1: number, num2: number) {
  return num1 + num2
}

sum.apply(null, [1, 2, 3])
// 还是...3?

а такжеTypescriptВ строгом режиме это запрещено:

// Typescript严格模式
function sum (num1: number, num2: number) {
  return num1 + num2
}

sum.apply(null, [1, 2, 3])
//           ❌ ^^^^^^^^^
//              Argument of type '[number, number, number]' is not 
//              assignable to parameter of type '[number, number]'.
//                Types of property 'length' are incompatible.
//                  Type '3' is not assignable to type '2'. ts(2345)

Что делать тогда?“...”оператор спреда иreduceстарый друг пришел на помощь:

// Typescript严格模式
function sum (...args: number[]) {
  return args.reduce<number>((total, num) => total + num, 0)
}

sum.apply(null, [1, 2, 3])
// 6

7. strictFunctionTypes

Это правило будет проверять и ограничивать параметры типа функции, чтобы они были инвариантными (contravariantly) вместо двойного (bivariantly, то есть ковариантный или устойчивый).

На первый взгляд внутренняя ОС: «Что это?», вот введение:

Что такое ковариантность и контравариантность?

Ковариантные и контравариантные вики очень сложны, но принцип на самом деле один.

  • Подтипы могут быть неявно преобразованы в супертипы.

Чтобы привести пример, который легче всего понять,intа такжеfloatДва типа отношений могут быть записаны следующим образом.intfloat: то естьintдаfloatподтип .

Эта более строгая проверка применяется ко всем типам функций, кроме объявлений методов или конструкторов. Методы специально исключены, чтобы гарантировать, что универсальные классы и интерфейсы (такие как Array ) в целом остаются ковариантными.

Посмотрите на следующееAnimalдаDogа такжеCatПример супертипа:

declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;
declare let f3: (x: Cat) => void;
f1 = f2;  // 启用 --strictFunctionTypes 时错误
f2 = f1;  // 正确
f2 = f3;  // 错误
  1. Первый оператор присваивания разрешен в режиме проверки типов по умолчанию, но помечен как ошибка в режиме строгого ввода функций.
  2. И шаблон строгой функциональной типизации помечает это как ошибку, потому что это не может быть оправдано.
  3. В любом шаблоне третье присваивание неверно, потому что оно никогда не имеет смысла.

Другой способ описать этот пример состоит в том, что в режиме проверки типов по умолчаниюTТип(x: T) => voidявляется изменчивым, но в режиме строгой функциональной типизацииTустойчив к:

interface Comparer<T> {
    compare: (a: T, b: T) => number;
}

declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;

animalComparer = dogComparer;  // 错误
dogComparer = animalComparer;  // 正确

Пишу здесь, вынужденный убить новичка.

Резюме и справка

Справочная статья:

  1. Насколько строг строгий режим Typescript?
  2. Как следует понимать ковариантную контравариантность в языках программирования?
  3. Строгая типизация функций TypeScript

На собеседованиях вас часто спрашиваютЗачемTypescriptСравниватьJavaScriptлегко использовать?

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

❤️ После прочтения трех вещей

Если вы найдете этот контент вдохновляющим, я хотел бы пригласить вас сделать мне три небольших одолжения:

  1. Ставьте лайк, чтобы больше людей увидело этот контент
  2. Обратите внимание на паблик «Учитель фронтенд-убеждения», и время от времени делитесь оригинальными знаниями.
  3. Также смотрите другие статьи

Вы также можете прийти ко мнеGitHubПолучите исходные файлы всех статей в блоге:

Руководство по убеждению:GitHub.com/Roger-Hi RO/…