TypeScript

Evgeny Markov

План лекции

  1. Стандарт ECMAScript
  2. Babel
  3. TypeScript

Mocha/LiveScript/JavaScript

Brendan Eich

ECMAScript

ECMAScript TC39

Эволюция ECMAScript

  • ES1, ES2 — первая редакция
  • ES3 (1999) — RegExp, try/catch
  • ES4 — ...
  • ES5 (2009) — строгий режим, геттеры и сеттеры, нативная поддержка JSON, новые методы Array и Object

Пример кода на ES5


            function Student(firstName, lastName) {
              this.firstName = firstName;
              this.lastName = lastName;
            };
          

            Object.defineProperty(Student.prototype, "name", {
              get: function() {
                return this.firstName + ' ' + this.lastName;
              }
            });
          

            Student.prototype.setSkills = function(skills) {
              this.skills = skills || [];
            };
          

ES2015 (ES6)

Классы


              class Student {
                constructor(firstName, lastName) {
                  this.firstName = firstName;
                  this.lastName = lastName;
                }

                get name() {
                  return `${this.firstName} ${this.lastName}`;
                }

                setSkills(skills = []) {
                  this.skills = skills;
                }
              }
            

let и const


              function foo() {
                var apples = 5;
              }
            

              {
                let apples = 10;
              }
            

              {
                const apples = 15;
              }
            

Стрелочные функции


              // краткий синтаксис
              const func = x => x * x;

              // блочный синтаксис
              const func = (x, y) => { return x + y; };
            

Шаблонные строки


                `строка текста`

                `строка текста 1
                  строка текста 2`

                `строка текста ${выражение} строка текста`
              

Деструктуризация массивов


                var zero, one, two;
                var numbers = ['zero', 'one', 'two'];

                zero = numbers[0];
                one = numbers[1];
                two = numbers[2];
              

                const numbers = ['zero', 'one', 'two'];
                const [zero, one, two] = numbers;
              

Деструктуризация объектов


                var p, q;
                var obj = { p: 42, q: true };

                p = obj.p;
                q = obj.q;
              

                const obj = { p: 42, q: true };
                const { p, q } = obj;
              

Промисы


                const promise = new Promise((resolve, reject) => {
                  setTimeout(() => resolve('result'), 1000);
                  setTimeout(() => reject(new Error('ignored')), 2000);
                });

                promise.then(
                  result => alert('Fulfilled: ' + result),
                  error => alert('Rejected: ' + error)
                );
              

ECMAScript 2016

Оператор возведения в степень


                2 ** 3 // 8
                3 ** 2 // 9
                3 ** 2.5 // 15.588457268119896
                10 ** -1 // 0.1
              

Метод includes у Array и String


                const str = 'Быть или не быть вот в чём вопрос.';

                str.includes('Быть'); // true
                str.includes('вопрос'); // true
                str.includes('несуществующий'); // false
              

ECMAScript 2017

async/await


                async function getAuthorAvatar(article) {
                  const userResponse = await fetch(`/articles/${article}`);
                  const { author } = await userResponse.json();

                  const avatarResponse = await fetch(`/avatars/${author.id}`);
                  const avatar = await avatarResponse.json();

                  return avatar.url;
                }
              

ECMAScript 2018

Операторы Rest и Spread для массивов


                const numbers = [1, 2, 3, 4, 5];
                const [first, second, ...others] = numbers;
                console.log(others); // [3, 4, 5]
              


                const others = [3, 4, 5];
                const numbers = [1, 2, ...others];
              

Операторы Rest и Spread для объектов


              const numbers = {
                zero: 1,
                one: 2,
                three: 3,
                four: 4
              };
              const { zero, one, ...others } = numbers;
              console.log(others); // { three: 3, four: 4 }
            


              const others = { three: 3, four: 4 };
              const numbers = { zero: 1, one: 2, ...others };
            

Снова промисы


                fetch('file.json')
                  .then(data => data.json())
                  .catch(error => console.error(error))
                  .finally(() => console.log('finished'));
              

ECMAScript 2019

Optional Catch Binding


                  // До
                  try {
                      ...
                  } catch (error) {
                      ...
                  }

                  // После
                  try {
                      ...
                  } catch {
                      ...
                  }
              

Array flat & flatMap


              const nestedArrays = ['a', ['b', 'c'], ['d', ['e', 'f']]];
              const flattenArray = nestedArrays.flat(2);
              // ['a', 'b', 'c', 'd', 'e', 'f']
            

              const words = ['мой любимый', 'ванильный раф'];
              const splittedWords = words.map(chunk => chunk.split(' '));
              // [['мой', 'любимый'], ['ванильный', 'раф']]
              const flattenWords = words.flatMap(chunk => chunk.split(' '));
              // ['мой', 'любимый', 'ванильный', 'раф']
            

Object.fromEntries


              const arr = [['name', 'Harry'], ['surname', 'Potter']];
              const obj = Object.fromEntries(arr);
              // { name: 'Harry', surname: 'Potter' }
            

String trimStart() & trimEnd()


              const one = '      Привет и позвольте ';
              const two = 'нам начать.        ';

              one.trimStart() + two.trimEnd()
              // 'Привет и позвольте нам начать.'
            

Стабильная сортировка (Timsort)

Sort algorithms benchmark

Будущее

Изменения в классах


              class Cat extends Animal {
                #name;
                static #count = 0;

                static getCount() {
                  return `На свете ${Cat.#count} кошечек.`;
                }

                static #increaseCount() {
                  Cat.#count++;
                }

                constructor(name = 2) {
                  super();
                  this.name = name;
                  Cat.#increaseCount();
                }
              }
            

Дальнейшее развитие ECMAScript

GitHub Logo

tc39/ecma262

Можно ли всё это использовать прямо сейчас?

Браузеры в России

Браузеры в России по данным Яндекс.Метрики Данные из отчёта «Яндекс.Радара» на 10 апреля 2019

Таблица совместимости

https://kangax.github.io/compat-table

Разный уровень поддержки языка

Venn Diagram
Как решать проблему?

Транспиляция — это процесс перевода исходного кода одного языка в другой

Transpilation

Babel Playground

How Babel works

Код на ES2015


            class Student {
              constructor(firstName, lastName) {
                this.firstName = firstName;
                this.lastName = lastName;
              }

              setSkills(skills = []) {
                this.skills = skills;
              }
            }
          

Код после транспиляции


            var Student = function () {
              function Student(firstName, lastName) {
                _classCallCheck(this, Student);

                this.firstName = firstName;
                this.lastName = lastName;
              }

              _createClass(Student, [{
                key: "setSkills",
                value: function setSkills() {
                  var skills =
                    arguments.length > 0 &&
                    arguments[0] !== undefined ? arguments[0] : [];

                  this.skills = skills;
                }
              }]);

              return Student;
            }();
          

Система плагинов

Plugins

  • arrow-functions
  • classes
  • destructuring
  • duplicate-keys
  • for-of
  • ...

Эксперименты


            const nestedNumbers = {
              one: {
                two: {
                  three: 42
                }
              }
            };

            const answer = nestedNumbers?.one?.two?.three; // 42

            const safe = nestedNumbers?.four?.five?.baz; // undefined
          
Pause

Что не так?


            function addNumbers(a, b) {
              return a + b;
            }
          

            addNumbers(2, 2); // 4
          

              addNumbers(2, '2');
            

              // '22'
            

              addNumbers([], []);
            

              // ''
            

              addNumbers([], {});
            

              // [object Object]
            

              addNumbers({}, []);
            

              // 0
            

Как исправить?


              function addNumbers(a, b) {
                if (typeof a !== 'number' || typeof b !== 'number') {
                  throw new Error();
                }

                return a + b;
              }
            

Что в IDE?

no IDE completion

Слабая динамическая типизация

  • Неявные преобразования
  • Типы становятся известны на этапе выполнения

Альтернативные языки

Type Systems

TypeScript Playground

TypeScript venn diagram

Автор языка

Anders Hejlsberg
Андерс Хейлсберг
Известен также по Turbo Pascal, Delphi, C#

Возможности

  • ES2015-ESNext
  • Транспиляция в ES3, ES5, ES2015, ...
  • Аннотации типов и проверка
  • Вывод типов
  • Интерфейсы
  • Обобщённые типы
  • Кортежи

Примитивные типы данных

Boolean


              let isDone: boolean = false;
            

Number


              const decimal: number = 6;
              const hex: number = 0xf00d;
              const binary: number = 0b1010;
              const octal: number = 0o744;
            

String


              const color1: string = "blue";
              const color2: string = 'red';

              const fullName: string = `Arkady`;
              const sentence: string = `Hello, my name is ${fullName}.`;
            

null, undefined


              const u: undefined = undefined;
              const n: null = null;
            

Array


            const list1: number[] = [1, 2, 3];
            const list2: Array<number> = [1, 2, 3];
          

Tuple


            // Объявление кортежа
            let point: [number, number];

            // Некорректная инициализация
            point = [10, 'hello']; // Error

            // Инициализация
            point = [20, 10]; // OK

            // Обращение
            point[1]; // 10

            // Деструктуризация
            const [first, second] = point;
          

Enum


              enum Color {
                Red,
                Green,
                Blue
              }

              const c: Color = Color.Green;
            

              enum Color {
                Red = 5,
                Green = 7,
                Blue = 9
              }

              enum Color {
                Red = '#F00',
                Green = '#0F0',
                Blue = '#00F'
              }
            

Object


                const colors: object = {
                  red: '#F00',
                  green: '#0F0',
                  blue: '#00F'
                }
              

                const settings: {
                  color: string;
                  delay: number;
                  retry: boolean;
                } = {
                  color: '#F00',
                  delay: 2000,
                  retry: false
                };
              

Any


            let notSure: any = 4;
            notSure = "maybe a string instead";
            notSure = false;

            let notSure: any = 4;
            notSure.ifItExists(); // Ошибка в рантайме
            notSure.toFixed(); // Всё ок, toFixed есть в рантайме

            let prettySure: object = 4;
            prettySure.toFixed(); // toFixed нет у object
          

Void и Never


            function warnUser(): void {
              console.log('This is my warning message');
            }

            function error(message: string): never {
                throw new Error(message);
            }

            function infiniteLoop(): never {
                while (true) {}
            }
          

Приведение типов


            const someValue: any = 'this is a string';

            const strLength1: number = (<string>someValue).length;
            const strLength2: number = (someValue as string).length;
          

Функции


                function add(x: number, y: number): number {
                  return x + y;
                }
              

                const add = (x: number, y: number): number => x + y;
              

                  type ActionGuard = (req: Request) => boolean;

                  const checkIsAuthorized: ActionGuard = req => {
                    return Boolean(req.user && req.user.status === 'AUTHORIZED');
                  };
                

Интерфейсы


              function printLogin(user: { login: string }) {
                console.log(user.login);
              }

              const user = {
                login: 'savichev'
              };

              printLogin(user);
            

Интерфейсы


              interface IUser {
                login: string;
              }

              function printLogin(user: IUser) {
                console.log(user.login);
              }

              const user = {
                username: 'savichev'
              };

              printLogin(user);
            

Опциональные свойства


              interface ISquareConfig {
                color?: string;
              }

              function createSquare(config: SquareConfig) {
                const newSquare = { color: "white", area: 100 };

                if (config.color) {
                    newSquare.color = config.color;
                }

                return newSquare;
              }

              let mySquare = createSquare({ color: "black" });
            

Классы


                interface IMakesSound {
                  makeSound(): void;
                }
              


                class Python implements IMakesSound {
                  private readonly _length: number;

                  public get length() {
                    return this._length / 100;
                  }

                  constructor(length: number) {
                    this._length = length;
                  }

                  protected makeSound() {
                    console.log('Ssssss!');
                  }
                }
              

                abstract class Snake {
                  private readonly _length: number;

                  public get length() {
                    return this._length / 100;
                  }

                  constructor(length: number) {
                    this._length = length;
                  }

                  protected abstract makeSound(): void;
                }
              

                class Python extends Snake {
                  private static population = 10000;

                  public static incrementPopulation() {
                    Python.population++;
                  }

                  constructor(length: number) {
                    super(length);
                    Python.incrementPopulation();
                  }

                  protected makeSound() {
                    console.log('Ssssss!');
                  }
                }
              

              var Snake = (function() {
                function Snake(length) {
                  this._length = length;
                }

                Object.defineProperty(
                  Snake.prototype,
                  "length",
                  {
                    get: function() {
                      return this._length / 100;
                    },
                    enumerable: true,
                    configurable: true
                  }
                );

                return Snake;
              })();
            

              var Python = (function(_super) {
                __extends(Python, _super);

                function Python(length) {
                  var _this = _super.call(this, length) || this;
                  Python.incrementPopulation();
                  return _this;
                }

                Python.incrementPopulation = function() {
                  Python.population++;
                };

                Python.prototype.makeSound = function() {
                  console.log("Ssssss!");
                };

                Python.population = 10000;

                return Python;
              })(Snake);
            
Pause

Вывод типов


const n: number = 42;
const s: string = 'Hello, world!';
const a: number[] = [1, 2, 3, 4];

            
const n = 42;
const s = 'Hello, world!';
const a = [1, 2, 3, 4];
            
          

Вывод типов


function greetWith(name: string): string {
  return `Hello, ${name}!`;
}

            
function greetWith(name: string) {
  return `Hello, ${name}!`;
}
            
          

Наиболее общий тип


let shapes = [new Circle(), new Square()];

          
shapes.push(new Triangle());
          
          

Наиболее общий тип


let shapes = [new Circle(), new Square()];
            
// Argument of type 'Triangle'
// is not assignable to parameter of type 'Square | Circle'.
shapes.push(new Triangle());
          

Наиболее общий тип


let shapes = [new Circle(), new Square()];


shapes.push(new Triangle());
          

Наиболее общий тип


let shapes = [new Circle(), new Square()];


shapes.push(new Triangle());
          

Наиболее общий тип


let shapes = [new Circle(), new Square()];


shapes.push(new Triangle());
          

Наиболее общий тип


let shapes = [new Circle(), new Square()];
        
// Argument of type 'Triangle'
// is not assignable to parameter of type 'Square | Circle'.
shapes.push(new Triangle());
          

Наиболее общий тип


let shapes: Shape[] = [new Circle(), new Square()];


shapes.push(new Triangle());
          

Совместимость типов


class Human {
    name: string;
}

class Robot {
    name: string;
}
            
let human: Human = new Robot();

🤖

Тип ≡ Множество

  • Можем пересекать типы   &
  • Можем объединять типы   |
  • Можем вычитать из одного типа другой

Пересечения (Intersection Types)


interface IStudent {
  age: number;
  name: string;
}

interface IWorker {
  companyName: string;
}

type IWorkingStudent = IStudent & IWorker;

const student: IWorkingStudent = {
  age: 21
  name: 'Irina'
  companyName: 'Yandex'
};
          

Пересечения (Intersection Types)


interface IStudent {
  id: number;
  age: number;
  name: string;
}

interface IWorker {
  id: string;
  companyName: string;
}

type IWorkingStudent = IStudent & IWorker;

const student: IWorkingStudent = {
  // Type 'number' is not assignable to type 'number & string'
  id: 1,
  ...
};
            

Объединения (Union Types)


interface IStudent {
  learn(): void;
}

interface ITeacher {
  learn(): void;
  teach(): void;
}

function getPerson(): IStudent | ITeacher {
  // ...
}

const person = getPerson();
person.learn(); // Ok
person.teach(); // Compilation error
          

Объединения (Union Types)


function padLeft(value: string, padding: any) {
  if (typeof padding === 'number') {
      return Array(padding + 1).join(' ') + value;
  }

  if (typeof padding === 'string') {
      return padding + value;
  }

  throw new Error(`Expected string or number, got '${padding}'.`);
}

padLeft('Hello world', 4); // Ok, '    Hello world'
padLeft('Hello world', true); // Runtime error
          

Объединения (Union Types)


function padLeft(value: string, padding: string | number) {
  // ...
}

padLeft('Hello world', true); // Compilation error
          

Out of scope

  • Type Guards
  • Type Aliases
  • Index types
  • Mapped types
  • Lookup types
  • ...

Почитать

Посмотреть

Спасибо!

Вопросы?