TypeScript

Для работы с TS надо установить tslint и typescript.

Конфиги

tscconfig.json (файл в корне) конфигурирет компиляцию в js, так как браузеры, разумеется, не читают TS. Пример простого конфига (tsconfig.json):

{
  "compilerOptions": {
    "module": "commonjs",
    "esModuleInterop": true,
    "target": "es6",
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "dist"
  },
  "lib": ["es2015"]
}
  • target – в какой формат js будет компилиться.
  • moduleResolution – окружение, в котором работаем и будем в нем транспилить.
  • outDir – папка, куда будет помещено потом все.
  • lib – какой библиотекой транспилим.

Если добавить еще, например, "strict": true, то это сделает работу TS более строгой, заставив искать всевозможные баги, неправильные написания, непомеченые типы и т.д. Без этой строчки “ругаться” будет намного меньше, разве что при транспиле уже.

Пример файла tslint.json:

{
  "defaultSeverity": "error",
  "extends": ["tslint:recommended"],
  "jsRules": {},
  "rules": {
    "no-console": false
  },
  "rulesDirectory": []
}

Примеры типизации

Переменные

let isVisited: boolean;
let animal: string | null = "cat"; //строка либо ноль
let users: []; //массив произвольный
let listUsers: any[]; //массив произвольный (альтернативная запись)
let categories: string[] //массив строк
let countries: Array<string> //массив строк (альтернативная запись)
let data: Array<string | number> //массив со строками и/или числами
const authorizationData: {login: string; password: string}; //это типизация объекта для "ленивых". Так делать не стоит, а надо писать интерфейсы (о них ниже)

Если значение присваивается сразу, то тип установится автоматически. Линтер может быть так настроен, что будет даже предупреждать, что указание типа излишне, так как его присвоило автоматом. Но есть разработчики, которые любят все равно прописывать типы “принудительно” и отключают эту опцию в конфиге линтера.

Функция

cosnt foo = (a: string, b: string): number => {
  return a > b ? -1 : a < b ? 1 : 0;
}

let result: number;

result = [].sort(foo)

Здесь мы видим, что сортировочная функция foo принимает в параметры 2 строки, а возвращает число. Потом мы для результата создаем переменную с типом число. Если бы где-то не совпали типы, то мы сразу увидели бы ошибку с подсказкой, в чем и заключается преимущество TS.

Пример (наверное, с ошибками) типизации инлайновых функций, которые можно часто встретить в библиотеках:

const foo: (a: User, b: number) => number = {
    return a[b];
}

Объект

Объекты могут типизироваться так называемыми классами (class) либо интерфейсами (interface) или типами (type).

Класс – это шаблон, на основе которого мы можем создавать объекты с одинаковой конфигурацией, т.е. свойствами и методами. Пример класса:

class User {
    name: string;
    age: number;
    print(){
        console.log(`name: ${this.name}  age: ${this.age}`);
    }
    toString(): string{
        return `${this.name}: ${this.age}`;
    }
}
 
let tom = new User();
tom.name = "Tom";
tom.age = 36;
tom.print();                    // name: Tom  age: 36
 
console.log(tom.toString());    // Tom: 36

Тип и интерфейс – это 2 разные сущности, обе из которых представляют группу свойств и методов, описывающих объект, но не обеспечивающих их реализации или инициализацию.

Типы и интерфейсы между собой очень похожи, за исключением незначительных отличий в синтаксисе (type = {…} против interdace {…}) и в поведении (типы в некоторых случаях не работают, когда применяется оператор union, т.е. |, а также не способны расширяться способом повторного объявления с новым свойством внутри).

Интерфейсы – по сути самый расхожий инструмент в TS. Хотя часто применяют type, из-за более короткого синтаксиса, там где нет смысла в интефейсе.

Создаем интерфейс и назначем его для объекта:

interface Animal {
  readonly id: number;
  name: string;
  weight?: number;
}

const dog: Animal = {
  id: 1,
  name: "Tuzik",
  weight: "5"
}
  • Вопросительный знак говорит о необязательности значения.
  • readonly запрещает дальнейшее изменение поля где-то в коде. В классах, в отличие от переменных, кроме readonly есть еще несколько свойств, которые можно назначить полю: private, protected и static.  Они роль играют при расширении класса, т.е. extends (создании новых классов на основе родительского).

Благодаря TS, мы можем во время присвоения типа объекту навести на него, нажать ctrl и увидеть, что в нем лежит.

Обычно сам интерфейс выносят из кода (в другой файл или хотя бы в самый низ).

Более того, внутри интерфейса можно даже типизировать ключи свойств:

interface Obj {
  [key: string]: string;
}

Там также может быть не примитивный тип в кее, а даже интерфейс.

Можно создавать интерфейсы на основе других интерфейсов (“экстендить” их).

Быстрая типизация при помощи “as”

Иногда бывают ситуации, что в переменную приходит один из двух объктов с разными интерфейсами (a: User | Animal). При этом у одного объекта (и интерфейса) есть определенное поле, а у другого его нет, допустим вес, т.е. weight. Чтобы не получать ошибки и быстро получить значение, можно принудительно указать вот так: (a as Animal).weight Это, конечно, “г-нокод”. Но зато быстрый способ.

Например, такая конструкция поможет что-то быстро получить из window, указав ему тип any: (window as any).location.

Применение enum (перечеслений)

Это одна из немногих возможностей TypeScript, которые не являются расширением JavaScript на уровне типов.

По сути enum – этнабор именованых констант, которые потом где-то используються. Они могут быть текстовыми и числовыми. Также могут экспорится.

Пример:

export enum ACTION_ENITY_KEY {
    manId = 'manId',
    womanId = 'womanId',
    childId = 'child'
}

type ActionInstances = {
    [key in ACTION_ENITY_KEY]?: number;
};

export interface AnaliticsAction extends ActionInstances {
    marker: ANALITIC_ACTION_TYPE;
    itemId?: number;
};

export enum ANALITIC_ACTION_TYPE {
    page_visited = 'page_visited',
    page_clicked = 'page_clicked',
    added_to_cart = 'added_to_cart',
    purchase_finished = 'purchase_finished'
};

const obj: AnaliticsAction = {
    marker: ANALITIC_ACTION_TYPE.page_visited,
    manId: 1
}

Здесь мы создали первый enum ACTION_ENITY_KEY и применили его для ключа необязательного свойства в типе ActionInstances. Затем этот тип ActionInstances расширили и создали на его основе интерфейс AnaliticsAction, у которого внутри применяется другой enum ANALITIC_ACTION_TYPE для одного из свойств.

В конце создаем объект obj, указывая для него интерфейс AnaliticsAction  и в результате можем указать значения только из enum для marker и для названия следующего ключа (manId в примере).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *