├── .dockerignore
├── .eslintignore
├── .eslintrc.yml
├── .github
└── workflows
│ └── Docker.yml
├── .gitignore
├── .yamllint
├── Dockerfile
├── Makefile
├── README.md
├── bin
└── test2.sh
├── description.ru.yml
├── docker-compose.override.yml
├── docker-compose.yml
├── eslint.config.js
├── modules
├── 10-basics
│ ├── 10-hello-world
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 20-typescript-as-a-second-language
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 30-variables
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 40-named-functions
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 45-anonymous-functions
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 50-arrays
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 55-objects
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 60-enums
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 70-type-aliases
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 80-any
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 90-modules
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ └── description.ru.yml
├── 20-functions
│ ├── 10-function-as-parameter
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 20-optional-parameters-in-callbacks
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 30-void
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 35-never
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 40-unknown
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 70-destructuring
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 80-rest-spread
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 85-function-overloads
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 90-type-narrowing
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ └── description.ru.yml
├── 22-arrays
│ ├── 20-type-annotations
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 25-multi-dimensional-arrays
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 40-readonly-arrays
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 50-tuples
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ └── description.ru.yml
├── 25-types
│ ├── 10-type-as-sets
│ │ ├── Makefile
│ │ ├── assets
│ │ │ └── some_type.png
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 20-union-types
│ │ ├── Makefile
│ │ ├── assets
│ │ │ └── number_or_string.png
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 22-nullable
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 25-literal-types
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 30-intersection-types
│ │ ├── Makefile
│ │ ├── assets
│ │ │ └── one_hundred_order.png
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 40-assignability
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 50-type-hierarcy
│ │ ├── Makefile
│ │ ├── assets
│ │ │ └── hierarcy_circle.png
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 60-structural-typing
│ │ ├── Makefile
│ │ ├── assets
│ │ │ ├── object_intersection.png
│ │ │ └── structual_object.png
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 70-variability
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ └── description.ru.yml
├── 30-classes
│ ├── 10-class-fields
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 15-class-as-types
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 20-members-visibility
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 23-parameter-properties
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 30-class-extending
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 70-static-property
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 80-abstract-classes
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ └── description.ru.yml
├── 35-interfaces
│ ├── 10-interfaces-overview
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 20-interface-using
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 30-interface-implementation
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ └── description.ru.yml
├── 40-generics
│ ├── 10-generics-overview
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 20-generic-types
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 30-generic-functions
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 40-many-parameters
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 50-async-functions
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ ├── 60-generic-classes
│ │ ├── Makefile
│ │ ├── index.ts
│ │ ├── ru
│ │ │ ├── EXERCISE.md
│ │ │ ├── README.md
│ │ │ └── data.yml
│ │ └── test.ts
│ └── description.ru.yml
└── 50-objects
│ ├── 15-object
│ ├── Makefile
│ ├── index.ts
│ ├── ru
│ │ ├── EXERCISE.md
│ │ ├── README.md
│ │ └── data.yml
│ └── test.ts
│ ├── 20-index-signatures
│ ├── Makefile
│ ├── index.ts
│ ├── ru
│ │ ├── EXERCISE.md
│ │ ├── README.md
│ │ └── data.yml
│ └── test.ts
│ ├── 30-mapped-types
│ ├── Makefile
│ ├── index.ts
│ ├── ru
│ │ ├── EXERCISE.md
│ │ ├── README.md
│ │ └── data.yml
│ └── test.ts
│ ├── 35-mapping-modifiers
│ ├── Makefile
│ ├── index.ts
│ ├── ru
│ │ ├── EXERCISE.md
│ │ ├── README.md
│ │ └── data.yml
│ └── test.ts
│ ├── 45-record
│ ├── Makefile
│ ├── index.ts
│ ├── ru
│ │ ├── EXERCISE.md
│ │ ├── README.md
│ │ └── data.yml
│ └── test.ts
│ └── description.ru.yml
├── package-lock.json
├── package.json
├── spec.yml
├── tsconfig.json
└── vitest.config.js
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | .eslintrc.js
2 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | env:
2 | browser: false
3 | es2021: true
4 | node: true
5 | extends:
6 | - airbnb-base
7 | - airbnb-typescript/base
8 | - plugin:@typescript-eslint/recommended
9 | - plugin:@typescript-eslint/recommended-requiring-type-checking
10 | overrides: []
11 | parserOptions:
12 | project: './tsconfig.json'
13 | ecmaVersion: latest
14 | sourceType: module
15 | rules:
16 | no-console: 0
17 |
--------------------------------------------------------------------------------
/.github/workflows/Docker.yml:
--------------------------------------------------------------------------------
1 | name: Docker
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | main:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: hexlet-basics/exercises-action@release
17 | with:
18 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
19 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /**/logs/
2 | *.DS_Store
3 | node_modules
4 | .vscode
5 | .idea/
6 |
--------------------------------------------------------------------------------
/.yamllint:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | extends: default
4 |
5 | rules:
6 | line-length: disable
7 | empty-lines: disable
8 | trailing-spaces: disable
9 |
10 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM hexletbasics/base-image:latest
2 |
3 | ENV PATH=/exercises-typescript/bin:/exercises-typescript/node_modules/.bin:$PATH
4 | # ENV NODE_OPTIONS --max-old-space-size=4096
5 | # --experimental-vm-modules
6 | ENV CI=true
7 |
8 | WORKDIR /exercises-typescript
9 |
10 | RUN npm i -g vitest
11 | COPY package.json package-lock.json ./
12 | RUN npm ci
13 |
14 | COPY . .
15 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | -include /opt/basics/common/common.mk
2 |
3 | compose-setup: compose-build compose-install
4 |
5 | compose:
6 | docker compose up
7 |
8 | compose-build:
9 | docker compose pull
10 | docker compose build
11 |
12 | compose-install:
13 | docker compose run exercises npm install
14 |
15 | compose-update:
16 | docker compose run exercises npx ncu -u
17 |
18 | code-lint:
19 | npx eslint .
20 |
21 | # compile:
22 | # @(for i in $$(find . -type f -name Main.java); do javac $$(dirname $$i)/*.java ; done)
23 |
24 | # clean:
25 | # @$$(find . -type f -name *.class -delete)
26 |
27 | compose-bash:
28 | docker compose run exercises bash
29 |
30 | compose-test:
31 | docker compose run exercises make test
32 |
33 | compose-description-lint:
34 | docker compose run exercises make description-lint
35 |
36 | compose-schema-validate:
37 | docker compose run exercises make schema-validate
38 |
39 | ci-check:
40 | docker compose --file docker-compose.yml build
41 | docker compose --file docker-compose.yml up --abort-on-container-exit
42 |
43 | test-fast:
44 | npx vitest
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # exercises-typescript
2 |
3 | [](../../actions)
4 |
5 | ## How to contribute
6 |
7 | * Discuss the project on Telegram: https://t.me/hexletcommunity/12
8 |
9 | ### Requirements
10 |
11 | * docker
12 | * docker compose V2
13 | * make
14 |
15 | ## Develop
16 |
17 | ```bash
18 | # setup
19 | make
20 | # run
21 | make compose
22 | # check
23 | make ci-check
24 |
25 | # run tests
26 | make compose-test
27 |
28 | # run linters and validators
29 | make code-lint
30 | make compose-description-lint
31 | make compose-schema-validate
32 | ```
33 |
34 | ##
35 | [](https://hexlet.io/?utm_source=github&utm_medium=link&utm_campaign=exercises-typescript)
36 |
37 | This repository is created and maintained by the team and the community of Hexlet, an educational project. [Read more about Hexlet](https://hexlet.io/?utm_source=github&utm_medium=link&utm_campaign=exercises-typescript).
38 | ##
39 |
40 | See most active contributors on [hexlet-friends](https://friends.hexlet.io/).
41 |
--------------------------------------------------------------------------------
/bin/test2.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | FORCE_COLOR=1 vitest related --run `pwd`/test.ts
4 |
--------------------------------------------------------------------------------
/description.ru.yml:
--------------------------------------------------------------------------------
1 | ---
2 | title: |
3 | Курс TypeScript: бесплатное обучение для разработчиков
4 | header: Typescript
5 | description: |
6 | В современной разработке TypeScript не просто занял прочное место, но и во многих местах заменил собой JavaScript. Знание TypeScript стало необходимым знанием любого разработчика, который работает либо с Node.js, либо с браузером
7 | seo_description: |
8 | Прокачайте свои знания в бесплатном курсе по Typescript | Интерактивные упражнения прямо в браузере | Бесплатный курс TypeScript от CodeBasics
9 |
10 | keywords:
11 | - typescript
12 | - typoscript
13 | - type script
14 | - тайпскрипт
15 | - ts
16 | - тс
17 | - тайп скрипт
18 | - онлайн курс
19 | - бесплатный курс
20 | - программирование
21 | - code basics
22 | - online course
23 | - free course
24 | - programming
25 |
--------------------------------------------------------------------------------
/docker-compose.override.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | services:
4 | exercises:
5 | volumes:
6 | - .:/exercises-typescript
7 | environment:
8 | - CHECK_TESTS=1
9 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | services:
4 | exercises:
5 | build: .
6 | image: hexletbasics/exercises-typescript
7 | command: make check
8 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import globals from "globals";
2 | import pluginJs from "@eslint/js";
3 | import tseslint from "typescript-eslint";
4 |
5 |
6 | /** @type {import('eslint').Linter.Config[]} */
7 | export default [
8 | {files: ["**/*.{js,mjs,cjs,ts}"]},
9 | {languageOptions: { globals: globals.browser }},
10 | pluginJs.configs.recommended,
11 | ...tseslint.configs.recommended,
12 | ];
--------------------------------------------------------------------------------
/modules/10-basics/10-hello-world/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/10-hello-world/index.ts:
--------------------------------------------------------------------------------
1 | console.log('Hello, World!');
2 |
--------------------------------------------------------------------------------
/modules/10-basics/10-hello-world/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 | Наберите в редакторе код из задания символ в символ и нажмите «Проверить».
2 |
3 | ```typescript
4 | console.log('Hello, World!');
5 | ```
6 |
--------------------------------------------------------------------------------
/modules/10-basics/10-hello-world/ru/README.md:
--------------------------------------------------------------------------------
1 | По традиции начнем с написания программы 'Hello, World!'. Эта программа будет выводить на экран текст:
2 |
3 |
4 | Hello, World!
5 |
6 |
7 | Чтобы вывести что-то на экран, нужно дать компьютеру специальную команду. В языке TypeScript такая команда — `console.log()`.
8 |
9 | ## Особенности запуска
10 |
11 | Компилятор TypeScript медленнее, чем нужно для отработки практики на code-basics. Из-за этого ошибки TypeScript в этом курсе показываются только в самом редакторе — подсвечиваются красным и раскрываются при наведении.
12 |
13 | Во время запуска практики показываются только ошибки JavaScript. Это значит, что перед проверкой кода нужно убедиться, что в редакторе нет красных подчеркиваний.
14 |
15 | Если вы хотите проверить примеры у себя на компьютере для корректного поведения, убедитесь, что используете флаг `--strict`.
16 |
--------------------------------------------------------------------------------
/modules/10-basics/10-hello-world/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Привет, Мир!
3 | tips:
4 | - |
5 | [Repl для экспериментов с TypeScript](https://www.typescriptlang.org/play)
6 | - >
7 | [Инструкция по установке
8 | TypeScript](https://github.com/Hexlet/ru-instructions/blob/main/typescript.md)
9 | - |
10 | [strict в tsconfig](https://www.typescriptlang.org/tsconfig#strict)
11 | - >
12 | [Документация по флагам запуска
13 | tsc](https://www.typescriptlang.org/docs/handbook/compiler-options.html#compiler-options)
14 | - >
15 | [Статья "Как устроена система типов в
16 | TypeScript"](https://ru.hexlet.io/blog/posts/sistema-tipov-v-typescript)
17 |
--------------------------------------------------------------------------------
/modules/10-basics/10-hello-world/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, vi } from 'vitest'
2 | import * as path from 'path';
3 |
4 | test('hello world', async () => {
5 | const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
6 | await import(path.join(__dirname, 'index'));
7 | const firstArg = consoleLogSpy.mock.calls[0]?.[0];
8 | expect(firstArg).toBe('Hello, World!')
9 | });
10 |
--------------------------------------------------------------------------------
/modules/10-basics/20-typescript-as-a-second-language/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/20-typescript-as-a-second-language/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function multiply(a: number, b: number) {
3 | return a * b;
4 | }
5 | // END
6 |
7 | export default multiply;
8 |
--------------------------------------------------------------------------------
/modules/10-basics/20-typescript-as-a-second-language/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 | По образцу примера с суммой из урока напишите функцию, которая находит произведение переданных чисел:
2 |
3 | ```typescript
4 | multiply(3, 8); // 24
5 | multiply(1, 2); // 2
6 | ```
7 |
--------------------------------------------------------------------------------
/modules/10-basics/20-typescript-as-a-second-language/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: О языке
3 | tips:
4 | - |
5 | [Telegram Hexlet: JavaScript/TypeScript](https://t.me/hexletcommunity/12)
6 | - |
7 | [TypeScript бойлерплейт](https://github.com/hexlet-boilerplates/typescript-package)
8 | - |
9 | [Playground с возможностью увидеть результат транспиляции TypeScript в JavaScript](https://www.typescriptlang.org/play)
10 |
--------------------------------------------------------------------------------
/modules/10-basics/20-typescript-as-a-second-language/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import multiply from './index';
4 |
5 | test('multiply', () => {
6 | expect(multiply(1, 3)).toBe(3);
7 |
8 | expectTypeOf(multiply).returns.toMatchTypeOf();
9 | });
10 |
--------------------------------------------------------------------------------
/modules/10-basics/30-variables/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/30-variables/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | function repeat(text: string, count: number) {
4 | // BEGIN
5 | let result = '';
6 | for (let i = 0; i < count; i += 1) {
7 | result += text;
8 | }
9 |
10 | return result;
11 | // END
12 | }
13 |
14 | export default repeat;
15 |
--------------------------------------------------------------------------------
/modules/10-basics/30-variables/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Допишите тело функции `repeat()`, которая повторяет строку указанное количество раз. Функция должна возвращать полученный результат. Постарайтесь не использовать встроенные методы, для такой реализации вам понадобится цикл.
3 |
4 | ```typescript
5 | repeat('hexlet', 2); // hexlethexlet
6 | repeat('wo', 3); // wowowo
7 | ```
8 |
--------------------------------------------------------------------------------
/modules/10-basics/30-variables/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Переменные
3 | tips:
4 | - >
5 | [Метод
6 | String.repeat()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/repeat)
7 | - >
8 | [Официальная
9 | документация](https://www.typescriptlang.org/docs/handbook/2/basic-types.html)
10 |
--------------------------------------------------------------------------------
/modules/10-basics/30-variables/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import repeat from './index';
4 |
5 | test('repeat', () => {
6 | expect(repeat('wow', 3)).toBe('wowwowwow');
7 | expect(repeat('s', 2)).toBe('ss');
8 | expect(repeat('s', 0)).toBe('');
9 |
10 | expectTypeOf(repeat).returns.toMatchTypeOf();
11 | });
12 |
--------------------------------------------------------------------------------
/modules/10-basics/40-named-functions/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/40-named-functions/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function getHiddenCard(cardNumber: string, hiddenPartLength = 4): string {
3 | const visibleDigitsLine = cardNumber.slice(-4);
4 | return visibleDigitsLine.padStart(hiddenPartLength + 4, '*');
5 | }
6 | // END
7 |
8 | export default getHiddenCard;
9 |
--------------------------------------------------------------------------------
/modules/10-basics/40-named-functions/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `getHiddenCard()`. Она принимает на вход номер кредитки, который состоит из 16 цифр, в виде строки и возвращает его скрытую версию. Эта версия может использоваться на сайте для отображения. Например, если номер исходной карты был *2034399002125581*, то скрытая версия выглядит так: *\*\*\*\*5581*.
3 |
4 | Получается, функция заменяет первые 12 символов на звездочки. Количество звездочек регулируется вторым необязательным параметром. Значение по умолчанию — 4.
5 |
6 | ```typescript
7 | // Кредитка передается внутрь как строка
8 | getHiddenCard('1234567812345678', 2) // "**5678"
9 | getHiddenCard('1234567812345678', 3) // "***5678"
10 | getHiddenCard('1234567812345678') // "****5678"
11 | getHiddenCard('2034399002121100', 1) // "*1100"
12 | ```
13 |
--------------------------------------------------------------------------------
/modules/10-basics/40-named-functions/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Именованные функции
3 |
--------------------------------------------------------------------------------
/modules/10-basics/40-named-functions/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import getHiddenCard from './index';
4 |
5 | test('getHiddenCard', () => {
6 | expect(getHiddenCard('1234123412341234')).toEqual('****1234');
7 | expect(getHiddenCard('1234123412344321')).toEqual('****4321');
8 | expect(getHiddenCard('1234123412344321', 2)).toEqual('**4321');
9 | expect(getHiddenCard('1234123412341234', 12)).toEqual('************1234');
10 |
11 | expectTypeOf(getHiddenCard).returns.toMatchTypeOf();
12 | });
13 |
--------------------------------------------------------------------------------
/modules/10-basics/45-anonymous-functions/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/45-anonymous-functions/index.ts:
--------------------------------------------------------------------------------
1 | const numbers = [1, 3, 8, 9, 100, 23, 55, 34];
2 |
3 | // BEGIN
4 | const getEvenNumbers = () => numbers.filter((num) => num % 2 === 0);
5 | // END
6 |
7 | export default getEvenNumbers;
8 |
--------------------------------------------------------------------------------
/modules/10-basics/45-anonymous-functions/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Напишите функцию, которая возвращает массив четных чисел из массива `numbers`.
3 |
--------------------------------------------------------------------------------
/modules/10-basics/45-anonymous-functions/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке мы познакомимся с анонимными функциями. Вместе со стрелочными они обычно используются в том же месте, где и определяются. Благодаря этому TypeScript может вывести типы их параметров.
3 |
4 | Чтобы определить анонимные функции, указание типов опускают:
5 |
6 | ```typescript
7 | const fruits = ['banana', 'mango', 'apple'];
8 | const upperFruits = fruits.map((name) => name.toUpperCase());
9 | // ['BANANA', 'MANGO', 'APPLE']
10 | ```
11 |
12 | Этот процесс называется **контекстная типизация (contextual typing)**, так как контекст определения функции позволяет вывести типы входных параметров. В итоге код выглядит идентично коду на JavaScript.
13 |
14 | Если функция определяется вне контекста, то к ней применяются те же правила, что и к именованным функциям. То есть типы параметров должны быть заданы во время определения:
15 |
16 | ```typescript
17 | const toUpper = (name: string): string => name.toUpperCase();
18 | const upperFruits = fruits.map(toUpper);
19 | ```
20 |
21 | ## Выводы
22 |
23 | В этом уроке мы рассмотрели способ определения анонимных функций и использование их в различных контекстах. Анонимные функции могут сделать код более читаемым и понятным.
24 |
--------------------------------------------------------------------------------
/modules/10-basics/45-anonymous-functions/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Анонимные функции
3 |
--------------------------------------------------------------------------------
/modules/10-basics/45-anonymous-functions/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import getEvenNumbers from './index';
4 |
5 | test('function', () => {
6 | expect(getEvenNumbers()).toEqual([8, 100, 34]);
7 |
8 | expectTypeOf(getEvenNumbers).returns.toMatchTypeOf();
9 | });
10 |
--------------------------------------------------------------------------------
/modules/10-basics/50-arrays/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/50-arrays/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function filterAnagrams(anagram: string, anagrams: string[]): string[] {
3 | const standard = anagram.split('').sort().join('');
4 | return anagrams.filter((item) => item.split('').sort().join('') === standard);
5 | }
6 | // END
7 |
8 | export default filterAnagrams;
9 |
--------------------------------------------------------------------------------
/modules/10-basics/50-arrays/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Анаграммы — это слова, которые состоят из одинаковых букв. Например:
3 |
4 | спаниель — апельсин
5 | карат — карта — катар
6 | топор — ропот — отпор
7 |
8 | Реализуйте функцию `filterAnagrams()`, которая находит все анаграммы слова. Функция принимает исходное слово и список для проверки — массив. А возвращает функция массив всех анаграмм. Если в списке нет анаграммы, то возвращается пустой массив:
9 |
10 | ```typescript
11 | filterAnagrams('abba', ['aabb', 'abcd', 'bbaa', 'dada']);
12 | // ['aabb', 'bbaa']
13 |
14 | filterAnagrams('racer', ['crazer', 'carer', 'racar', 'caers', 'racer']);
15 | // ['carer', 'racer']
16 |
17 | filterAnagrams('laser', ['lazing', 'lazy', 'lacer']);
18 | // []
19 | ```
20 |
--------------------------------------------------------------------------------
/modules/10-basics/50-arrays/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке мы поговорим про массивы. TypeScript умеет выводить их тип, как и в случае с примитивными типами данных:
3 |
4 | ```typescript
5 | const fruits = ['banana', 'mango', 'apple'];
6 | // Все работает
7 | const upperFruits = fruits.map((name) => name.toUpperCase());
8 |
9 | // А так уже нет
10 | // Property 'key' does not exist on type 'string'.
11 | const upperFruits = fruits.map((name) => name.key);
12 | ```
13 |
14 | **Массив** — это составной тип данных, который представляет собой контейнер для другого типа. Например, тип «массив чисел» или «массив строк» — это контейнеры, содержащие в себе строки или числа.
15 |
16 | Чтобы обозначить такой тип, используются квадратные скобки: `number[]`, `string[]`.
17 |
18 | Определение массива выше можно было бы записать так:
19 |
20 | ```typescript
21 | const fruits: string[] = ['banana', 'mango', 'apple'];
22 | ```
23 |
24 | Так же описываются типы в определении функций:
25 |
26 | ```typescript
27 | function toUpperArray(items: string[]): string[] {
28 | return items.map((s) => s.toUpperCase());
29 | }
30 | ```
31 |
32 | В заключении можно сказать, что массивы могут быть полезными инструментами при работе с данными.
33 |
--------------------------------------------------------------------------------
/modules/10-basics/50-arrays/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Массивы
3 | tips:
4 | - >
5 | [Официальная
6 | документация](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#arrays)
7 |
--------------------------------------------------------------------------------
/modules/10-basics/50-arrays/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import filterAnagrams from './index';
4 |
5 | test('function', () => {
6 | expect(filterAnagrams('abba', ['aabb', 'abcd', 'bbaa', 'dada'])).toEqual(['aabb', 'bbaa']);
7 |
8 | expect(filterAnagrams('racer', ['crazer', 'carer', 'racar', 'caers', 'racer'])).toEqual(['carer', 'racer']);
9 |
10 | expect(filterAnagrams('laser', ['lazing', 'lazy', 'lacer'])).toEqual([]);
11 |
12 | expectTypeOf(filterAnagrams).returns.toMatchTypeOf();
13 | });
14 |
--------------------------------------------------------------------------------
/modules/10-basics/55-objects/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/55-objects/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function isComplete(course: { name: string, lessons: string[] }): boolean {
3 | return course.lessons.length >= 4;
4 | }
5 | // END
6 |
7 | export default isComplete;
8 |
--------------------------------------------------------------------------------
/modules/10-basics/55-objects/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `isComplete()`, которая принимает на вход курс и определяет, завершен ли он. Завершенным считается курс, в который добавлено четыре или более уроков:
3 |
4 | ```typescript
5 | // Определите тип исходя из структуры объекта
6 | const course = {
7 | name: 'Java',
8 | lessons: ['variables', 'functions', 'conditions'],
9 | };
10 | isComplete(course); // false
11 | ```
12 |
--------------------------------------------------------------------------------
/modules/10-basics/55-objects/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке разберем типы объекта. Они состоят из типов всех входящих в него свойств.
3 |
4 | Выводятся типы автоматически:
5 |
6 | ```typescript
7 | // Тип: { firstName: string, pointsCount: number }
8 | const user = {
9 | firstName: 'Mike',
10 | pointsCount: 1000,
11 | };
12 |
13 | // Поменять тип свойств нельзя
14 | // Type 'number' is not assignable to type 'string'.
15 | user.firstName = 7;
16 | ```
17 |
18 | TypeScript не позволяет обращаться к несуществующим свойствам. Это значит, что структура любого объекта должна быть задана при его инициализации:
19 |
20 | ```typescript
21 | // Property 'age' does not exist on type '{ firstName: string, pointsCount: number; }'.
22 | user.age = 100;
23 | ```
24 |
25 | Чтобы принять такой объект в функцию как параметр, нужно указать его структуру в описании функции:
26 |
27 | ```typescript
28 | // Свойства в описании типа разделяются через запятую (,)
29 | function doSomething(user: { firstName: string, pointsCount: number }) {
30 | // ...
31 | }
32 | ```
33 |
34 | Теперь внутрь можно передавать любой объект, который совпадает по свойствам:
35 |
36 | ```typescript
37 | doSomething({ firstName: 'Alice', pointsCount: 2000 });
38 | doSomething({ firstName: 'Bob', pointsCount: 1800 });
39 |
40 | // Так нельзя
41 | doSomething({ firstName: 'Bob' });
42 | // И так тоже
43 | doSomething({ firstName: 'Bob', pointsCount: 1800, key: 'another' });
44 | ```
45 |
46 | Как и в случае примитивных типов данных, ни null, ни undefined по умолчанию не разрешены. Чтобы изменить это поведение, нужно добавить опциональность:
47 |
48 | ```typescript
49 | // firstName может быть undefined
50 | // pointsCount может быть null
51 | function doSomething(user: { firstName?: string, pointsCount: number | null }) {
52 | // ...
53 | }
54 | ```
55 |
56 | Объекты могут быть полезными инструментами при разработке программного обеспечения.
57 |
--------------------------------------------------------------------------------
/modules/10-basics/55-objects/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Объектные типы (Object Types)
3 |
--------------------------------------------------------------------------------
/modules/10-basics/55-objects/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import isComplete from './index';
4 |
5 | test('function', () => {
6 | const course1 = {
7 | name: 'Java',
8 | lessons: ['variables', 'functions', 'conditions'],
9 | };
10 | expect(isComplete(course1)).toBe(false);
11 |
12 | const course2 = {
13 | name: 'Java',
14 | lessons: ['variables', 'functions', 'conditions', 'loops'],
15 | };
16 | expect(isComplete(course2)).toBe(true);
17 |
18 | const course3 = {
19 | name: 'Java',
20 | lessons: ['variables', 'functions', 'conditions', 'loops', 'cringe'],
21 | };
22 | expect(isComplete(course3)).toBe(true);
23 |
24 | expectTypeOf(isComplete).returns.toMatchTypeOf();
25 | expectTypeOf(isComplete).parameter(0).toMatchTypeOf<{ name: string, lessons: string[] }>();
26 | });
27 |
--------------------------------------------------------------------------------
/modules/10-basics/60-enums/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/60-enums/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | enum ModalStatus {
3 | Opened,
4 | Closed,
5 | }
6 |
7 | function buildModal(text: string, status: ModalStatus): { text: string; status: ModalStatus } {
8 | return {
9 | text,
10 | status,
11 | };
12 | }
13 | // END
14 |
15 | export { ModalStatus };
16 | export default buildModal;
17 |
--------------------------------------------------------------------------------
/modules/10-basics/60-enums/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | 1. Реализуйте перечисление `ModalStatus` с двумя значениями: Opened и Closed
3 | 2. Реализуйте функцию `buildModal()`. Он возвращает объект, который описывает модальное окно. Параметры функции:
4 |
5 | * Текст, который должен быть внутри окна после инициализации
6 | * Статус, с которым надо создать объект окна
7 |
8 | Функция возвращает объект с двумя полями: `text` (здесь хранится переданный текст) и `status` (здесь хранится переданный статус)
9 |
10 | ```typescript
11 | const modal = buildModal('hexlet forever', ModalStatus.Opened);
12 | // { text: 'hexlet forever', status: ModalStatus.Opened }
13 | ```
14 |
--------------------------------------------------------------------------------
/modules/10-basics/60-enums/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке мы познакомимся с перечислением. Такая конструкция языка позволяет создать набор имен, а затем обращаться к ним.
3 |
4 | ## Использование перечислений
5 |
6 | Перечисления используют вместо строк для постоянных значений:
7 |
8 | ```typescript
9 | enum OrderStatus {
10 | Created,
11 | Paid,
12 | Shipped,
13 | Delivered,
14 | }
15 |
16 | const order = {
17 | items: 3,
18 | status: OrderStatus.Created,
19 | };
20 | ```
21 |
22 | Самый распространенный пример использования перечислений — хранение разных статусов. Но есть и другие случаи. Например, с их помощью хранят различные справочные данные и избавляются от магических значений:
23 |
24 | * Направления движения
25 | * Стороны света
26 | * Дни недели
27 | * Месяцы
28 |
29 | ```typescript
30 | enum CardinalDirection {
31 | North,
32 | South,
33 | East,
34 | West,
35 | }
36 |
37 | const direction = CardinalDirection.North;
38 | ```
39 |
40 | Перечисление — это и значение, и тип. Его можно указывать как тип в параметрах функции:
41 |
42 | ```typescript
43 | setStatus(status: OrderStatus)
44 | ```
45 |
46 | Также перечисления после компиляции превращаются в JavaScript-объект, в котором каждому значению соответствует свойство. У этого свойства есть тип `number` и начинается он с `0`:
47 |
48 | ```typescript
49 | const status = OrderStatus.Created;
50 | console.log(status); // 0
51 | ```
52 |
53 | Это позволяет в дальнейшем удобно использовать стандартные методы — например, `Object.keys` и `Object.values`:
54 |
55 | ```typescript
56 | const statuses = Object.keys(OrderStatus);
57 | console.log(statuses); // ['0', '1', '2', '3', 'Created', 'Paid', 'Shipped', 'Delivered']
58 | ```
59 |
60 | Среди ключей мы видим числа `'0', '1', '2', '3'`. Компилятор создает такие числовые ключи автоматически, а созданный объект выглядит так:
61 |
62 | ```typescript
63 | console.log(OrderStatus); // =>
64 | // {
65 | // '0': 'Created',
66 | // '1': 'Paid',
67 | // '2': 'Shipped',
68 | // '3': 'Delivered',
69 | // 'Created': 0,
70 | // 'Paid': 1,
71 | // 'Shipped': 2,
72 | // 'Delivered': 3
73 | // }
74 | ```
75 |
76 | Но можно избавиться от создания дополнительных ключей, если указать строковые значения:
77 |
78 | ```typescript
79 | enum OrderStatus {
80 | Created = '0',
81 | Paid = '1',
82 | Shipped = '2',
83 | Delivered = '3',
84 | }
85 |
86 | const statuses = Object.keys(OrderStatus);
87 | console.log(statuses); // ['Created', 'Paid', 'Shipped', 'Delivered']
88 |
89 | console.log(OrderStatus); // =>
90 | // {
91 | // 'Created': '0',
92 | // 'Paid': '1',
93 | // 'Shipped': '2',
94 | // 'Delivered': '3'
95 | // }
96 | ```
97 |
98 | ## Выводы
99 |
100 | В этом уроке мы узнали, для чего и как используется перечисление. Также мы разобрали, что его можно указывать как тип в параметрах функции.
101 |
--------------------------------------------------------------------------------
/modules/10-basics/60-enums/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Перечисления (Enums)
3 | tips:
4 | - >
5 | [Официальная
6 | документация](https://www.typescriptlang.org/docs/handbook/enums.html)
7 |
--------------------------------------------------------------------------------
/modules/10-basics/60-enums/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import buildModal, { ModalStatus } from './index';
4 |
5 | test('function', () => {
6 | expect(buildModal('hexlet', ModalStatus.Opened))
7 | .toEqual({ text: 'hexlet', status: ModalStatus.Opened });
8 |
9 | expect(buildModal('code-basics', ModalStatus.Closed))
10 | .toEqual({ text: 'code-basics', status: ModalStatus.Closed });
11 |
12 | expectTypeOf(buildModal).returns.toMatchTypeOf<{ text: string, status: ModalStatus }>();
13 | });
14 |
--------------------------------------------------------------------------------
/modules/10-basics/70-type-aliases/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/70-type-aliases/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | type User = {
3 | name: string,
4 | age: number,
5 | };
6 |
7 | function getOlderUser(user1: User, user2: User): User | null {
8 | if (user1.age > user2.age) {
9 | return user1;
10 | }
11 | if (user2.age > user1.age) {
12 | return user2;
13 | }
14 |
15 | return null;
16 | }
17 | // END
18 |
19 | export type { User };
20 | export default getOlderUser;
21 |
--------------------------------------------------------------------------------
/modules/10-basics/70-type-aliases/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `getOlderUser()`, которая принимает на вход двух пользователей и возвращает того, который старше. Если пользователи являются ровесниками, то возвращается `null`:
3 |
4 | ```typescript
5 | const user1 = { name: 'Petr', age: 8 };
6 | ```
7 |
8 | Определите для пользователя псевдоним, чтобы не дублировать определение его типа в параметрах функции.
9 |
--------------------------------------------------------------------------------
/modules/10-basics/70-type-aliases/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Псевдонимы типов (Type Aliases)
3 |
--------------------------------------------------------------------------------
/modules/10-basics/70-type-aliases/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import getOlderUser, { User } from './index';
4 |
5 | test('function', () => {
6 | const user1 = {
7 | name: 'sem',
8 | age: 3,
9 | };
10 |
11 | const user2 = {
12 | name: 'inna',
13 | age: 5,
14 | };
15 |
16 | const user3 = {
17 | name: 'mika',
18 | age: 5,
19 | };
20 |
21 | expect(getOlderUser(user1, user2)).toEqual(user2);
22 | expect(getOlderUser(user2, user1)).toEqual(user2);
23 |
24 | expect(getOlderUser(user2, user3)).toBeNull();
25 |
26 | expectTypeOf(getOlderUser).returns.toMatchTypeOf();
27 | });
28 |
--------------------------------------------------------------------------------
/modules/10-basics/80-any/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/80-any/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | /* eslint-disable @typescript-eslint/no-explicit-any */
5 |
6 | // BEGIN
7 | function getParams(query: string) {
8 | const parts = query.split('&');
9 | const init: any = {};
10 | const result = parts.reduce((acc, part) => {
11 | const [key, value] = part.split('=');
12 | acc[key] = value;
13 | return acc;
14 | }, init);
15 |
16 | return result;
17 | }
18 | // END
19 |
20 | export default getParams;
21 |
--------------------------------------------------------------------------------
/modules/10-basics/80-any/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `getParams()`, которая принимает на вход строку запроса (query string) и возвращает параметры в виде объекта:
3 |
4 | ```typescript
5 | getParams('per=10&page=5');
6 | // { per: '10', page: '5' }
7 | getParams('name=hexlet&count=3&order=asc');
8 | // { name: 'hexlet', count: '3', order: 'asc' }
9 | ```
10 |
11 | Эту задачу лучше всего решать через метод `reduce()`.
12 |
--------------------------------------------------------------------------------
/modules/10-basics/80-any/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Тип Any
3 | tips:
4 | - >
5 | [Официальная документация про
6 | any](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any)
7 |
--------------------------------------------------------------------------------
/modules/10-basics/80-any/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import getParams from './index';
3 |
4 | test('function', () => {
5 | expect(getParams('per=10&page=5')).toEqual({ per: '10', page: '5' });
6 | expect(getParams('name=hexlet&count=3&order=asc')).toEqual({ name: 'hexlet', count: '3', order: 'asc' });
7 | });
8 |
--------------------------------------------------------------------------------
/modules/10-basics/90-modules/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/10-basics/90-modules/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-namespace */
2 | // BEGIN
3 | namespace Company {
4 | export function isEmployeeEmail(email: string, domain: string): boolean {
5 | return email.endsWith(`@${domain}`);
6 | }
7 | }
8 | // END
9 |
10 | type User = {
11 | email: string
12 | };
13 |
14 | function authorize(user: User | null): boolean {
15 | const companyDomain = 'hexlet.io';
16 |
17 | const email = user?.email ?? '';
18 |
19 | return Company.isEmployeeEmail(email, companyDomain);
20 | }
21 |
22 | export default authorize;
23 |
--------------------------------------------------------------------------------
/modules/10-basics/90-modules/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте namespace `Company`, в котором экспортируется функция `isEmployeeEmail()`. Функция принимает почту и домен. Если емейл пользователя содержит указанный домен, то функция возвращает `true`:
3 |
4 | ```typescript
5 | Company.isEmployeeEmail('tirion@hexlet.io', 'hexlet.io');
6 | // true
7 |
8 | Company.isEmployeeEmail('user@example.com', 'hexlet.io');
9 | // false
10 | ```
11 |
--------------------------------------------------------------------------------
/modules/10-basics/90-modules/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке рассмотрим систему модулей TypeScript, которая была создана до стандартизации ESM-модулей.
3 |
4 | По умолчанию эта система модулей совместима с Node.js-модулями. Она использует идентичный алгоритм определения импортов и экспортов, при этом синтаксически похожа на ESM. В нем мы используем ключевые слова `import`/`export` для импортирования в текущий модуль и экспорта из него, при этом остается валидным использование CommonJS модулей.
5 |
6 | Для примера создадим два файла. Из одного будем экспортировать функцию, а в другом импортировать ее:
7 |
8 | ```typescript
9 | // @file helloWorld.ts
10 | export default function helloWorld() {
11 | console.log("Hello, world!");
12 | }
13 |
14 | // @file main.ts
15 | import helloWorld from './helloWorld';
16 | ```
17 |
18 | В системе модулей TypeScript также поддерживается именованный экспорт/импорт и импорт всего экспортированного через `import * as`:
19 |
20 | ```typescript
21 | // @file helloWorld
22 | export function helloWorld() {}
23 | export function helloWorldAgain() {}
24 |
25 | // @file main
26 | import { helloWorld, helloWorldAgain } from './helloWorld';
27 |
28 | // @file next
29 | import * as hw from './helloWorld';
30 | hw.helloWorld();
31 | ```
32 |
33 | ## Импорт типов
34 |
35 | TypeScript также как и JavaScript использует npm-пакеты для зависимостей. При этом важно помнить, что в некоторых пакетах типов может и не быть. Например, в пакете `lodash` нет типов, поэтому при импорте его функций TypeScript будет ругаться на отсутствие типов.
36 |
37 | Для решения этой проблемы нужно установить пакет `@types/lodash`, который содержит типы для `lodash`.
38 |
39 | Но верна и обратная ситуация, например, пакет `type-fest` содержит только типы. Чтобы создать такой пакет самому и случайно не импортировать реализацию функций, нужно использовать специальный синтаксис:
40 |
41 | ```typescript
42 | // @file user.types.ts
43 | export type User = { name: string };
44 |
45 | // @file main.ts
46 | import type { User } from './user.types';
47 | ```
48 |
49 | ## Пространство имен (Namespace)
50 |
51 | Модули решают проблему разнородных сущностей и коллизий с помощью разнесения кода по нескольким файлам. Чтобы коллизии не возникали в рамках одного файла, используют механизм пространств имен `namespace`:
52 |
53 | ```typescript
54 | namespace Hello {
55 | export function helloWorld() {
56 | console.log("Hello, world!");
57 | }
58 | }
59 |
60 | const helloWorld = Hello.helloWorld();
61 | ```
62 |
63 | Больше всего этот механизм полезен авторам библиотек и оберток с типами `@types/`. Они заключают все интерфейсы в один `namespace`, совпадающий с названием библиотеки. Это гарантирует отсутствие коллизий имен и упрощает пользователям слияние интерфейсов. О последнем пункте поговорим в одном из следующих уроков курса.
64 |
--------------------------------------------------------------------------------
/modules/10-basics/90-modules/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Система модулей
3 | tips:
4 | - >
5 | [Официальная документация про
6 | модули](https://www.typescriptlang.org/docs/handbook/modules.html)
7 | - >
8 | [Как происходит Module
9 | Resolution](https://www.typescriptlang.org/docs/handbook/module-resolution.html#how-nodejs-resolves-modules)
10 | - >
11 | [О модулях в JavaScript на
12 | Hexlet](https://ru.hexlet.io/courses/programming-basics/lessons/modules/theory_unit)
13 | - >
14 | [Официальная документация про
15 | Namespaces](https://www.typescriptlang.org/docs/handbook/namespaces.html)
16 | - >
17 | [Метод
18 | includes()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes)
19 |
--------------------------------------------------------------------------------
/modules/10-basics/90-modules/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import authorize from './index';
3 |
4 | test('function', () => {
5 | expect(authorize({ email: 'test@hexlet.io' })).toBe(true);
6 | expect(authorize({ email: 'hexlet.io@example.com' })).toBe(false);
7 | expect(authorize({ email: 'test@example.com' })).toBe(false);
8 | expect(authorize(null)).toBe(false);
9 | });
10 |
--------------------------------------------------------------------------------
/modules/10-basics/description.ru.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Основы TypeScript
3 | description: |
4 | TypeScript – один из самых популярных языков программирования в мире. Он используется для создания интерактивных веб-страниц, мобильных приложений, в серверной разработке.
5 | Изучать TS мы будем с нуля, с самых азов. Первый модуль – плацдарм для написания осмысленных программ. В нем мы разберем, как написать свой первый код на TS. Расскажем, что такое комментарии и зачем они нужны. На примере проверки ваших решений рассмотрим, что такое тестирование и как читать вывод тестов.
6 |
--------------------------------------------------------------------------------
/modules/20-functions/10-function-as-parameter/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/20-functions/10-function-as-parameter/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function filter(numbers: number[], callback: (n: number) => boolean): number[] {
3 | const result: number[] = [];
4 | numbers.forEach((n) => {
5 | if (callback(n)) {
6 | result.push(n);
7 | }
8 | });
9 | return result;
10 | }
11 | // END
12 |
13 | export default filter;
14 |
--------------------------------------------------------------------------------
/modules/20-functions/10-function-as-parameter/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `filter()`, которая принимает на вход массив чисел и предикат. Последний будет использоваться для проверки каждого числа на соответствие требованиям:
3 |
4 | ```typescript
5 | const numbers = [1, -5, 2, 3, 4, 133];
6 | filter(numbers, (n) => n > 3); // [4, 133]
7 | filter(numbers, (n) => n % 2 == 0); // [2, 4]
8 | ```
9 |
10 | Параметры функции:
11 |
12 | 1. Массив чисел
13 | 2. Анонимная функция, которая принимает на вход число и возвращает логическое значение
14 |
--------------------------------------------------------------------------------
/modules/20-functions/10-function-as-parameter/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В TypeScript используется несколько способов типизировать функции, которые передаются как параметры. В этом уроке мы научимся работать с ними.
3 |
4 | ## Как типизировать функции, которые передаются как параметры
5 |
6 | Самый простой способ типизировать функции как параметры — использовать тип `Function`. Он описывает функцию JavaScript со всеми ее особенностями, включая свойства `bind`, `call` и `apply`.
7 |
8 | Опишем входной параметр `callback` функции `process`:
9 |
10 | ```typescript
11 | function process(callback: Function) {
12 | const value = callback();
13 | // ...
14 | }
15 | ```
16 |
17 | `Function` отключает проверку типов для вызываемой функции. В итоге количество и тип входных аргументов не проверяются, а результатом работы такой функции будет `any`. Это может привести к логическим ошибкам и неожиданному поведению:
18 |
19 | ```typescript
20 | process(Math.round); // ?
21 | ```
22 |
23 | Данный пример сработает, хотя поведение вряд ли будет ожидаемым, так как `Math.round` вызовется без аргументов и вернет `NaN`. Поэтому мы рекомендуем избегать использования `Function`.
24 |
25 | Другой способ описывать функции — использовать стрелочную функцию с указанием входных и выходных типов:
26 |
27 | ```typescript
28 | function process(callback: () => string) {
29 | // value имеет тип string
30 | const value = callback();
31 | // ...
32 | }
33 |
34 | process(Math.round);
35 | // Argument of type '(x: number) => number' is not
36 | // assignable to parameter of type '() => string'.
37 | ```
38 |
39 | Определение стрелочной функции похоже на настоящую, но тут важно не перепутать. Здесь мы видим именно описание типа, а не определение функции.
40 |
41 | Рассмотрим еще несколько примеров для закрепления:
42 |
43 | ```typescript
44 | function process(callback: () => number)
45 | function process(callback: () => string[])
46 | function process(callback: () => { firstName: string; })
47 | ```
48 |
49 | Параметры синтаксически указываются так же, как и для стрелочных функций:
50 |
51 | ```typescript
52 | function process(callback: (n: number) => string) {
53 | const value = callback(10);
54 | // ...
55 | }
56 | ```
57 |
58 | Здесь мы определили тип `callback` функцией с параметром `n` с типом `number` и возвращаемое значение `string`.
59 |
60 | Если определение функции встречается часто, то для него можно создать псевдоним:
61 |
62 | ```typescript
63 | type myFunction = (n: number) => string;
64 |
65 | function process(callback: myFunction) {
66 | const value = callback(10);
67 | // ...
68 | }
69 | ```
70 |
71 | Такая запись упрощает чтение кода и позволяет избежать дублирования.
72 |
--------------------------------------------------------------------------------
/modules/20-functions/10-function-as-parameter/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Функции как параметры
3 | tips:
4 | - >
5 | [О функциях в The TypeScript
6 | Handbook](https://habr.com/ru/company/macloud/blog/561470/)
7 |
--------------------------------------------------------------------------------
/modules/20-functions/10-function-as-parameter/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import filter from './index';
3 |
4 | test('function', () => {
5 | const result = filter([], (n) => n > 3);
6 | expect(result).toEqual([]);
7 |
8 | const result2 = filter([3, 2, 8, 9], (n) => n > 3);
9 | expect(result2).toEqual([8, 9]);
10 |
11 | const result3 = filter([3, 2, 8, 9], (n) => n < 8);
12 | expect(result3).toEqual([3, 2]);
13 | });
14 |
--------------------------------------------------------------------------------
/modules/20-functions/20-optional-parameters-in-callbacks/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/20-functions/20-optional-parameters-in-callbacks/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | // BEGIN
4 | function map(numbers: number[], callback: (n: number, index: number) => number): number[] {
5 | const result: number[] = [];
6 | numbers.forEach((n, index) => result.push(callback(n, index)));
7 | return result;
8 | }
9 | // END
10 |
11 | export default map;
12 |
--------------------------------------------------------------------------------
/modules/20-functions/20-optional-parameters-in-callbacks/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `map()`. Она должна принимать на вход массив чисел и колбек, который будет использоваться для преобразования каждого числа из массива в другое число. Функция возвращает массив с новыми числами:
3 |
4 | ```typescript
5 | map([3, 9], (n) => n - 3);
6 | // [0, 6]
7 |
8 | map([8, 9], (n) => n + 8);
9 | // [16, 17]
10 | ```
11 |
12 | Параметры функции:
13 |
14 | 1. Массив чисел
15 | 2. Анонимная функция, которая принимает на вход число и возвращает число. У этой функции есть необязательный параметр — индекс элемента в массиве
16 |
17 | ```typescript
18 | map([8, 9], (n, index) => index + n);
19 | // [8, 10]
20 | ```
21 |
--------------------------------------------------------------------------------
/modules/20-functions/20-optional-parameters-in-callbacks/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Опциональные параметры в функциях
3 |
--------------------------------------------------------------------------------
/modules/20-functions/20-optional-parameters-in-callbacks/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import map from './index';
3 |
4 | test('map', () => {
5 | const result = map([], (n: number) => n + 3);
6 | expect(result).toEqual([]);
7 |
8 | const result2 = map([3, 9], (n: number) => n - 3);
9 | expect(result2).toEqual([0, 6]);
10 |
11 | const result3 = map([8, 9], (n: number) => n + 8);
12 | expect(result3).toEqual([16, 17]);
13 |
14 | const result4 = map([10, 10, 10], (n: number, index) => n + index);
15 | expect(result4).toEqual([10, 11, 12]);
16 | });
17 |
--------------------------------------------------------------------------------
/modules/20-functions/30-void/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/20-functions/30-void/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 |
3 | function forEach(numbers: number[], callback: (n: number, index: number) => void): void {
4 | numbers.forEach((n, index) => callback(n, index));
5 | }
6 | // END
7 |
8 | export default forEach;
9 |
--------------------------------------------------------------------------------
/modules/20-functions/30-void/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Попробуйте самостоятельно определить функцию `forEach()` для чисел:
3 |
4 | ```typescript
5 | forEach([1, 2, 3], (n) => console.log(n));
6 | // 1
7 | // 2
8 | // 3
9 |
10 | const result = [];
11 | forEach([1, 2, 3], (n) => result.push(n));
12 | // [1, 2, 3]
13 | ```
14 |
15 | Параметры функции:
16 |
17 | 1. Массив чисел
18 | 2. Анонимная функция, которая принимает на вход число и возвращает `void`. У этой функции есть необязательный параметр — индекс элемента в массиве
19 |
20 | ```typescript
21 | forEach([8, 9], (n, index) => console.log(index + n));
22 | // 8
23 | // 10
24 | ```
25 |
--------------------------------------------------------------------------------
/modules/20-functions/30-void/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке мы рассмотрим тип `void`. Он указывается как возврат для функций, которые ничего не возвращают.
3 |
4 | ## Использование типа Void
5 |
6 | `void` автоматически выводится, когда внутри функции нет инструкции `return` или она пустая:
7 |
8 | ```typescript
9 | function noop() {
10 | // внутри пусто
11 | }
12 | ```
13 |
14 | В JavaScript подобные функции возвращают `undefined`, но в TypeScript `void` и `undefined` — это разные вещи. Они различаются по контекстной типизации. А происходит это из-за особенностей работы самого JavaScript. Самый яркий пример — метод `forEach()`.
15 |
16 | Метод `forEach()` не использует данные, возвращаемые переданным колбеком, который вызывается внутри. Возможно, логично было бы определить возврат как `undefined`, но посмотрим на пример:
17 |
18 | ```typescript
19 | const numbers = [1, 2, 3];
20 | const result = [];
21 |
22 | numbers.forEach((n) => result.push(n));
23 | ```
24 |
25 | Метод `push()` возвращает новую длину массива. Если бы `forEach()` требовал от колбека возврат `undefined`, то такой код привел бы к ошибке компиляции. Его пришлось бы переписать, например, так:
26 |
27 | ```typescript
28 | // Теперь колбек ничего не возвращает,
29 | // соответственно, результат вызова undefined
30 | numbers.forEach((n) => {
31 | result.push(n);
32 | });
33 | ```
34 |
35 | Чтобы не писать такой код, и был введен `void`. Он позволяет возвращать любые данные, но делает так, что их использование бессмысленно.
36 |
37 | Мы можем определить тип функции, который возвращает `void`, и использовать его для типизации переменной:
38 |
39 | ```typescript
40 | type VoidFunc = () => void;
41 |
42 | // Тип функции определяется через контекст
43 | // присваивания ее переменной с типом VoidFunc
44 | const f: VoidFunc = () => true;
45 |
46 | const v = f();
47 | ```
48 |
49 | Хотя `f()` возвращает `true`, переменная `v` имеет тип `void`. Это означает, что мы не можем использовать ее для чего-либо, кроме как для присваивания другой переменной с типом `void`.
50 |
51 | Существует единственная ситуация, когда указание `void` явно запрещает возврат из функции. Это определение функции вне контекста использования, когда ее тип указывается явно:
52 |
53 | ```typescript
54 | function foo(): void {
55 | return true; // Error!
56 | }
57 |
58 | const bar = function(): void {
59 | return true; // Error!
60 | };
61 | ```
62 |
63 | В этом случае возврат любого значения приведет к ошибке компиляции.
64 |
65 | Также `void` является оператором JavaScript, поэтому важно не запутаться с ним. Он вычисляет выражение, которое следует за ним и возвращает `undefined`:
66 |
67 | ```typescript
68 | void 10 === undefined // true
69 | ```
70 |
--------------------------------------------------------------------------------
/modules/20-functions/30-void/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Тип Void
3 | tips:
4 | - >
5 | [Официальная
6 | документация](https://www.typescriptlang.org/docs/handbook/basic-types.html#void)
7 | - >
8 | [void в TypeScript и
9 | JavaScript](https://habr.com/ru/company/ruvds/blog/468229/)
10 |
--------------------------------------------------------------------------------
/modules/20-functions/30-void/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import forEach from './index';
3 |
4 | test('forEach', () => {
5 | const result: number[] = [];
6 | forEach([], (n: number) => result.push(n));
7 | expect(result).toEqual([]);
8 |
9 | const result2: number[] = [];
10 | forEach([3, 9], (n: number) => result2.push(n + 1));
11 | expect(result2).toEqual([4, 10]);
12 |
13 | const result3: number[] = [];
14 | forEach([8, 9], (n: number, i) => result3.push(n + i));
15 | expect(result3).toEqual([8, 10]);
16 | });
17 |
--------------------------------------------------------------------------------
/modules/20-functions/35-never/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/20-functions/35-never/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function fail(): never {
3 | throw new Error('wow');
4 | }
5 | // END
6 |
7 | export default fail;
8 |
--------------------------------------------------------------------------------
/modules/20-functions/35-never/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `fail()`, которая выбрасывает любое исключение. Пропишете ее возвращаемый тип явно.
3 |
--------------------------------------------------------------------------------
/modules/20-functions/35-never/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке познакомимся с типом `never`.
3 |
4 | ## Использование типа never
5 |
6 | Тип `never` используется, когда функция гарантированно ничего не возвращает. Например, если внутри функции есть бесконечный цикл:
7 |
8 | ```typescript
9 | function infiniteLoop(): never {
10 | while (true) {
11 | // Какая-то логика
12 | }
13 | }
14 | ```
15 |
16 | Мы явно указали, что функция `infiniteLoop` ничего не возвращает.
17 |
18 | Еще тип `never` используется, если функция выбрасывает исключение:
19 |
20 | ```typescript
21 | function stop(message: string): never {
22 | throw new Error(message);
23 | }
24 | ```
25 |
26 | Также тип `never` используется, когда функция завершает программу:
27 |
28 | ```typescript
29 | function exit(code: number = 0): never {
30 | process.exit(code);
31 | }
32 | ```
33 |
34 | Важным условием для `never` является отсутствие нормального завершения функции. Например, в примере ниже компилятор выдаст ошибку:
35 |
36 | ```typescript
37 | // A function returning 'never' cannot have a reachable end point.
38 | function printSomething(): never {
39 | console.log('hexlet');
40 | }
41 | ```
42 |
43 | Функция `printSomething()` ничего не возвращает явно. Но так как она завершается в принципе, JavaScript подставляет неявный возврат `undefined`.
44 |
45 | `never` автоматически выводится даже там, где прописан явный возврат. Но компилятор видит, что этот возврат невозможен:
46 |
47 | ```typescript
48 | function fail() { // Автоматически выводится never
49 | // функция exit, определенная выше, имеет возвращаемый тип never
50 | return exit(1);
51 | }
52 | ```
53 |
--------------------------------------------------------------------------------
/modules/20-functions/35-never/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Тип never (возврат из функции)
3 | tips:
4 | - |
5 | [Never Type](https://basarat.gitbook.io/typescript/type-system/never)
6 |
--------------------------------------------------------------------------------
/modules/20-functions/35-never/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import fail from './index';
3 |
4 | test('function', () => {
5 | expect(fail).toThrow();
6 | });
7 |
--------------------------------------------------------------------------------
/modules/20-functions/40-unknown/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/20-functions/40-unknown/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function isPlainObject(value: unknown): boolean {
3 | return value instanceof Object && !Array.isArray(value);
4 | }
5 | // END
6 |
7 | export default isPlainObject;
8 |
--------------------------------------------------------------------------------
/modules/20-functions/40-unknown/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `isPlainObject()`, которая проверяет, является ли переданное значение объектом. Эта функция считает, что массив не объект:
3 |
4 | ```typescript
5 | isPlainObject(1); // false
6 | isPlainObject('hexlet'); // false
7 | isPlainObject({}); // true
8 | isPlainObject({ name: 'code-basics' }); // true
9 | isPlainObject([1, 8]); // false
10 | ```
11 |
--------------------------------------------------------------------------------
/modules/20-functions/40-unknown/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Использование типа `any` в TypeScript отключает проверки типов, что не желательно. Также в наиболее строгом режиме с помощью `"strict": true` в `tsconfig.json` использование `any` невозможно. А это значительно повышает безопасность кода.
3 |
4 | При этом бывают ситуации, когда тип неизвестен, но работа с ним должна быть безопасна с точки зрения типов. Для этого в TypeScript существует дополнение к `any` — `unknown`, которое разберем в этом уроке.
5 |
6 | ## Использование типа unknown
7 |
8 | Главное отличие `unknown` от `any` связано с проверкой типов. `unknown` запрещает выполнять любые операции:
9 |
10 | ```typescript
11 | let value: unknown = 'code-basics';
12 |
13 | value.toUpperCase(); // Error!
14 | value.trim(); // Error!
15 | ```
16 |
17 | Может показаться странным, что перед нами строка, но над ней нельзя выполнять строковые операции. К этому надо привыкнуть. Тип в статически типизированных языках определяется не тем, что мы видим своими глазами, а тем, как тип выводится — автоматически или через явное указание.
18 |
19 | Переменная `unknown` типа нужна редко — когда нужно дальше уточнить тип. Но все меняется, когда нам нужно создать функцию, которая может работать с любым входящим типом. В JavaScript распространена подобная практика:
20 |
21 | ```typescript
22 | // Пример из lodash
23 | _.isError(new Error); // true
24 | _.isError(Error); // false
25 | _.isError('code-basics'); // false
26 | ```
27 |
28 | Такую функцию можно реализовать с помощью `any`, но тогда мы отключим проверку типов:
29 |
30 | ```typescript
31 | function isError(value: any)
32 | ```
33 |
34 | Лучше использовать `unknown`, тогда TypeScript защитит от потенциальных ошибок типов:
35 |
36 | ```typescript
37 | function isError(value: unknown)
38 | ```
39 |
40 | Затем внутри тела можно выполнить нужную проверку, чтобы узнать, с чем мы работаем:
41 |
42 | ```typescript
43 | function isError(value: unknown): boolean {
44 | return value instanceof Error;
45 | }
46 | ```
47 |
48 | `instanceof` работает только с конструкторами, поэтому в примере выше мы проверяем, является ли значение экземпляром класса `Error`.
49 |
--------------------------------------------------------------------------------
/modules/20-functions/40-unknown/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Тип unknown
3 |
--------------------------------------------------------------------------------
/modules/20-functions/40-unknown/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import isPlainObject from './index';
3 |
4 | test('function', () => {
5 | expect(isPlainObject(3)).toBe(false);
6 | expect(isPlainObject('hexlet')).toBe(false);
7 | expect(isPlainObject({})).toBe(true);
8 | expect(isPlainObject([])).toBe(false);
9 | });
10 |
--------------------------------------------------------------------------------
/modules/20-functions/70-destructuring/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/20-functions/70-destructuring/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function lessonsCount({ lessons }: { lessons: string[] }): number {
3 | return lessons.length;
4 | }
5 | // END
6 |
7 | export default lessonsCount;
8 |
--------------------------------------------------------------------------------
/modules/20-functions/70-destructuring/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `lessonsCount()`, которая принимает на вход курс и возвращает количество лекций внутри него:
3 |
4 | ```typescript
5 | const course = { lessons: ['intro', 'lala'] };
6 | lessonsCount(course); // 2
7 | ```
8 |
9 | Используйте внутри деструктуризацию, чтобы извлечь уроки прямо в параметрах функции.
10 |
--------------------------------------------------------------------------------
/modules/20-functions/70-destructuring/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке разберем деструктуризацию в определении функций.
3 |
4 | ## Использование деструктуризации
5 |
6 | **Деструктуризация** — это механизм, с помощью которого переданный как аргумент объект распаковывается, а его части присваиваются локальным переменным функции. В JavaScript он выглядит так:
7 |
8 | ```javascript
9 | // Обычное определение
10 | const f = (user) => {
11 | console.log(user.firstName, user.age);
12 | };
13 |
14 | // Деструктурированный объект
15 | const f = ({ firstName, age }) => {
16 | console.log(firstName, age);
17 | };
18 |
19 | const user = { firstName: 'Smith', age: 30 };
20 | f(user); // => 'Smith', 30
21 | ```
22 |
23 | Деструктурированный объект визуально похож на параметры функции. При этом он все равно остается объектом, поэтому в TypeScript его тип описывается после закрывающей фигурной скобки:
24 |
25 | ```typescript
26 | // Обычное определение
27 | function f(user: { firstName: string, age: number }) {
28 | console.log(user.firstName, user.age);
29 | }
30 |
31 | // Деструктурированный объект
32 | function f({ firstName, age }: { firstName: string, age: number }) {
33 | console.log(firstName, age);
34 | }
35 | ```
36 |
37 | Здесь мы описали тип объекта внутри параметра функции и сразу же деструктурировали его.
38 |
39 | Если вынести определение типа в псевдоним, то можно сделать код поменьше:
40 |
41 | ```typescript
42 | type User = {
43 | firstName: string;
44 | age: number;
45 | }
46 |
47 | function foo({ firstName, age }: User) {
48 | console.log(firstName, age);
49 | }
50 | ```
51 |
52 | Этот же тип `User` можно будет использовать и в других местах.
53 |
54 | То же самое применимо и к массивам:
55 |
56 | ```typescript
57 | // Обычное определение
58 | function foo(point: number[]) {
59 | console.log(point);
60 | }
61 |
62 | // Деструктурированный массив
63 | function foo([x, y]: number[]) {
64 | console.log(x, y);
65 | }
66 |
67 | type Point = number[];
68 |
69 | // С псевдонимом
70 | function foo([x, y]: Point) {
71 | console.log(x, y);
72 | }
73 | ```
74 |
75 | Использование деструктуризации удобно, когда внутри функции нужно использовать несколько свойств объекта или элементов массива. В таком случае можно сразу извлечь их в параметрах функции, а не писать `user.firstName` или `point[0]` внутри тела функции.
76 |
--------------------------------------------------------------------------------
/modules/20-functions/70-destructuring/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Деструктуризация
3 | tips:
4 | - >
5 | [Официальная
6 | документация](https://www.typescriptlang.org/docs/handbook/2/functions.html#parameter-destructuring)
7 |
--------------------------------------------------------------------------------
/modules/20-functions/70-destructuring/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import lessonsCount from './index';
3 |
4 | test('function', () => {
5 | expect(lessonsCount({ lessons: [] })).toBe(0);
6 | const course = { lessons: ['intro'] };
7 | expect(lessonsCount(course)).toBe(1);
8 | });
9 |
--------------------------------------------------------------------------------
/modules/20-functions/80-rest-spread/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/20-functions/80-rest-spread/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function max(first: number, ...rest: number[]): number {
3 | return Math.max(first, ...rest);
4 | }
5 | // END
6 |
7 | export default max;
8 |
--------------------------------------------------------------------------------
/modules/20-functions/80-rest-spread/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Определите функцию `max()`, которая отличается от примера из урока только тем, что у нее первый параметр обязательный.
3 |
4 | Например:
5 |
6 | ```typescript
7 | max(1,2,3);
8 | max(234);
9 | ```
10 |
--------------------------------------------------------------------------------
/modules/20-functions/80-rest-spread/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Сегодня разберем rest- и spread-операторы.
4 |
5 | ## Rest-оператор
6 |
7 | Rest-оператор позволяет создавать функции с переменным числом параметров, при этом сворачивать их в массив:
8 |
9 | ```typescript
10 | function max(...numbers: number[]): number {
11 | return Math.max(...numbers);
12 | }
13 | ```
14 |
15 | Этот массив является обычным параметром функции, поэтому ему задается тип в соответствии с тем, какие значения ожидаются внутри этого массива. Пример с двумя параметрами:
16 |
17 | ```typescript
18 | function do(operation: string, ...numbers: number[]) {
19 | // выполняем операцию operation для всех numbers
20 | }
21 | ```
22 |
23 | В этом смысле rest-оператор в TypeScript ничем не отличается от rest-оператора в JavaScript. А вот со spread-оператором есть одна особенность.
24 |
25 | ## Spread-оператор
26 |
27 | Spread-оператор в функциях — это как rest-оператор наоборот. Он позволяет раскладывать массив на отдельные параметры:
28 |
29 | ```typescript
30 | const numbers = [1, 2, 3];
31 | Math.max(...numbers);
32 | ```
33 |
34 | Если функция принимает на вход любое количество аргументов, как в примере выше, то такой код работает без проблем. Но если функция принимает на вход определенное число аргументов, то TypeScript выдаст ошибку компиляции:
35 |
36 | ```typescript
37 | function sum(a: number, b: number): number {
38 | return a + b;
39 | }
40 |
41 | // Выведенный тип number[] — "ноль или больше чисел",
42 | // а не "массив из двух чисел"
43 | const args = [1, 2];
44 | sum(...args);
45 | // A spread argument must either have a tuple type
46 | // or be passed to a rest parameter.
47 | ```
48 |
49 | В примере выше TypeScript не может понять, что массив `args` состоит из двух чисел. Массивы в JavaScript изменяемы, поэтому TypeScript не может полагаться на количество элементов в конкретный момент времени. Есть разные способы обойти это ограничение. Но в этой ситуации проще использовать Type Assertion — указание компилятору, что мы точно знаем о коде:
50 |
51 | ```typescript
52 | const args = [1, 2] as const;
53 | ```
54 |
55 | Подробнее о Type Assertion поговорим в модуле о типах. С его помощью мы явно указываем, что этот массив состоит из двух конкретных значений, которые не поменяются.
56 |
--------------------------------------------------------------------------------
/modules/20-functions/80-rest-spread/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Rest и Spread
3 |
--------------------------------------------------------------------------------
/modules/20-functions/80-rest-spread/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import max from './index';
3 |
4 | test('function', () => {
5 | expect(max(1, 3, 8)).toBe(8);
6 | expect(max(10)).toBe(10);
7 | expect(max(4, 1)).toBe(4);
8 | });
9 |
--------------------------------------------------------------------------------
/modules/20-functions/85-function-overloads/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/20-functions/85-function-overloads/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | // BEGIN
4 | function newYearCongratulate(name: string): string;
5 | function newYearCongratulate(year: number, name: string): string;
6 | function newYearCongratulate(data1: string | number, data2?: string): string {
7 | if (typeof data1 === 'number') {
8 | return `Hi ${data2}! Happy New Year ${data1}!`;
9 | }
10 |
11 | return `Hi ${data1}! Happy New Year!`;
12 | }
13 | // END
14 |
15 | export default newYearCongratulate;
16 |
--------------------------------------------------------------------------------
/modules/20-functions/85-function-overloads/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `newYearCongratulate()`, которая аналогична примеру на Kotlin из теории:
3 |
4 | ```typescript
5 | newYearCongratulate('John'); // Hi John! Happy New Year!
6 | newYearCongratulate(2023, 'Mila'); // Hi Mila! Happy New Year 2023!
7 | ```
8 |
--------------------------------------------------------------------------------
/modules/20-functions/85-function-overloads/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Перегрузка функций (Function Overloads)
3 | tips:
4 | - >
5 | [Официальная
6 | документация](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads)
7 |
--------------------------------------------------------------------------------
/modules/20-functions/85-function-overloads/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import sayHello from './index';
3 |
4 | test('function', () => {
5 | expect(sayHello('John')).toBe('Hi John! Happy New Year!');
6 | expect(sayHello(2023, 'Mila')).toBe('Hi Mila! Happy New Year 2023!');
7 | });
8 |
--------------------------------------------------------------------------------
/modules/20-functions/90-type-narrowing/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/20-functions/90-type-narrowing/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function last(value: string | number): string | number {
3 | if (typeof value === 'number') {
4 | return value % 10;
5 | }
6 |
7 | return value[value.length - 1] ?? '';
8 | }
9 | // END
10 |
11 | export default last;
12 |
--------------------------------------------------------------------------------
/modules/20-functions/90-type-narrowing/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `last()`, которая извлекает последний элемент из переданного значения. Значением может быть строка или число. Функция возвращает значение того же типа, которое было передано в качестве параметра:
3 |
4 | ```typescript
5 | // Передаем в качестве параметра строку
6 | // Функция возвращает строку
7 | last('hexlet'); // t
8 |
9 | // Передаем в качестве параметра число
10 | // Функция возвращает число
11 | last(12345); // 5
12 | ```
13 |
--------------------------------------------------------------------------------
/modules/20-functions/90-type-narrowing/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Сужение типа (Narrowing)
3 | tips:
4 | - >
5 | [Официальная
6 | документация](https://www.typescriptlang.org/docs/handbook/2/narrowing.html)
7 | - |
8 | [Type Guard](https://basarat.gitbook.io/typescript/type-system/typeguard)
9 |
--------------------------------------------------------------------------------
/modules/20-functions/90-type-narrowing/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import last from './index';
3 |
4 | test('function', () => {
5 | expect(last('John')).toBe('n');
6 | expect(last('')).toBe('');
7 | expect(last(1)).toBe(1);
8 | expect(last(134)).toBe(4);
9 | });
10 |
--------------------------------------------------------------------------------
/modules/20-functions/description.ru.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Типизация функций
3 | description: |
4 | Большая часть задач по типизации функций связана с правильным описанием входных параметров и возвращаемых значений, которые могут быть достаточно сложными. Сюда же входят особенности работы с rest и destructuring.
5 |
--------------------------------------------------------------------------------
/modules/22-arrays/20-type-annotations/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/22-arrays/20-type-annotations/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function unique(coll: (number | string)[]): (number | string)[] {
3 | const init: (number | string)[] = [];
4 |
5 | return coll.reduce(
6 | (acc, curr) => (acc.includes(curr) ? acc : [...acc, curr]),
7 | init,
8 | );
9 | }
10 | // END
11 |
12 | export default unique;
13 |
--------------------------------------------------------------------------------
/modules/22-arrays/20-type-annotations/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `unique()`, которая убирает дубликаты из массива. Функция принимает на вход массив чисел и строк. Результатом работы функции должен быть новый массив, в котором сохраняется только первое вхождение каждого элемента. Порядок значений результата определяется порядком их появления в массиве.
3 |
4 | ```typescript
5 | unique([9, 9, 3, 8, 8]); // [9, 3, 8]
6 | unique(['twinkle', 'twinkle', 'little', 'bat']); // ['twinkle', 'little', 'bat']
7 | unique([1, 1, 3, 'oops!']); // [1, 3, 'oops!']
8 | ```
9 |
--------------------------------------------------------------------------------
/modules/22-arrays/20-type-annotations/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке мы научимся использовать аннотации типов.
3 |
4 | ## Использование аннотации типов
5 |
6 | В простых случаях тип массива определяется как название типа и квадратные скобки после него, например: `string[]`, `number[]`. Эта схема работает и с псевдонимами типов:
7 |
8 | ```typescript
9 | type User = {
10 | name: string
11 | };
12 |
13 | // При определении констант и переменных
14 | const users: User[] = [];
15 |
16 | // В определении функций
17 | function foo(users: User[]) {
18 | // ...
19 | }
20 | ```
21 |
22 | Здесь мы определяем массив, элементами которого являются объекты типа `User`. В таком массиве можно хранить только объекты, которые соответствуют типу `User`.
23 |
24 | В случае составных типов, например, если мы хотим использовать объединение или описание объекта, добавляются круглые скобки — `(Type)[]`:
25 |
26 | ```typescript
27 | const users: ({ name: string })[] = [];
28 | const users: (string | null)[] = [];
29 | const users: (User | null | { name: string })[] = [];
30 | ```
31 |
32 | Внутри круглых скобок стоит описание типа, а затем уже идет квадратные скобки.
33 |
34 | Также TypeScript дает еще один синтаксис, который описывается так: `Array`. Он универсальный — с его помощью можно описать любой массив. Описывается тип в такой записи между знаками меньше и больше:
35 |
36 | ```typescript
37 | const users: Array = [];
38 | const users: Array = [];
39 | const users: Array = [];
40 |
41 | const users: Array<{ name: string }> = [];
42 | const users: Array = [];
43 | ```
44 |
45 | Но обычно так не делают. Там, где можно использовать более короткий вариант, используют его. Форма `Array` нужна в первую очередь для дженериков, которые рассмотрим позже.
46 |
47 | ## Определение пустого массива
48 |
49 | Если определить пустой массив и не указать тип, то его типом автоматически станет `any[]`. В такой массив можно добавить любые данные, включая вложенные массивы:
50 |
51 | ```typescript
52 | const items = [];
53 | items.push(1);
54 | items.push('wow');
55 | items.push(['code-basics', 'hexlet']);
56 | ```
57 |
58 | Код с `any` будет работать всегда, но он выключает проверку типов. Чтобы этого не происходило, нужно всегда явно типизировать пустой массив:
59 |
60 | ```typescript
61 | const items: Array = [];
62 | ```
63 |
--------------------------------------------------------------------------------
/modules/22-arrays/20-type-annotations/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Аннотации типов
3 | tips:
4 | - >
5 | [Массивы в официальной
6 | документации](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#arrays)
7 |
--------------------------------------------------------------------------------
/modules/22-arrays/20-type-annotations/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import unique from './index';
3 |
4 | test('function', () => {
5 | expect(unique([])).toEqual([]);
6 | expect(unique([2, 3, -100, -100, -100])).toEqual([2, 3, -100]);
7 | expect(unique(['as', 'good', 'as', 'it', 'gets'])).toEqual([
8 | 'as',
9 | 'good',
10 | 'it',
11 | 'gets',
12 | ]);
13 | expect(unique([1, 1, 3, 'oops!'])).toEqual([1, 3, 'oops!']);
14 | });
15 |
--------------------------------------------------------------------------------
/modules/22-arrays/25-multi-dimensional-arrays/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/22-arrays/25-multi-dimensional-arrays/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function getField(size: number): null[][] {
3 | const field = Array(size).fill(null).map(() => Array(size).fill(null));
4 | return field;
5 | }
6 | // END
7 |
8 | export default getField;
9 |
--------------------------------------------------------------------------------
/modules/22-arrays/25-multi-dimensional-arrays/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `getField()`, которая генерирует игровое поле для крестиков ноликов. Функция принимает на вход размерность поля и возвращает массив массивов нужного размера, заполненный значениями `null`.
3 |
4 | ```typescript
5 | const field1 = getField(1);
6 | console.log(field1);
7 | // [[null]]
8 |
9 | const field2 = getField(2);
10 | console.log(field2);
11 | // [[null, null], [null, null]]
12 | ```
13 |
--------------------------------------------------------------------------------
/modules/22-arrays/25-multi-dimensional-arrays/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке мы рассмотрим многомерные массивы.
3 |
4 | ## Работа с многомерными массивами
5 |
6 | Чтобы определить многомерные массивы, нужно использовать синтаксис `Type[][]`. Дословно это означает, что перед нами массив, который содержит массивы со значениями типа `Type`. Несколько примеров:
7 |
8 | ```typescript
9 | // Тип number[][] выводится автоматически
10 | const items1 = [[3, 8], [10, 4, 8]];
11 |
12 | const items2: number[][] = []
13 | // или так Array
14 |
15 | // Используя псевдоним
16 | type User = {
17 | name: string;
18 | }
19 |
20 | // или так Array
21 | const users: User[][] = [
22 | [{ name: 'Eva'}, { name: 'Adam' }],
23 | ];
24 | ```
25 |
26 | Добавление в такие массивы немассивов приведет к ошибке типизации:
27 |
28 | ```typescript
29 | items1.push(99); // Error: Type 'number' is not assignable
30 | ```
31 |
32 | Чтобы определить массивы составных типов, нужно использовать скобки:
33 |
34 | ```typescript
35 | const coll: (string | number)[][] = [];
36 | coll.push(['hexlet', 5])
37 | ```
38 |
39 | Также можно использовать синтаксис `Array>`. В примере ниже массив, внутри которого находятся массивы, содержащие значения типа `Type`:
40 |
41 | ```typescript
42 | const coll: Array> = [];
43 | coll.push(['hexlet', 5])
44 | ```
45 |
46 | Сами массивы при этом могут быть частью объекта. Технически это позволяет создавать бесконечную вложенность из объектов и массивов:
47 |
48 | ```typescript
49 | type Course = {
50 | name: string;
51 | lessons: Lesson[];
52 | }
53 |
54 | type Lesson = {
55 | name: string;
56 | links: string[];
57 | }
58 | ```
59 |
60 | Здесь мы определяем тип `Course`, который содержит массив `lessons`. Каждый элемент этого массива — это объект типа `Lesson`, который содержит массив `links`. Каждый элемент этого массива — это строка. Такая структура данных может быть полезна, например, для хранения информации о курсах на сайте.
61 |
--------------------------------------------------------------------------------
/modules/22-arrays/25-multi-dimensional-arrays/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Многомерные массивы
3 |
--------------------------------------------------------------------------------
/modules/22-arrays/25-multi-dimensional-arrays/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import getField from './index';
3 |
4 | test('function', () => {
5 | expect(getField(1)).toEqual([[null]]);
6 | expect(getField(2)).toEqual([[null, null], [null, null]]);
7 | expect(getField(3)).toEqual(
8 | [[null, null, null], [null, null, null], [null, null, null]],
9 | );
10 | });
11 |
--------------------------------------------------------------------------------
/modules/22-arrays/40-readonly-arrays/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/22-arrays/40-readonly-arrays/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function reverse(coll: readonly number[]): number[] {
3 | return coll.map((_, index) => coll[coll.length - 1 - index]);
4 | }
5 | // END
6 |
7 | export default reverse;
8 |
--------------------------------------------------------------------------------
/modules/22-arrays/40-readonly-arrays/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `reverse()`, которая переворачивает массив. Технически она должна возвращать новый массив, в котором элементы расположены в обратном порядке. Используйте модификатор `readonly` для входящего массива. Не используйте встроенный метод `reverse()`.
3 |
4 | ```typescript
5 | reverse([1, 2, 8]); // [8, 2, 1]
6 | reverse([10, 33, 7, 0]); // [0, 7, 33, 10]
7 | ```
8 |
--------------------------------------------------------------------------------
/modules/22-arrays/40-readonly-arrays/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В разработке на JavaScript, где активно применяются функции высшего порядка, такие как `map`, `filter` и `reduce`, массивы меняются редко. Обычно вместо этого создаются новые.
3 |
4 | Технически JavaScript не может запретить изменять существующие массивы, поэтому ответственность за соблюдение этого правила лежит на программистах. В этом уроке разберем работу с массивами только для чтения.
5 |
6 | ## Использование неизменяемых массивов
7 |
8 | В TypeScript работа с неизменяемыми массивами встроена в систему типов. Чтобы гарантировать неизменяемость, массив помечается модификатором `readonly`:
9 |
10 | ```typescript
11 | function process(numbers: readonly number[]) {
12 | numbers.push(1); // Error!
13 | }
14 | ```
15 |
16 | В этом случае TypeScript выдает ошибку, что тип `readonly number[]` не содержит метода `push`.
17 |
18 | Модификатор `readonly` запрещает изменение массива, но не запрещает изменение объектов, которые находятся внутри массива:
19 |
20 | ```typescript
21 | const items: readonly ({ key: string })[] = [{ key: 'value'}];
22 | items[0].key = 'another value'; // ok!
23 | ```
24 |
25 | Мы успешно изменили значение свойства `key` в объекте, который находится внутри массива.
26 |
27 | Модификатор `readonly` — синтаксический сахар. В случае массива `readonly` меняет тип `Array` на тип `ReadonlyArray`. Такая запись, как и `Array` улучшает читабельность кода, но в остальном не отличается от `readonly Type[]`.
28 |
29 | Код выше можно было бы записать так:
30 |
31 | ```typescript
32 | const items: ReadonlyArray<{ key: string }> = [{ key: 'value'}];
33 | ```
34 |
--------------------------------------------------------------------------------
/modules/22-arrays/40-readonly-arrays/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Массивы только для чтения
3 | tips:
4 | - >
5 | [Массивы только для чтения и
6 | кортежи](https://mariusschulz.com/blog/read-only-array-and-tuple-types-in-typescript/)
7 |
--------------------------------------------------------------------------------
/modules/22-arrays/40-readonly-arrays/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import reverse from './index';
3 |
4 | test('function', () => {
5 | expect(reverse([])).toEqual([]);
6 | expect(reverse([1, 2])).toEqual([2, 1]);
7 | expect(reverse([8, 3, 9])).toEqual([9, 3, 8]);
8 | });
9 |
--------------------------------------------------------------------------------
/modules/22-arrays/50-tuples/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/22-arrays/50-tuples/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | export type Point = [number, number, number];
3 |
4 | function isTheSamePoint(p1: Point, p2: Point): boolean {
5 | return p1.every((el, i) => el === p2[i]);
6 | }
7 | // END
8 |
9 | export default isTheSamePoint;
10 |
--------------------------------------------------------------------------------
/modules/22-arrays/50-tuples/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Создайте и экспортируйте тип `Point`, который описывает точку в пространстве, состоящую из трех координат: x, y, z.
3 |
4 | Реализуйте функцию `isTheSamePoint()`, которая проверяет две точки на их одинаковое расположение. Две точки совпадают, если совпадают все их координаты:
5 |
6 | ```typescript
7 | const p1: Point = [1, 3, 4];
8 | const p2: Point = [1, 3, 4];
9 | const p3: Point = [0, 8, 4];
10 |
11 | isTheSamePoint(p1, p2); // true
12 | isTheSamePoint(p1, p3); // false
13 | isTheSamePoint(p2, p3); // false
14 | ```
15 |
--------------------------------------------------------------------------------
/modules/22-arrays/50-tuples/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Обычно массивы могут менять свой размер и содержать от нуля значений. Поэтому пустой массив как значение `[]` является валидным для массивов любого типа.
3 |
4 | При этом иногда массивы выступают в качестве упрощенной версии объекта, где количество значений и их порядок строго определены. Например, с помощью такого массива можно представить точку на плоскости: `[x, y]`.
5 |
6 | Такие массивы нужны для экономии символов, когда приходится создавать много одинаковых данных, например, для тестирования.
7 |
8 | В TypeScript подобные массивы называются кортежами, с которыми мы познакомимся в этом уроке.
9 |
10 | ## Использование кортежей
11 |
12 | У кортежей есть свой синтаксис определения. Например, рассмотрим представление точки:
13 |
14 | ```typescript
15 | const point: [number, number] = [1, 3]
16 | // Можно поменять
17 | const point[0] = 4;
18 |
19 | // Обращение к несуществующему индексу приведет к ошибке
20 | point[3]; // Error!
21 |
22 | // Нельзя создать не совпадающий по типу
23 | const point2: [number, number] = [1, 'x']; // Error!
24 | ```
25 |
26 | Так как кортежи имеют фиксированное количество элементов, было бы логично, если бы такое же поведение применялось к `push()` или `pop()`. Ведь если мы определили кортеж из двух элементов, то элементов должно быть именно два.
27 |
28 | На практике код ниже сработает:
29 |
30 | ```typescript
31 | point.push(10);
32 | console.log(point); // [4, 3, 10];
33 | ```
34 |
35 | Такое поведения сохраняется для [обратной совместимости](https://stackoverflow.com/questions/64069552/typescript-array-push-method-cant-catch-a-tuple-type-of-the-array). И общая рекомендация состоит в том, чтобы не пытаться изменять размер кортежа.
36 |
37 | Кортежи могут состоять из элементов разных типов:
38 |
39 | ```typescript
40 | type HTTPResponse = [number, string];
41 |
42 | // Порядок определения важен
43 | const response: HTTPResponse = [404, 'Page is not found'];
44 | // Так не сработает ['Page is not found', 404]
45 | ```
46 |
47 | Часть из них может быть опциональная. В таком случае опциональные элементы должны быть в конце кортежа:
48 |
49 | ```typescript
50 | type HTTPResponse = [number, string?];
51 |
52 | const response1: HTTPResponse = [500];
53 | const response2: HTTPResponse = [201, 'Created'];
54 | ```
55 |
56 | В примере выше первый элемент массива всегда должен быть числом, а второй — строкой или может отсутствовать.
57 |
58 | Если создавать переменные для кортежей и использовать псевдоним, то его нужно указывать явно. Иначе с точки зрения TypeScript будет создан обычный массив:
59 |
60 | ```typescript
61 | // Будет иметь тип (string | number)[]
62 | const response = [201, 'Created'];
63 | ```
64 |
--------------------------------------------------------------------------------
/modules/22-arrays/50-tuples/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Кортежи (Tuples)
3 | tips:
4 | - >
5 | [Кортежи в официальной
6 | документации](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types)
7 |
--------------------------------------------------------------------------------
/modules/22-arrays/50-tuples/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import isTheSamePoint, { Point } from './index';
3 |
4 | test('function', () => {
5 | const p1: Point = [1, 2, 3];
6 | const p2: Point = [1, 2, 3];
7 | const p3: Point = [0, 2, 3];
8 | const p4: Point = [1, 21, 3];
9 | const p5: Point = [1, 2, 13];
10 |
11 | expect(isTheSamePoint(p1, p2)).toBe(true);
12 | expect(isTheSamePoint(p1, p3)).toBe(false);
13 | expect(isTheSamePoint(p4, p5)).toBe(false);
14 | });
15 |
--------------------------------------------------------------------------------
/modules/22-arrays/description.ru.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Типизация массивов
3 | description: |
4 | Работа с массивами в некоторых случаях требует более точной типизации. В этом модуле мы рассмотрим особенности типизации массивов: содержащие элементы разных типов, многомерные массивы и массивы фиксированной длины.
5 |
--------------------------------------------------------------------------------
/modules/25-types/10-type-as-sets/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/25-types/10-type-as-sets/assets/some_type.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hexlet-basics/exercises-typescript/bf724cc97528629332c3b3c493b164ccea548c64/modules/25-types/10-type-as-sets/assets/some_type.png
--------------------------------------------------------------------------------
/modules/25-types/10-type-as-sets/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | type CustomType = null | undefined | number;
3 | // END
4 |
5 | export default CustomType;
6 |
--------------------------------------------------------------------------------
/modules/25-types/10-type-as-sets/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Определите тип `CustomType`, который может принимать следующие значения:
3 |
4 | 1. null
5 | 2. undefined
6 | 3. числа
7 |
--------------------------------------------------------------------------------
/modules/25-types/10-type-as-sets/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Типы как множества
3 |
--------------------------------------------------------------------------------
/modules/25-types/10-type-as-sets/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import type CustomType from './index';
3 |
4 | test('CustomType', () => {
5 | const numberValue: CustomType = 1;
6 | expect(numberValue).toBe(1);
7 |
8 | const nullValue: CustomType = null;
9 | expect(nullValue).toBe(null);
10 |
11 | const undefinedValue: CustomType = undefined;
12 | expect(undefinedValue).toBe(undefined);
13 | });
14 |
--------------------------------------------------------------------------------
/modules/25-types/20-union-types/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/25-types/20-union-types/assets/number_or_string.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hexlet-basics/exercises-typescript/bf724cc97528629332c3b3c493b164ccea548c64/modules/25-types/20-union-types/assets/number_or_string.png
--------------------------------------------------------------------------------
/modules/25-types/20-union-types/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function lastIndex(str: string, char: string): number | null {
3 | const index = str.lastIndexOf(char);
4 | return index === -1 ? null : index;
5 | }
6 | // END
7 |
8 | export default lastIndex;
9 |
--------------------------------------------------------------------------------
/modules/25-types/20-union-types/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 | Реализуйте функцию `lastIndex(str, char)`, которая возвращает индекс последнего вхождения символа в строку или `null`, если такого символа нет.
2 |
3 | ```typescript
4 | const str = 'test';
5 | lastIndex(str, 't'); // 3
6 | lastIndex(str, 'p'); // null
7 | ```
8 |
--------------------------------------------------------------------------------
/modules/25-types/20-union-types/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке мы научимся работать с объединениями типов, которые играют большую роль в TypeScript. Они позволяют выразить обычную ситуацию для JavaScript, когда возвращаемое значение или аргумент функции могут быть различного типа. Например, метод `String.prototype.at()` может возвращать значение типа `string` либо `undefined`.
3 |
4 | Объединение указывается с помощью оператора прямой черты `|`, по обе стороны которого располагаются типы.
5 |
6 | Определим свой тип для функции `at`:
7 |
8 | ```typescript
9 | type at = (str: string, position: number) => string | undefined;
10 | ```
11 |
12 |
13 |
14 | С точки зрения теории множеств операция union обозначает объединение. Когда мы объединяем несколько множеств, получается новое множество, в которое входят все элементы исходных множеств.
15 |
16 | В TypeScript это означает, что в результате мы получаем тип, который обещает содержать переменную одного из типов объединения. Так мы можем завести свой тип, под который попадают все строки **ИЛИ** числа:
17 |
18 | ```typescript
19 | type NumberOrString = number | string;
20 |
21 | let val: NumberOrString = 10; // OK
22 | val = 'My string'; // OK
23 | val = true; // Type 'boolean' is not assignable to type 'NumberOrString'.
24 | ```
25 |
26 |
27 |
28 | 
29 |
30 | На практике нередко встречаются случаи, когда нам нужно поддержать работу функции с большим количеством допустимых значений. В JavaScript мы можем соединить строку не только со строкой, но и числом или с `true`. Для решения похожей задачи в прошлых уроках мы познакомились с перегрузкой функции. Опишем тип такой функции с применением объединения:
31 |
32 | ```typescript
33 | type AllowedToConcatenation = number | string | null | undefined | boolean;
34 |
35 | const concat = (base: AllowedToConcatenation, suffix: AllowedToConcatenation): string => `${base}${suffix}`;
36 | ```
37 |
38 | Чтобы описать все допустимые значения функции `concat()` через перегрузку, нам бы потребовалось написать код для каждого случая отдельно. Здесь же мы описали тип `AllowedToConcatenation` через объединение один раз и применили его в двух местах.
39 |
40 | Union Types используется повсеместно, где программист хочет сказать, что переменная может содержать значения разных, но заранее описанных типов. Чтобы указать произвольные типы, может использоваться `unknown` или дженерики, которые рассмотрим далее в курсе.
41 |
--------------------------------------------------------------------------------
/modules/25-types/20-union-types/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Объединения (Union Types)
3 |
--------------------------------------------------------------------------------
/modules/25-types/20-union-types/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import lastIndex from './index';
4 |
5 | test('lastIndex', () => {
6 | const str = 'jestTest';
7 | expect(lastIndex(str, 'j')).toBe(0);
8 | expect(lastIndex(str, 't')).toBe(7);
9 | expect(lastIndex(str, 'e')).toBe(5);
10 | expect(lastIndex(str, 'p')).toBeNull();
11 |
12 | expectTypeOf(lastIndex).returns.toMatchTypeOf();
13 | });
14 |
--------------------------------------------------------------------------------
/modules/25-types/22-nullable/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/25-types/22-nullable/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function formatPrice(price?: number | null): string {
3 | if (price === undefined || price === null) {
4 | return '$0.00';
5 | }
6 |
7 | return `$${price.toFixed(2)}`;
8 | }
9 | // END
10 |
11 | export default formatPrice;
12 |
--------------------------------------------------------------------------------
/modules/25-types/22-nullable/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `formatPrice()`, которая принимает число и возвращает строку с округлением до второго числа после запятой. Если пришел `null` или `undefined` должна вернуться `'$0.00'`.
3 |
4 | ```typescript
5 | formatPrice(3.145); // '$3.15'
6 | formatPrice(200); // '$200.00'
7 | formatPrice(); // '$0.00'
8 | formatPrice(null); // '$0.00'
9 | ```
10 |
--------------------------------------------------------------------------------
/modules/25-types/22-nullable/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В TypeScript `null` и `undefined` не просто значения. Это два типа, которые состоят из одного значения. Представим, если бы TypeScript работал так же, как JavaScript. Тогда эти значения можно было бы передавать в любом месте. И неважно, что там ожидается: строка, массив и тому подобное.
3 |
4 | Все было бы хорошо, пока не пришло время выполнения кода. В этот момент мы бы получили ошибку, потому что внутри функции ожидался бы массив, а пришел `null` или `undefined`. Такая проблема, например, существует в JavaScript:
5 |
6 | ```javascript
7 | function foo(value) {
8 | const upperValue = value.toUpperCase();
9 | // остальная логика
10 | }
11 |
12 | foo(null); // Uncaught TypeError: Cannot read properties of null (reading 'toUpperCase')
13 | ```
14 |
15 | В статически типизированных языках, где `null` используется как общий тип для всего, проверка типов ничего не подскажет. В таком случае возникает исключение `NullPointerException` — одно из самых запоминающихся для всех пользователей. Поэтому код начинает обрастать проверками на `null`:
16 |
17 | ```java
18 | public void doSomething(SomeObject obj) {
19 | if(obj != null) {
20 | obj.myMethod();
21 | }
22 | }
23 |
24 | doSomething(null);
25 | ```
26 |
27 | Данный пример на Java показывает, что `null` может быть передан в любое место, где ожидается объект. Поэтому внутри функции мы должны проверить, что `obj` не равен `null`. Если бы мы не сделали этой проверки, то получили бы исключение `NullPointerException`.
28 |
29 | В TypeScript c правильной (`strict`) конфигурацией подобная проверка встроена, и статический анализатор скажет о возможной проблеме:
30 |
31 | ```typescript
32 | function foo(value?: string | null) {
33 | const upperValue = value.toUpperCase(); // Object is possibly 'null' or 'undefined'.
34 | // остальная логика
35 | }
36 | ```
37 |
38 | В данном случае мы получили ошибку компиляции, потому что `value` может быть `null` или `undefined`.
39 |
40 | Чтобы ее решить, нужно написать соответствующее условие или использовать оператор `?.`. Это позволяет избежать ошибок во время исполнения кода:
41 |
42 | ```typescript
43 | function foo(value?: string | null) {
44 | if (value !== null && value !== undefined) {
45 | const upperValue = value.toUpperCase(); // (parameter) value: string
46 | }
47 | // остальная логика
48 | }
49 | ```
50 |
51 | Это стало возможным благодаря выделению значений `null` и `undefined` в отдельные типы. Благодаря каждой проверке мы отсекаем не подходящее нам множество значений и получаем безопасный вызов метода. Такие проверки также называются отсечением типов (Differentiating Types) и Type Guards.
52 |
--------------------------------------------------------------------------------
/modules/25-types/22-nullable/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Null и Undefined
3 | tips:
4 | - >
5 | [Type Guards и Differentiating
6 | Types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types)
7 | - |
8 | [Type Guard](https://basarat.gitbook.io/typescript/type-system/typeguard)
9 | - >
10 | [Optional
11 | chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
12 |
--------------------------------------------------------------------------------
/modules/25-types/22-nullable/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import formatPrice from './index';
3 |
4 | test('formatPrice', () => {
5 | expect(formatPrice()).toBe('$0.00');
6 | expect(formatPrice(null)).toBe('$0.00');
7 | expect(formatPrice(200)).toBe('$200.00');
8 | expect(formatPrice(3.145)).toBe('$3.15');
9 | });
10 |
--------------------------------------------------------------------------------
/modules/25-types/25-literal-types/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/25-types/25-literal-types/index.ts:
--------------------------------------------------------------------------------
1 | type Turtle = 'turtle' | null;
2 |
3 | type Game = {
4 | makeTurn: (direction: 'left' | 'right') => void;
5 | state: Array;
6 | };
7 |
8 | const startGame = (): Game => {
9 | const state: Array = ['turtle', null, null, null, null];
10 |
11 | // BEGIN
12 | const makeTurn = (direction: 'left' | 'right'): void => {
13 | const turtleIndex = state.indexOf('turtle');
14 | const nextIndex = direction === 'left' ? turtleIndex - 1 : turtleIndex + 1;
15 |
16 | if (nextIndex < 0 || nextIndex > 4) {
17 | throw new Error('Out of bounds');
18 | }
19 |
20 | state[turtleIndex] = null;
21 | state[nextIndex] = 'turtle';
22 | };
23 | // END
24 |
25 | return { makeTurn, state };
26 | };
27 |
28 | export default startGame;
29 |
--------------------------------------------------------------------------------
/modules/25-types/25-literal-types/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `makeTurn()`, которая принимает строку `left` или `right` и перемещает черепашку вперед-назад по одномерному массиву фиксированного размера с пятью элементами. Если черепашка выходит за пределы массива, то выбрасывается исключение.
3 |
4 | ```typescript
5 | const { makeTurn, state } = startGame();
6 | console.log(state); // ['turtle', null, null, null, null]
7 |
8 | makeTurn('left') // ERROR
9 |
10 | makeTurn('right');
11 | makeTurn('right');
12 | console.log(state); // [null, null, 'turtle', null, null]
13 | ```
14 |
--------------------------------------------------------------------------------
/modules/25-types/25-literal-types/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Литералы (Literal Types)
3 | tips:
4 | - >
5 | [Literal
6 | Types](https://basarat.gitbook.io/typescript/type-system/literal-types)
7 |
--------------------------------------------------------------------------------
/modules/25-types/25-literal-types/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import startGame from './index';
4 |
5 | test('startTurtleGame', () => {
6 | const { makeTurn, state } = startGame();
7 |
8 | expect(state).toEqual(['turtle', null, null, null, null]);
9 |
10 | expect(() => makeTurn('left')).toThrow();
11 | expect(state).toEqual(['turtle', null, null, null, null]);
12 |
13 | makeTurn('right');
14 | expect(state).toEqual([null, 'turtle', null, null, null]);
15 |
16 | makeTurn('right');
17 | makeTurn('right');
18 | expect(state).toEqual([null, null, null, 'turtle', null]);
19 |
20 | makeTurn('right');
21 | expect(state).toEqual([null, null, null, null, 'turtle']);
22 |
23 | expect(() => makeTurn('right')).toThrow();
24 |
25 | makeTurn('left');
26 | expect(state).toEqual([null, null, null, 'turtle', null]);
27 |
28 | expectTypeOf(makeTurn).returns.toMatchTypeOf();
29 | });
30 |
--------------------------------------------------------------------------------
/modules/25-types/30-intersection-types/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/25-types/30-intersection-types/assets/one_hundred_order.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hexlet-basics/exercises-typescript/bf724cc97528629332c3b3c493b164ccea548c64/modules/25-types/30-intersection-types/assets/one_hundred_order.png
--------------------------------------------------------------------------------
/modules/25-types/30-intersection-types/index.ts:
--------------------------------------------------------------------------------
1 | enum Permission {
2 | READ,
3 | WRITE,
4 | DELETE,
5 | }
6 |
7 | type User = {
8 | login: string,
9 | };
10 |
11 | type AdminPermission = {
12 | permission: Permission,
13 | };
14 |
15 | // BEGIN
16 | type Admin = User & AdminPermission;
17 |
18 | const addAdmin = (user: User): Admin => ({ ...user, permission: Permission.READ });
19 | // END
20 |
21 | export { User, Admin, Permission };
22 | export default addAdmin;
23 |
--------------------------------------------------------------------------------
/modules/25-types/30-intersection-types/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 | Реализуйте тип `Admin`, который является пересечением типов `AdminPermission` и `User`. Реализуйте функцию `addAdmin()`, которая принимает значение с типом `User` и возвращает значение с типом `Admin`. В качестве значения для свойства `permission` должно быть значение `Permission.READ`.
2 |
3 | ```typescript
4 | const user: User = { login: 'login1' };
5 | const admin = addAdmin(user); // { login: 'login1', permission: Permission.READ }
6 | ```
7 |
--------------------------------------------------------------------------------
/modules/25-types/30-intersection-types/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Вместе с объединением важной операцией в теории множеств является пересечение. Для разработчиков, которые привыкли к динамике JavaScript, эта операция может показаться менее значимой. Но без нее не обойтись, например, при описании типа слияния объектов.
3 |
4 | Пересечение указывается с помощью символа `&`, по обе стороны от которого располагаются типы.
5 |
6 | Определим тип объекта со статусом заказа, а затем более строгий тип с точной ценой:
7 |
8 | ```typescript
9 | type Order = {
10 | status: 'Created',
11 | }
12 |
13 | type OneHundredOrder = Order & {
14 | cost: 100
15 | }
16 |
17 | const myOrder: OneHundredOrder = {
18 | status: 'Created',
19 | cost: 100
20 | }
21 | ```
22 |
23 | Из пересечения объектных типов с полями `status` **И** `cost` мы получили тип `OneHundredOrder`, который содержит оба этих поля.
24 |
25 | Тип – это множество значений. Когда мы задаем пересечение типов, мы получаем новый тип, который содержит значения, подходящие под ограничения обоих типов.
26 |
27 | 
28 |
29 | Если мы объявим переменную `const StringAndNumber: string & number`, то ей нужно будет присвоить значение, которое одновременно принадлежит множествам `string` и `number`. То есть оно является одновременно и строкой, и числом. Такого значения не существует, поэтому `StringAndNumber` будет иметь тип `never`. `never` соответствует пустому множеству — тип, у которого нет ни одного значения.
30 |
--------------------------------------------------------------------------------
/modules/25-types/30-intersection-types/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Пересечение (Intersections Types)
3 |
--------------------------------------------------------------------------------
/modules/25-types/30-intersection-types/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import addAdmin, { User, Permission } from './index';
3 |
4 | test('addAdmin', () => {
5 | const user: User = {
6 | login: 'login1',
7 | };
8 |
9 | const admin = addAdmin(user);
10 | expect(admin).toEqual({ ...user, permission: Permission.READ });
11 | });
12 |
--------------------------------------------------------------------------------
/modules/25-types/40-assignability/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/25-types/40-assignability/index.ts:
--------------------------------------------------------------------------------
1 | type Form = {
2 | age: {
3 | value: number,
4 | validator: (val: number) => boolean
5 | },
6 | name: {
7 | value: string,
8 | validator: (val: string) => boolean
9 | }
10 | };
11 |
12 | const form: Form = {
13 | // BEGIN
14 | name: {
15 | value: 'Kirill',
16 | validator: (val: string) => val.length > 1,
17 | },
18 | age: {
19 | value: 17,
20 | validator: (val: number) => val > 18,
21 | },
22 | // END
23 | };
24 |
25 | export default form;
26 |
--------------------------------------------------------------------------------
/modules/25-types/40-assignability/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 | Реализуйте объект по описанному типу `Form`. Поле `name.value` должно проходить валидацию, а поле `age` — нет.
2 |
3 | ```typescript
4 | console.log(form.name.validator(form.name.value)); // true
5 | console.log(form.age.validator(form.age.value)); // false
6 | ```
7 |
--------------------------------------------------------------------------------
/modules/25-types/40-assignability/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке мы разберем присвоение одного значения другому. Это одна из базовых операций с переменными в большинстве языков. Самая частая ошибка, с которой вы столкнетесь в TypeScript будет выглядеть как `Error: Type X is not assignable to type Y.`. Такой код не удается скомпилировать, поэтому нужно разобраться, как это исправить.
3 |
4 | Присвоение одного значения другому и передача аргументом в функцию называют присваиваемостью(**Assignability**):
5 |
6 | ```typescript
7 | let x: number;
8 | const y: number = 10;
9 | x = y;
10 |
11 | function len(str: string): number {
12 | return str.length;
13 | }
14 | len(false); // Error!
15 | ```
16 |
17 | При присвоении `x = y;` и передачи аргумента `f(false);` сначала проверяется, может ли переменная содержать передаваемый тип — совместим ли тип `x` с типом `y`.
18 |
19 | Если думать о типах, как о множествах значений, то присваиваемость — это проверка, что множество значений `x` входит в множество значений `y`. Например, литеральный тип `'one'` входит в множество значений `string`, а множество значений `number` — нет.
20 |
21 | Переменная типа `x` присваивается переменной типа `y`, если множество значений `x` входит в множество значений `y`. Или другими словами — если множество значений `x` является подмножеством множества значений `y`.
22 |
23 | Так что когда вы в следующий раз столкнетесь с ошибкой `Type X is not assignable to type Y.`, не стоит сразу приводить всё к самому общему типу через `as any`. Ведь, в таком случае вы полностью отключаете проверку типов для этой переменной.
24 |
25 | Сначала нужно разобраться, что ожидается на вход, и что возвращает функция. И только после этого можно модифицировать собственные типы: расширять допустимые типы, например, с помощью объединения и только в крайних случаях использовать хак с `any`.
26 |
27 | Чтобы понимать, что к чему в TypeScript можно присвоить, нужно смотреть на код с точки зрения иерархии типов и структурной типизации. Этому посвящены следующие уроки в этом курсе.
28 |
--------------------------------------------------------------------------------
/modules/25-types/40-assignability/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Присвоение значения
3 | tips:
4 | - >
5 | [Assignability в старой
6 | документации](https://github.com/microsoft/TypeScript-New-Handbook/blob/master/reference/Assignability.md)
7 | - >
8 | [Таблица assignability в официальной
9 | документации](https://www.typescriptlang.org/docs/handbook/type-compatibility.html#any-unknown-object-void-undefined-null-and-never-assignability)
10 | definitions:
11 | - name: Совместимость типов (Types Compatibility)
12 | description: >
13 | совокупность правил, на основе которых при анализе типа данных принимается
14 | решение о возможности заменить один тип данных другим таким образом, чтобы
15 | замена не нарушила выполнение программы.
16 |
--------------------------------------------------------------------------------
/modules/25-types/40-assignability/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import form from './index';
4 |
5 | test('form', () => {
6 | const nameValidator = form.name.validator;
7 | const ageValidator = form.age.validator;
8 |
9 | expect(nameValidator(form.name.value)).toBe(true);
10 | expect(ageValidator(form.age.value)).toBe(false);
11 |
12 | expectTypeOf(nameValidator).parameters.toMatchTypeOf<[string]>();
13 | expectTypeOf(ageValidator).parameters.toMatchTypeOf<[number]>();
14 | });
15 |
--------------------------------------------------------------------------------
/modules/25-types/50-type-hierarcy/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/25-types/50-type-hierarcy/assets/hierarcy_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hexlet-basics/exercises-typescript/bf724cc97528629332c3b3c493b164ccea548c64/modules/25-types/50-type-hierarcy/assets/hierarcy_circle.png
--------------------------------------------------------------------------------
/modules/25-types/50-type-hierarcy/index.ts:
--------------------------------------------------------------------------------
1 | type User = {
2 | id: number,
3 | name: string,
4 | age: number,
5 | };
6 |
7 | type Friends = [number, number];
8 |
9 | export type UserResponse = {
10 | users: User[],
11 | friends: Friends[]
12 | };
13 |
14 | // BEGIN
15 | const defaultUser = { id: 0, name: '', age: 0 };
16 | const getUserFriends = (userResponseJSON: string, userId: number): User[] => {
17 | const userResponse = JSON.parse(userResponseJSON) as UserResponse;
18 |
19 | return userResponse.friends
20 | .map(([ownerId, friendId]: Friends): User => {
21 | if (!(userId === ownerId || userId === friendId)) return defaultUser;
22 | const searchId = (ownerId === userId) ? friendId : ownerId;
23 | const friend: User | undefined = userResponse.users.find(({ id }) => id === searchId);
24 |
25 | return friend === undefined ? defaultUser : friend;
26 | })
27 | .filter((user: User) => user.id > 0);
28 | };
29 | // END
30 |
31 | export default getUserFriends;
32 |
--------------------------------------------------------------------------------
/modules/25-types/50-type-hierarcy/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `getUserFriends(userResponseJSON, userId)`, которая принимает на вход JSON-строку и `userId` пользователя. JSON содержит массив пользователей `users` и с массив друзей `friends` в виде пар `[userId, userId]`. Функция возвращает список друзей пользователя по переданному `userId``.
3 |
4 | Если пользователь с указанным id не найден, то функция должна вернуть пустой массив.
5 |
6 | ```typescript
7 | const userJson = JSON.stringify({
8 | users: [
9 | { id: 1, name: 'John', age: 20 },
10 | { id: 2, name: 'Mary', age: 21 },
11 | ],
12 | friends: [
13 | [1, 2],
14 | ],
15 | });
16 |
17 | getUserFriends(userJson, 1); // [{ id: 2, name: 'Mary', age: 21 }]
18 | getUserFriends(userJson, 2); // [{ id: 1, name: 'John', age: 20 }]
19 | getUserFriends(userJson, 3); // []
20 | ```
21 |
--------------------------------------------------------------------------------
/modules/25-types/50-type-hierarcy/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Иерархия типов
3 | tips:
4 | - >
5 | [Как устроена система типов в
6 | TypeScript](https://ru.hexlet.io/blog/posts/sistema-tipov-v-typescript)
7 | - >
8 | [Type
9 | Assertion](https://basarat.gitbook.io/typescript/type-system/type-assertion)
10 |
--------------------------------------------------------------------------------
/modules/25-types/50-type-hierarcy/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import getUserFriends, { UserResponse } from './index';
3 |
4 | test('getUserFriends', () => {
5 | const userJson = JSON.stringify({
6 | users: [
7 | { id: 1, name: 'John', age: 20 },
8 | { id: 2, name: 'Mary', age: 21 },
9 | { id: 3, name: 'Peter', age: 22 },
10 | { id: 4, name: 'Ann', age: 23 },
11 | ],
12 | friends: [
13 | [1, 2],
14 | [1, 3],
15 | [3, 2],
16 | ],
17 | });
18 |
19 | const friends = getUserFriends(userJson, 10);
20 | expect(friends).toEqual([]);
21 |
22 | const friends1 = getUserFriends(userJson, 1);
23 | expect(friends1).toEqual([
24 | { id: 2, name: 'Mary', age: 21 },
25 | { id: 3, name: 'Peter', age: 22 },
26 | ]);
27 |
28 | const friends2 = getUserFriends(userJson, 2);
29 | expect(friends2).toEqual([
30 | { id: 1, name: 'John', age: 20 },
31 | { id: 3, name: 'Peter', age: 22 },
32 | ]);
33 |
34 | const friends3 = getUserFriends(userJson, 3);
35 | expect(friends3).toEqual([
36 | { id: 1, name: 'John', age: 20 },
37 | { id: 2, name: 'Mary', age: 21 },
38 | ]);
39 | });
40 |
--------------------------------------------------------------------------------
/modules/25-types/60-structural-typing/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/25-types/60-structural-typing/assets/object_intersection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hexlet-basics/exercises-typescript/bf724cc97528629332c3b3c493b164ccea548c64/modules/25-types/60-structural-typing/assets/object_intersection.png
--------------------------------------------------------------------------------
/modules/25-types/60-structural-typing/assets/structual_object.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hexlet-basics/exercises-typescript/bf724cc97528629332c3b3c493b164ccea548c64/modules/25-types/60-structural-typing/assets/structual_object.png
--------------------------------------------------------------------------------
/modules/25-types/60-structural-typing/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | enum LoadingStatus {
3 | Loading = 'Loading',
4 | Success = 'Success',
5 | Error = 'Error',
6 | }
7 |
8 | type DataState =
9 | | { status: LoadingStatus.Loading }
10 | | { status: LoadingStatus.Success; data: number }
11 | | { status: LoadingStatus.Error; error: Error };
12 |
13 | const handleData = (dataState: DataState): string => {
14 | switch (dataState.status) {
15 | case LoadingStatus.Loading:
16 | return 'loading...';
17 | case LoadingStatus.Success:
18 | return String(dataState.data);
19 | case LoadingStatus.Error:
20 | return dataState.error.message;
21 | default:
22 | return 'unknown';
23 | }
24 | };
25 | // END
26 |
27 | export { DataState, LoadingStatus };
28 | export default handleData;
29 |
--------------------------------------------------------------------------------
/modules/25-types/60-structural-typing/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Опишите тип состояния `DataState` и перечисление `LoadingStatus`. Затем реализуйте функцию `handleData()`, которая принимает на вход `DataState` и возвращает строку в зависимости от состояния: `loading...` при `LoadingStatus.loading`, `error` при `LoadingStatus.error`, строку из числового поля `data` при `LoadingStatus.success`. Если статус не входит в перечисление, функция возвращает `unknown`.
3 |
4 | ```typescript
5 | const loading: DataState = { status: LoadingStatus.Loading };
6 | console.log(handleData(loading)); // loading...
7 |
8 | const error: DataState = { status: LoadingStatus.Error, error: new Error('error') };
9 | console.log(handleData(error)); // error
10 |
11 | const success: DataState = { status: LoadingStatus.Success, data: 42 };
12 | console.log(handleData(success)); // 42
13 | ```
14 |
--------------------------------------------------------------------------------
/modules/25-types/60-structural-typing/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Структурная типизация
3 | tips:
4 | - >
5 | [Structural typing в официальной
6 | документации](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#structural-typing)
7 | - >
8 | [Как получить номинативную типизацию в
9 | TS](https://spin.atomicobject.com/2018/01/15/typescript-flexible-nominal-typing/)
10 | - |
11 | [Тип-произведение](https://en.wikipedia.org/wiki/Product_type)
12 | - |
13 | [Тип-сумма](https://en.wikipedia.org/wiki/Tagged_union)
14 | definitions:
15 | - name: Структурная типизация
16 | description: >
17 | принцип, который определяет совместимость типов на основе их описания
18 | (структуры). Переменная типа `A` также может использоваться в том месте,
19 | где ожидается тип `B`, если обладает той же или более широкой структурой.
20 |
--------------------------------------------------------------------------------
/modules/25-types/60-structural-typing/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import handleData, { DataState, LoadingStatus } from './index';
3 |
4 | test('handleData', () => {
5 | const loading: DataState = { status: LoadingStatus.Loading };
6 | expect(handleData(loading)).toBe('loading...');
7 |
8 | const success: DataState = { status: LoadingStatus.Success, data: 42 };
9 | expect(handleData(success)).toBe('42');
10 |
11 | const error: DataState = { status: LoadingStatus.Error, error: new Error('error') };
12 | expect(handleData(error)).toBe('error');
13 |
14 | const unknown = { status: 'unknown' };
15 | // @ts-expect-error type '{ status: 'unknown' }' is not assignable to type 'DataState'.
16 | expect(handleData(unknown)).toBe('unknown');
17 | });
18 |
--------------------------------------------------------------------------------
/modules/25-types/70-variability/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/25-types/70-variability/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unused-vars */
2 | // BEGIN
3 | type Transaction = {
4 | apply: (amount: number) => number;
5 | };
6 |
7 | type Wallet = {
8 | transactions: Transaction[];
9 | balance: number;
10 | };
11 |
12 | const applyTransactions = (wallet: Wallet) => {
13 | try {
14 | let { balance } = wallet;
15 |
16 | wallet.transactions.forEach((transaction) => {
17 | balance = transaction.apply(balance);
18 | });
19 | return balance;
20 | } catch (e) {
21 | return wallet.balance;
22 | }
23 | };
24 | // END
25 |
26 | export type { Transaction, Wallet };
27 | export default applyTransactions;
28 |
--------------------------------------------------------------------------------
/modules/25-types/70-variability/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `applyTransactions(wallet)` и типы `Transaction`, `Wallet`. `Wallet` содержит список транзакций в виде массива элементов типа `Transaction` и числовой баланс. `Transaction` содержит метод `apply`, который принимает баланс и возвращает новый баланс.
3 |
4 | Функция `applyTransactions(wallet)` должна принимать аргумент типа `Wallet` и возвращать баланс после применения всего списка транзакций. В случае ошибки в одной из транзакций должно вернуться изначальный баланс, и не продолжать применять транзакции.
5 |
6 | ```typescript
7 | const wallet: Wallet = {
8 | transactions: [
9 | {
10 | apply: (amount) => amount + 1,
11 | },
12 | ],
13 | balance: 0
14 | }
15 |
16 | console.log(applyTransactions(wallet)) // 1
17 | ```
18 |
--------------------------------------------------------------------------------
/modules/25-types/70-variability/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Ковариантность и контравариантность
3 | tips:
4 | - >
5 | [Ковариантность и
6 | контравариантность](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science))
7 | - |
8 | [Теория программирования: Вариантность](https://habr.com/ru/post/477448/)
9 | definitions:
10 | - name: Ковариантность
11 | description: >
12 | свойство типов, когда типы, составляющие объединение, являются
13 | подмножествами друг друга.
14 | - name: Контравариантность
15 | description: >
16 | свойство типов, когда типы, составляющие объединение, являются
17 | надмножествами друг друга.
18 |
--------------------------------------------------------------------------------
/modules/25-types/70-variability/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 | import applyTransactions, { Wallet } from './index';
3 |
4 | test('applyTransactions', () => {
5 | const wallet: Wallet = {
6 | balance: 100,
7 | transactions: [
8 | {
9 | apply: (amount: number) => amount + 10,
10 | },
11 | {
12 | apply: (amount: number) => amount - 20,
13 | },
14 | {
15 | apply: (amount: number) => amount + 30,
16 | },
17 | ],
18 | };
19 |
20 | expect(applyTransactions(wallet)).toBe(120);
21 | expect(wallet.balance).toBe(100);
22 |
23 | const wallet2: Wallet = {
24 | balance: 10,
25 | transactions: [
26 | {
27 | apply: (amount: number) => amount + 10,
28 | },
29 | {
30 | apply: () => {
31 | throw new Error('Error');
32 | },
33 | },
34 | {
35 | apply: (amount: number) => amount + 30,
36 | },
37 | ],
38 | };
39 |
40 | expect(applyTransactions(wallet2)).toBe(10);
41 |
42 | expectTypeOf(wallet2.transactions[0].apply).returns.toMatchTypeOf();
43 | });
44 |
--------------------------------------------------------------------------------
/modules/25-types/description.ru.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Подробнее о типах
3 | description: |
4 | Комбинирование типов с помощью пересечения и объединения, их особенности. В этом модуле мы рассмотрим основные концепции, которые позволяют глубже понять, как работает типизация в TypeScript, и не бояться ошибок, выдаваемых компилятором.
5 |
--------------------------------------------------------------------------------
/modules/30-classes/10-class-fields/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/30-classes/10-class-fields/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | type CustomFileOptions = {
3 | name: string;
4 | size: number;
5 | };
6 |
7 | class CustomFile {
8 | name: string;
9 |
10 | size: number;
11 |
12 | constructor({ name, size }: CustomFileOptions) {
13 | this.name = name;
14 | this.size = size;
15 | }
16 |
17 | toString() {
18 | return `${this.name} (${this.size} bytes)`;
19 | }
20 | }
21 | // END
22 |
23 | export default CustomFile;
24 |
--------------------------------------------------------------------------------
/modules/30-classes/10-class-fields/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте класс `CustomFile`, в конструктор которого передается объект с полями: `name` — именем файла, и `size` — размером в байтах. Внутри класса определите метод `toString()`, который должен вернуть форматированную строку в формате ` ( bytes)`.
3 |
4 | ```typescript
5 | const file = new CustomFile({ name: 'open-world.jpeg', size: 1000 });
6 | console.log(file.toString()); // open-world.jpeg (1000 bytes)
7 | ```
8 |
--------------------------------------------------------------------------------
/modules/30-classes/10-class-fields/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Типизация классов в TypeScript добавляет новый синтаксис определения классов, которого нет в JavaScript. Этот синтаксис существует только на уровне проверки типов. В результирующем коде он вырезается или заменяется. В этом уроке мы изучим реализацию таких классов.
3 |
4 | ## Определение полей внутри класса
5 |
6 | Начнем с примера. Посмотрите на этот класс:
7 |
8 | ```typescript
9 | class Point {
10 | x: number;
11 | y: number;
12 |
13 | constructor(x: number, y: number) {
14 | this.x = x;
15 | this.y = y;
16 | }
17 | }
18 |
19 | const p = new Point(10, 20);
20 | console.log(p); // { x: 10, y: 20 }
21 | ```
22 |
23 | Здесь мы видим новый синтаксис, который описывает поля класса: `x` и `y`. Их описание обязательно, так как классы — это функции-конструкторы, а функции в TypeScript являются объектами.
24 |
25 | Перед тем как двигаться дальше, разберемся с понятием «поле класса».
26 |
27 | ## Поле класса
28 |
29 | Обычно в JavaScript все называют свойствами. А слово «поле» используют как синоним те, кто пришел из других языков. Но это не одно и тоже.
30 |
31 | Внутри класса мы определяем **поля** — это данные самого класса. А **свойство** — это то, с помощью чего мы взаимодействуем с объектом.
32 |
33 | Часто свойства и поля совпадают, но так происходит не всегда. Например, свойство может быть доступно только для чтения - геттером, который берет информацию из поля. Или свойство может быть доступно только для записи - сеттером, который устанавливает новое значение поля:
34 |
35 | ```typescript
36 | class Point {
37 | x: number;
38 |
39 | y: number;
40 |
41 | // Возвращаемый тип не указывается, так как это конструктор
42 | constructor(x: number, y: number) {
43 | this.x = x;
44 | this.y = y;
45 | }
46 |
47 | get inspect(): string {
48 | return `(${this.x}, ${this.y})`
49 | }
50 | }
51 |
52 | const p = new Point(2, 5);
53 | // Свойство есть, а поля такого нет
54 | console.log(p.inspect); // (2, 5)
55 | ```
56 |
57 | В примере мы определили геттер `inspect`, который возвращает строку с координатами точки. Это свойство доступно только для чтения, так как мы не определили сеттер.
58 |
59 | Поля могут быть инициализированы сразу при определении класса. Это удобно, когда конструктор не нужен или данные не зависят от его вызова — задаются статически внутри:
60 |
61 | ```typescript
62 | class Point {
63 | x = 0;
64 |
65 | y = 0;
66 | }
67 |
68 | const p = new Point();
69 | console.log(p); // { x: 0, y: 0 }
70 | ```
71 |
72 | Как и в случае обычных переменных, тип полей выводится автоматически во время их инициализации, поэтому указывать явный тип не обязательно.
73 |
--------------------------------------------------------------------------------
/modules/30-classes/10-class-fields/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Классы
3 | tips:
4 | - >
5 | [Официальная
6 | документация](https://www.typescriptlang.org/docs/handbook/2/classes.html)
7 |
--------------------------------------------------------------------------------
/modules/30-classes/10-class-fields/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import CustomFile from './index';
3 |
4 | test('CustomFile', () => {
5 | const file = new CustomFile({ name: 'foo.txt', size: 4 });
6 |
7 | expect(file.name).toBe('foo.txt');
8 | expect(file.size).toBe(4);
9 | expect(file.toString()).toBe('foo.txt (4 bytes)');
10 | });
11 |
--------------------------------------------------------------------------------
/modules/30-classes/15-class-as-types/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/30-classes/15-class-as-types/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | type OptionName = string;
3 | type OptionSize = number;
4 | type CustomFileOptions = { name: OptionName, size: OptionSize };
5 |
6 | class CustomFile {
7 | name: OptionName;
8 |
9 | size: OptionSize;
10 |
11 | private isCopy: boolean;
12 |
13 | constructor(options: CustomFileOptions) {
14 | this.name = options.name;
15 | this.size = options.size;
16 | this.isCopy = (options instanceof CustomFile);
17 | }
18 |
19 | toString(): string {
20 | const copyString = this.isCopy ? '(copy) ' : '';
21 | return `${copyString}${this.name} (${this.size} bytes)`;
22 | }
23 | }
24 | // END
25 |
26 | export default CustomFile;
27 |
--------------------------------------------------------------------------------
/modules/30-classes/15-class-as-types/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте класс `CustomFile`, в конструктор которого передается имя файла и размер в байтах или другой файл. Внутри класса определите метод `toString()`, который должен вернуть форматированную строку в формате `(copy) ( bytes)`. `(copy)` должно выводиться только в том случае, если файл является копией другого файла.
3 |
4 | ```typescript
5 | const file = new CustomFile({ name: 'open-world.jpeg', size: 1000 });
6 | console.log(file.toString()); // open-world.jpeg (1000 bytes)
7 |
8 | const file2 = new CustomFile(file);
9 | console.log(file2.toString()); // (copy) open-world.jpeg (1000 bytes)
10 |
11 | const file3 = new CustomFile(file2);
12 | console.log(file2.toString()); // (copy) open-world.jpeg (1000 bytes)
13 | ```
14 |
--------------------------------------------------------------------------------
/modules/30-classes/15-class-as-types/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Классы в TypeScript являются одновременно значением и типом данных. Второе нам особенно важно в контексте типизации функций и методов, что мы и изучим в этом уроке.
3 |
4 | Рассмотрим следующий пример:
5 |
6 | ```typescript
7 | class Point {
8 | x: number;
9 |
10 | y: number;
11 |
12 | constructor(x: number, y: number) {
13 | this.x = x;
14 |
15 | this.y = y;
16 | }
17 | }
18 |
19 | function isEqual(p1: Point, p2: Point): boolean {
20 | return p1.x === p2.x && p1.y === p2.y;
21 | }
22 | ```
23 |
24 | Здесь функция `isEqual()` принимает два аргумента типа `Point`. И хоть мы используем в качестве типа класс `Point`, но передавать в функции мы можем любые объекты с полями `x` и `y`:
25 |
26 | ```typescript
27 | isEqual({ x: 1, y: 2 }, { x: 1, y: 2 }); // OK
28 | ```
29 |
30 | Такое поведение обусловлено структурной типизацией. При сравнении типов TypeScript сравнивает их структуру, а не имена. На практике это упрощает работу с внешними библиотеками и заглушками для тестирования.
31 |
32 | TypeScript будет явно требовать экземпляр класса, если у него есть приватные поля:
33 |
34 | ```typescript
35 | class Point {
36 | private x: number;
37 |
38 | private y: number;
39 |
40 | constructor(x: number, y: number) {
41 | this.x = x;
42 | this.y = y;
43 | }
44 |
45 | isEqual(p2: Point): boolean {
46 | return this.x === p2.x && this.y === p2.y;
47 | }
48 | }
49 |
50 | const point = new Point(1, 2);
51 | point.isEqual(new Point(10, 1)); // OK
52 | point.isEqual({ x: 1, y: 2}); // Error: Argument of type '{ x: number; y: number; }' is not assignable to parameter of type 'Point'.
53 | ````
54 |
55 |
56 |
--------------------------------------------------------------------------------
/modules/30-classes/15-class-as-types/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Классы как типы
3 | tips:
4 | - >
5 | [Перегрузка
6 | конструктора](https://www.typescriptlang.org/docs/handbook/2/classes.html#constructors)
7 |
--------------------------------------------------------------------------------
/modules/30-classes/15-class-as-types/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import CustomFile from './index';
3 |
4 | test('CustomFile', () => {
5 | const file = new CustomFile({ name: 'foo.txt', size: 4000 });
6 |
7 | expect(file.name).toBe('foo.txt');
8 | expect(file.size).toBe(4000);
9 | expect(file.toString()).toBe('foo.txt (4000 bytes)');
10 |
11 | const file2 = new CustomFile(file);
12 | expect(file2.toString()).toBe('(copy) foo.txt (4000 bytes)');
13 | });
14 |
--------------------------------------------------------------------------------
/modules/30-classes/20-members-visibility/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/30-classes/20-members-visibility/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | type CustomFileOptions = {
4 | name: string;
5 | size: number;
6 | };
7 |
8 | class CustomFile {
9 | private name: string;
10 |
11 | private size: number;
12 |
13 | constructor(options: CustomFileOptions) {
14 | this.name = options.name;
15 | this.size = options.size;
16 | }
17 |
18 | protected toString() {
19 | return `${this.name} (${this.size} bytes)`;
20 | }
21 | }
22 |
23 | // BEGIN
24 | class ImageCustomFile extends CustomFile {
25 | private width: number;
26 |
27 | private height: number;
28 |
29 | constructor(options: CustomFileOptions & { width: number; height: number }) {
30 | super(options);
31 | this.width = options.width;
32 | this.height = options.height;
33 | }
34 |
35 | toString() {
36 | return `${super.toString()} ${this.width}x${this.height}`;
37 | }
38 | }
39 | // END
40 |
41 | export default ImageCustomFile;
42 |
--------------------------------------------------------------------------------
/modules/30-classes/20-members-visibility/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте класс `ImageCustomFile`, который расширяет (`extends`) класс `CustomFile` дополнительными приватными полями: `width`, `height`. Также переопределите метод `toString()`. Теперь он должен дополнительно выводить `x`.
3 |
4 | ```typescript
5 | const imageCustomFile = new ImageCustomFile({
6 | name: 'image.png',
7 | size: 100,
8 | width: 200,
9 | height: 300,
10 | });
11 | console.log(imageCustomFile.toString()); // image.png (100 bytes) 200x300
12 | ```
13 |
14 | Чтобы вызвать метод родительского класса, используйте `super.toString()`.
15 |
--------------------------------------------------------------------------------
/modules/30-classes/20-members-visibility/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В некоторых случаях свойства и методы в классе создаются только для внутреннего использования. Разработчики не хотят давать возможность вызывать их снаружи, иначе их случайно могут начать использовать, что не планировалось.
3 |
4 | В языках с классами принято разделять свойства на публичные, приватные и защищенные. Первые доступны для всех, вторые могут использоваться только внутри класса, а третьи — внутри класса и в его наследниках. В этом уроке разберем каждый из этих видов.
5 |
6 | ## Публичные свойства
7 |
8 | По умолчанию в TypeScript все свойства публичные. Это можно обозначить явно с помощью ключевого слова `public`:
9 |
10 | ```typescript
11 | class Point {
12 | public x: number;
13 |
14 | public y: number;
15 |
16 | constructor(x: number, y: number) {
17 | this.x = x;
18 | this.y = y;
19 | }
20 |
21 | public someMethod() {
22 | // some logic
23 | }
24 | }
25 | ```
26 |
27 |
28 |
29 | ## Приватные свойства
30 |
31 | Также свойства можно сделать приватными. Тогда пропадет возможность обращаться к ним снаружи напрямую:
32 |
33 | ```typescript
34 | class Point {
35 | private x: number;
36 |
37 | private y: number;
38 |
39 | constructor(x: number, y: number) {
40 | this.x = x;
41 | this.y = y;
42 | }
43 | }
44 |
45 | const p = new Point(10, 8);
46 | p.x; // Property 'x' is private and only accessible within class 'Point'.
47 | p.y; // Property 'y' is private and only accessible within class 'Point'.
48 | ```
49 |
50 |
51 |
52 | ## Защищенные свойства
53 |
54 | И наконец, свойства можно сделать защищенными. Это значит, что они доступны внутри класса и в наследниках:
55 |
56 | ```typescript
57 | class Point {
58 | protected x: number;
59 |
60 | protected y: number;
61 |
62 | constructor(x: number, y: number) {
63 | this.x = x;
64 | this.y = y;
65 | }
66 | }
67 |
68 | class Point3D extends Point {
69 | protected z: number;
70 |
71 | constructor(x: number, y: number, z: number) {
72 | super(x, y);
73 | this.z = z;
74 | }
75 |
76 | public getCoordinates() {
77 | return [this.x, this.y, this.z]; // OK
78 | }
79 | }
80 |
81 | const p = new Point3D(10, 8, 5);
82 | p.x; // Property 'x' is protected and only accessible within class 'Point' and its subclasses.
83 | p.y; // Property 'y' is protected and only accessible within class 'Point' and its subclasses.
84 | p.z; // Property 'z' is protected and only accessible within class 'Point3D' and its subclasses.
85 | ```
86 |
87 |
88 |
--------------------------------------------------------------------------------
/modules/30-classes/20-members-visibility/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Защита свойств и методов
3 |
--------------------------------------------------------------------------------
/modules/30-classes/20-members-visibility/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import ImageCustomFile from './index';
3 |
4 | test('ImageCustomFile', () => {
5 | const imageCustomFile = new ImageCustomFile({
6 | name: 'image.png',
7 | size: 100,
8 | width: 200,
9 | height: 300,
10 | });
11 |
12 | expect(imageCustomFile.toString()).toBe('image.png (100 bytes) 200x300');
13 |
14 | const imageCustomFile2 = new ImageCustomFile({
15 | name: 'image2.png',
16 | size: 400,
17 | width: 500,
18 | height: 600,
19 | });
20 |
21 | expect(imageCustomFile2.toString()).toBe('image2.png (400 bytes) 500x600');
22 | // @ts-expect-error - private property
23 | expect(imageCustomFile2.name).toBe('image2.png');
24 | // @ts-expect-error - private property
25 | expect(imageCustomFile2.size).toBe(400);
26 | // @ts-expect-error - private property
27 | expect(imageCustomFile2.width).toBe(500);
28 | // @ts-expect-error - private property
29 | expect(imageCustomFile2.height).toBe(600);
30 | });
31 |
--------------------------------------------------------------------------------
/modules/30-classes/23-parameter-properties/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/30-classes/23-parameter-properties/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | class CustomFile {
3 | constructor(private name: string, private size: number) { }
4 |
5 | toString() {
6 | return `${this.name} (${this.size} bytes)`;
7 | }
8 | }
9 | // END
10 |
11 | export default CustomFile;
12 |
--------------------------------------------------------------------------------
/modules/30-classes/23-parameter-properties/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте класс `CustomFile`, в конструктор которого передается имя файла и размер в байтах. Внутри класса определите метод `toString()`, который должен вернуть форматированную строку в формате ` ( bytes)`. Используйте свойства параметров для заполнения свойств класса.
3 |
4 | ```typescript
5 | const file = new CustomFile('open-world.jpeg', 1000);
6 | console.log(file); // open-world.jpeg (1000 bytes)
7 | ```
8 |
--------------------------------------------------------------------------------
/modules/30-classes/23-parameter-properties/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Заполнение свойств из параметров конструктора частая задача в работе с классами. Поэтому в TypeScript добавили специальный синтаксис, который позволяет делать это автоматически:
3 |
4 | ```typescript
5 | class SomeClass {
6 | constructor(public one: number, private two: string) {}
7 |
8 | get three(): string {
9 | return `${this.one} ${this.two}`;
10 | }
11 | }
12 | ```
13 |
14 | Этот код делает то же самое, что и этот:
15 |
16 | ```typescript
17 | class SomeClass {
18 | public one: number;
19 |
20 | private two: string;
21 |
22 | constructor(one: number, two: string) {
23 | this.one = one;
24 | this.two = two;
25 | }
26 |
27 | get three(): string {
28 | return `${this.one} ${this.two}`;
29 | }
30 | }
31 | ```
32 |
33 |
34 |
35 | Новый синтаксис позволяет не дублировать код заполнения свойств из параметров и делает его более лаконичным. Если в конструкторе есть какая-то логика, то свойства все равно нужно заполнять вручную.
36 |
--------------------------------------------------------------------------------
/modules/30-classes/23-parameter-properties/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Свойства параметров
3 | tips:
4 | - >
5 | [Перегрузка
6 | конструктора](https://www.typescriptlang.org/docs/handbook/2/classes.html#constructors)
7 | - >
8 | [Typescript Constructor
9 | Shorthand](https://dev.to/satansdeer/typescript-constructor-shorthand-3ibd)
10 |
--------------------------------------------------------------------------------
/modules/30-classes/23-parameter-properties/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import CustomFile from './index';
3 |
4 | test('CustomFile', () => {
5 | const file = new CustomFile('foo.txt', 4);
6 |
7 | expect(file.toString()).toBe('foo.txt (4 bytes)');
8 |
9 | const file2 = new CustomFile('bar.txt', 8);
10 | expect(file2.toString()).toBe('bar.txt (8 bytes)');
11 | });
12 |
--------------------------------------------------------------------------------
/modules/30-classes/30-class-extending/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/30-classes/30-class-extending/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | // BEGIN
4 | class HttpError extends Error {
5 | constructor(public status: number, message: string) {
6 | super(message);
7 | }
8 | }
9 |
10 | class NotFoundError extends HttpError {
11 | constructor(message: string) {
12 | super(404, message);
13 | }
14 | }
15 |
16 | class UnauthorizedError extends HttpError {
17 | constructor(message: string) {
18 | super(401, message);
19 | }
20 | }
21 |
22 | class ForbiddenError extends HttpError {
23 | constructor(message: string) {
24 | super(403, message);
25 | }
26 | }
27 | // END
28 |
29 | export {
30 | HttpError,
31 | NotFoundError,
32 | UnauthorizedError,
33 | ForbiddenError,
34 | };
35 |
--------------------------------------------------------------------------------
/modules/30-classes/30-class-extending/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте класс `HttpError`, который должен наследоваться от встроенного класса `Error` и принимать первым параметром код ошибки, а вторым — `message`. Также реализуйте классы `NotFoundError`, `UnauthorizedError`, `ForbiddenError`. Каждый из них должен наследоваться от класса `HttpError` и иметь свойство `status`, которое равно коду ошибки и `message` — сообщение, передающееся в базовый класс. Коды ошибок: `404`, `401`, `403`.
3 |
4 | ```typescript
5 | import { NotFoundError } from './errors';
6 |
7 | const error = new NotFoundError('Not Found');
8 | console.log(error.status); // 404
9 | console.log(error.message); // Not Found
10 | ```
11 |
--------------------------------------------------------------------------------
/modules/30-classes/30-class-extending/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Наследование
3 | tips:
4 | - >
5 | [Наследование
6 | классов](https://www.typescriptlang.org/docs/handbook/2/classes.html#extends-clauses)
7 | - |
8 | [Вариантность](https://habr.com/ru/post/477448/)
9 | - |
10 | [Mixins](https://basarat.gitbook.io/typescript/type-system/mixins)
11 |
--------------------------------------------------------------------------------
/modules/30-classes/30-class-extending/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import {
3 | ForbiddenError, HttpError, NotFoundError, UnauthorizedError,
4 | } from './index';
5 |
6 | test('HttpError', () => {
7 | const error = new HttpError(500, 'Internal Server Error');
8 | expect(error.status).toBe(500);
9 | expect(error.message).toBe('Internal Server Error');
10 |
11 | const forbiddenError = new ForbiddenError('Access denied');
12 | expect(forbiddenError.status).toBe(403);
13 | expect(forbiddenError.message).toBe('Access denied');
14 |
15 | const notFoundError = new NotFoundError('Not found');
16 | expect(notFoundError.status).toBe(404);
17 | expect(notFoundError.message).toBe('Not found');
18 |
19 | const unauthorizedError = new UnauthorizedError('Unauthorized');
20 | expect(unauthorizedError.status).toBe(401);
21 | expect(unauthorizedError.message).toBe('Unauthorized');
22 | });
23 |
--------------------------------------------------------------------------------
/modules/30-classes/70-static-property/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/30-classes/70-static-property/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | class UserResponse {
3 | constructor(public user: string) {}
4 |
5 | static fromArray(users: string[]): UserResponse[] {
6 | return users.map((user) => new UserResponse(user));
7 | }
8 | }
9 | // END
10 |
11 | export default UserResponse;
12 |
--------------------------------------------------------------------------------
/modules/30-classes/70-static-property/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Другое полезное применение статических свойств и методов — создание фабричных методов. Фабричный метод — это статический метод, который возвращает новый экземпляр класса. Реализуйте класс `UserResponse` с полем `user: string` и фабричный метод `fromArray`, который принимает массив и возвращает массив экземпляров класса `UserResponse`:
3 |
4 | ```typescript
5 | const response = UserResponse.fromArray(['user1', 'user2', 'user3']);
6 | console.log(response[0].user); // user1
7 | console.log(response[0] instanceof UserResponse); // true
8 | ```
9 |
--------------------------------------------------------------------------------
/modules/30-classes/70-static-property/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Иногда нам требуется задать свойство или метод, который будет общим для всех экземпляров этого класса. Например, чтобы определить, является ли объект экземпляром класса. В таком случае при объявлении метода мы можем указать ключевое слово `static`, и он станет доступен через имя класса:
3 |
4 | ```typescript
5 | class CustomFile {
6 | private static readonly maxCustomFileSize = 1000;
7 |
8 | static isCustomFile(file: CustomFile): boolean {
9 | return file instanceof CustomFile;
10 | }
11 |
12 | protected static isCustomFileTooBig(size: number): boolean {
13 | return size > CustomFile.maxCustomFileSize;
14 | }
15 |
16 | constructor(private name: string, private size: number) {
17 | if (CustomFile.isCustomFileTooBig(size)) {
18 | throw new Error('CustomFile is too big');
19 | }
20 | }
21 | }
22 |
23 | CustomFile.isCustomFile(new CustomFile('open-world.jpeg', 1000)); // true
24 | ```
25 |
26 |
27 |
28 | Статическим методам и свойствам также можно назначить модификаторы доступа `public`, `protected` и `private` и модификатор неизменяемости `readonly`. Это позволяет ограничить использование свойств и методов только текущим классом или наследниками.
29 |
30 | В отличии от JavaScript в TypeScript статические свойства и методы не могут быть переопределены в подклассах:
31 |
32 | ```typescript
33 | class CustomFile {
34 | static maxCustomFileSize = 1000;
35 |
36 | static isCustomFile(file: CustomFile): boolean {
37 | return file instanceof CustomFile;
38 | }
39 | }
40 |
41 | class ImageCustomFile extends CustomFile {
42 | static maxCustomFileSize = 2000; // Error!
43 |
44 | static isCustomFile(file: CustomFile): boolean { // Error!
45 | return file instanceof ImageCustomFile;
46 | }
47 | }
48 | ```
49 |
50 |
51 |
52 | Такой код не удастся скомпилировать. При этом остается доступ к статическим свойствам и методам родительского класса:
53 |
54 | ```typescript
55 | const file = new ImageCustomFile();
56 | console.log(ImageCustomFile.maxCustomFileSize); // 1000
57 | console.log(ImageCustomFile.isCustomFile(file)); // true
58 | ```
59 |
60 |
61 |
--------------------------------------------------------------------------------
/modules/30-classes/70-static-property/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Статические методы и свойства
3 |
--------------------------------------------------------------------------------
/modules/30-classes/70-static-property/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import UserResponse from './index';
3 |
4 | test('UserResponse', () => {
5 | const users = ['user1', 'user2'];
6 | const result = UserResponse.fromArray(users);
7 |
8 | expect(result).toEqual([
9 | new UserResponse('user1'),
10 | new UserResponse('user2'),
11 | ]);
12 | expect(result[0]).toBeInstanceOf(UserResponse);
13 |
14 | const users2 = ['user3', 'user4', 'user5'];
15 | const result2 = UserResponse.fromArray(users2);
16 |
17 | expect(result2[0].user).toBe('user3');
18 | expect(result2[2]).toBeInstanceOf(UserResponse);
19 | });
20 |
--------------------------------------------------------------------------------
/modules/30-classes/80-abstract-classes/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/30-classes/80-abstract-classes/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | abstract class Clock {
3 | constructor(protected hours: number, protected minutes: number, protected seconds: number) {}
4 |
5 | tick(): void {
6 | this.seconds += 1;
7 | if (this.seconds === 60) {
8 | this.seconds = 0;
9 | this.minutes += 1;
10 | }
11 | if (this.minutes === 60) {
12 | this.minutes = 0;
13 | this.hours += 1;
14 | }
15 | if (this.hours === 24) {
16 | this.hours = 0;
17 | }
18 | }
19 |
20 | abstract render(): string;
21 | }
22 | // END
23 |
24 | export default Clock;
25 |
--------------------------------------------------------------------------------
/modules/30-classes/80-abstract-classes/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 | Создайте абстрактный класс `Clock`, который будет содержать общую логику для классов часов с разными форматами вывода времени: 12-часовой и 24-часовой.
2 |
3 | В общей логике должно быть хранение данных: часы `hours`, минуты `minutes` и секунды `seconds`. Так же в общую логику входит метод `tick()`, который при каждом вызове увеличивает секунду на единицу. Если секунда увеличивается до значения 60, то увеличивается минута на 1, а секунда сбрасывается до 0. То же самое с минутами и часами: если значение минут увеличивается до 60, то увеличивается значение текущего часа, а минуты сбрасываются до 0. Если значение часа увеличивается до 24, то происходит сброс этого значения до 0.
4 |
5 | Начальное значение времени задается при создании объекта. Первым параметром в конструктор передается текущий час, вторым минуты, третьим секунды.
6 |
7 | Абстрактный класс `Clock` должен требовать от своих наследников реализацию метода `render()`.
8 |
9 | ```typescript
10 |
11 | // 24-часовой формат
12 | class Clock24 extends Clock {
13 | render(): string {
14 | const currentHour = this.hours % 24;
15 | const hours = currentHour.toString().padStart(2, '0');
16 | const minutes = this.minutes.toString().padStart(2, '0');
17 |
18 | return `${hours} : ${minutes}`;
19 | }
20 | }
21 |
22 | const clock24 = new Clock24(23, 59, 59);
23 | console.log(clock24.render()); // => '23 : 59'
24 | clock24.tick();
25 | console.log(clock24.render()); // => '00 : 00'
26 |
27 | // 12-часовой формат
28 | class Clock12 extends Clock {
29 | render(): string {
30 | const timeType = this.hours >= 12 ? 'PM' : 'AM';
31 |
32 | let currentHour = this.hours > 12 ? this.hours - 12 : this.hours;
33 | if (timeType === 'AM' && this.hours === 0) {
34 | currentHour = 12;
35 | }
36 |
37 | const hours = currentHour.toString().padStart(2, '0');
38 | const minutes = this.minutes.toString().padStart(2, '0');
39 | return `${hours} : ${minutes} ${timeType}`;
40 | }
41 | }
42 |
43 | const clock12 = new Clock12(23, 59, 59);
44 | console.log(clock12.render()); // => '11 : 59 PM'
45 | clock12.tick();
46 | console.log(clock12.render()); // => '12 : 00 AM'
47 | ```
48 |
--------------------------------------------------------------------------------
/modules/30-classes/80-abstract-classes/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Когда нам нужно определить общее поведение для нескольких классов, удобно использовать абстрактные классы, которые мы разберем в этом уроке.
3 |
4 | Абстрактные классы хоть и не могут быть созданы напрямую, однако они могут быть наследованы. Еще они могут указать явно, какой метод должен быть реализован в наследниках:
5 |
6 | ```typescript
7 | abstract class CustomFile {
8 | protected name: string;
9 |
10 | protected size: number;
11 |
12 | constructor(name: string, size: number) {
13 | this.name = name;
14 | this.size = size;
15 | }
16 |
17 | sizeInKb(): number {
18 | return this.size / 1024;
19 | }
20 | }
21 |
22 | class ImageCustomFile extends CustomFile {
23 | constructor(name: string, size: number) {
24 | super(name, size);
25 | }
26 | }
27 | ```
28 |
29 |
30 |
31 | Чтобы выносить из классов общую часть кода, абстрактные классы активно используются для построения архитектуры приложения и фреймворков. Например, в React есть класс `Component`, который может быть представлен как абстрактный класс. Мы не можем создать его напрямую, но он требует от наследников реализации метода `render`. Это позволяет создавать компоненты, которые будут рендериться при инициализации:
32 |
33 | ```typescript
34 | abstract class Component {
35 | abstract render(): void;
36 |
37 | constructor() {
38 | this.render();
39 | }
40 | }
41 | ```
42 |
43 |
44 |
--------------------------------------------------------------------------------
/modules/30-classes/80-abstract-classes/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Абстрактные классы
3 | definitions:
4 | - name: Абстрактный класс
5 | description: >
6 | это класс, который не может быть создан напрямую. Он предназначен только
7 | для наследования. Для создания абстрактного класса используется ключевое
8 | слово `abstract`.
9 | tips:
10 | - >
11 | [Когда использовать абстрактные классы в
12 | TypeScript](https://khalilstemmler.com/blogs/typescript/abstract-class)
13 |
--------------------------------------------------------------------------------
/modules/30-classes/80-abstract-classes/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import Clock from './index';
3 |
4 | test('Clock', () => {
5 | class Clock12 extends Clock {
6 | render(): string {
7 | const timeType = this.hours >= 12 ? 'PM' : 'AM';
8 |
9 | let currentHour = this.hours > 12 ? this.hours - 12 : this.hours;
10 | if (timeType === 'AM' && this.hours === 0) {
11 | currentHour = 12;
12 | }
13 |
14 | const hours = currentHour.toString().padStart(2, '0');
15 | const minutes = this.minutes.toString().padStart(2, '0');
16 | return `${hours} : ${minutes} ${timeType}`;
17 | }
18 | }
19 |
20 | class Clock24 extends Clock {
21 | render(): string {
22 | return `${this.hours.toString().padStart(2, '0')} : ${this.minutes.toString().padStart(2, '0')}`;
23 | }
24 | }
25 |
26 | const clock121 = new Clock12(11, 59, 0);
27 | expect(clock121.render()).toBe('11 : 59 AM');
28 |
29 | const clock12 = new Clock12(23, 59, 58);
30 | expect(clock12.render()).toBe('11 : 59 PM');
31 |
32 | clock12.tick();
33 | clock12.tick();
34 |
35 | expect(clock12.render()).toBe('12 : 00 AM');
36 |
37 | const clock24 = new Clock24(23, 59, 58);
38 | expect(clock24.render()).toBe('23 : 59');
39 |
40 | clock24.tick();
41 | clock24.tick();
42 |
43 | expect(clock24.render()).toBe('00 : 00');
44 | });
45 |
--------------------------------------------------------------------------------
/modules/30-classes/description.ru.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Классы
3 | description: |
4 | С помощью классов описывают одинаковые объекты с состоянием и поведением. Классы в TypeScript расширяют стандартные в JavaScript и предоставляют дополнительные инструменты, так полюбившиеся программистам на "классических" ООП языках.
5 |
--------------------------------------------------------------------------------
/modules/35-interfaces/10-interfaces-overview/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/35-interfaces/10-interfaces-overview/index.ts:
--------------------------------------------------------------------------------
1 | interface IVehicle {
2 | seats: number;
3 | colour: string;
4 | canHavePassengers: boolean;
5 | fuelPer100Kilometers: number;
6 | calcFuelNeeded(distance:number): number;
7 | }
8 |
9 | // BEGIN
10 | class Car implements IVehicle {
11 | constructor(
12 | public seats: number,
13 | public colour: string,
14 | public canHavePassengers: boolean,
15 | public fuelPer100Kilometers: number,
16 | ) {}
17 |
18 | calcFuelNeeded(distance: number) {
19 | return (this.fuelPer100Kilometers / 100) * distance;
20 | }
21 | }
22 | // END
23 |
24 | export default Car;
25 |
--------------------------------------------------------------------------------
/modules/35-interfaces/10-interfaces-overview/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Вам дан интерфейс IVehicle. Задача состоит в том, чтобы на основе этого интерфейса реализовать класс Car, который будет иметь метод calcFuelNeeded, принимающий расстояние в километрах и возвращающий расход топлива на указанную дистанцию. Также у класса Car должна быть функция-конструктор, которая принимает и реализует свойства, указанные в интерфейсе.
3 |
4 | ```typescript
5 | const porche = new Car(4, 'red', true, 20);
6 | console.log(porche.calcFuelNeeded(200)); // 40
7 | ```
8 |
--------------------------------------------------------------------------------
/modules/35-interfaces/10-interfaces-overview/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | В этом уроке мы поговорим об интерфейсах. Мы узнаем, что это такое, зачем они нужны, и в каких случаях их стоит использовать вместо типов.
3 |
4 | ## Что такое интерфейс
5 |
6 | **Интерфейс** — это конструкция языка TypeScript, которая используется, чтобы описывать объекты и функции.
7 |
8 | Рассмотрим следующий пример:
9 |
10 | ```typescript
11 | interface IUser {
12 | firstName: string;
13 | pointsCount: number;
14 | }
15 |
16 | const user: IUser = {
17 | firstName: 'Mark',
18 | pointsCount: 100,
19 | };
20 | ```
21 |
22 | В данном фрагменте мы создали интерфейс и реализовали на его основе объект `user`.
23 |
24 | Интерфейс выглядит как определение объектного типа. Объектные типы и интерфейсы взаимозаменяемы почти во всех ситуациях. Сравним с примером выше:
25 |
26 | ```typescript
27 | type User = {
28 | firstName: string;
29 | pointsCount: number;
30 | }
31 |
32 | const user: User = {
33 | firstName: 'Mark',
34 | pointsCount: 100,
35 | };
36 | ```
37 |
38 | Здесь мы реализовали такой же объект, но уже на основе типа, а не интерфейса. Разницы почти нет.
39 |
40 | Документация TypeScript говорит о том, что мы можем выбирать, что использовать — тип или интерфейс. Выбор зависит от ситуации. В таком случае возникает вопрос, зачем нужны интерфейсы, когда уже есть типы?
41 |
42 | ## Когда использовать интерфейсы
43 |
44 | Интерфейсы и типы во многом похожи. Но есть отличия, на основе которых мы и можем выбирать, что именно следует использовать в конкретном случае.
45 |
46 | Главная особенность интерфейсов связана с классами. Классы, которые реализуют интерфейсы, содержат внутри себя свойства и методы, указанные в реализуемом интерфейсе:
47 |
48 | ```typescript
49 | interface Countable {
50 | count(): number;
51 | }
52 |
53 | class SchoolClass implements Countable {
54 | // Тут какая-то логика
55 | count(): number {
56 | // Обязательно создать этот метод, так как он указан в интерфейсе
57 | }
58 | }
59 |
60 | const sc = new SchoolClass();
61 | // Возвращает число студентов в классе
62 | sc.count();
63 | ```
64 |
65 | В этом примере мы реализовали класс на основе интерфейса. Теперь во всех функциях, где объекты используются только для того, чтобы посчитать количество чего-либо внутри них, можно указывать `Countable` вместо `SchoolClass`:
66 |
67 | ```typescript
68 | // А не function doSomething(obj: SchoolClass)
69 | function doSomething(obj: Countable) {
70 | // Где-то внутри вызывается
71 | obj.count();
72 | }
73 | ```
74 |
75 | Так благодаря интерфейсам функция становится более универсальной. Мы можем передать любые объекты, соответствующие `Countable`, а не только `SchoolClass`. В программировании такая возможность называется полиморфизмом подтипов ([Subtyping](https://en.wikipedia.org/wiki/Subtyping)).
76 |
--------------------------------------------------------------------------------
/modules/35-interfaces/10-interfaces-overview/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Об интерфейсах
3 | tips:
4 | - >
5 | [Interfaces vs
6 | Types](https://ultimatecourses.com/blog/typescript-interfaces-vs-types)
7 |
--------------------------------------------------------------------------------
/modules/35-interfaces/10-interfaces-overview/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import Car from './index';
3 |
4 | test('Car', () => {
5 | const porche = new Car(4, 'red', true, 20);
6 | expect(porche.calcFuelNeeded(100)).toBe(20);
7 |
8 | const schoolBus = new Car(30, 'yellow', true, 24);
9 | expect(schoolBus.calcFuelNeeded(25)).toBe(6);
10 |
11 | const lada = new Car(4, 'white', true, 13);
12 | expect(lada.calcFuelNeeded(200)).toBe(26);
13 | });
14 |
--------------------------------------------------------------------------------
/modules/35-interfaces/20-interface-using/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/35-interfaces/20-interface-using/index.ts:
--------------------------------------------------------------------------------
1 | interface IFlying {
2 | canFly: true;
3 | }
4 |
5 | interface IBird extends IFlying {
6 | isLiving: true;
7 | }
8 |
9 | interface IPlane extends IFlying {
10 | canCarryPeople: true;
11 | }
12 |
13 | // BEGIN
14 | interface ISuperman extends
15 | IBird, IPlane {
16 | guessWho: (guess: string) => string;
17 | }
18 |
19 | const superMan: ISuperman = {
20 | canFly: true,
21 | isLiving: true,
22 | canCarryPeople: true,
23 | guessWho: (guess) => (guess.toLowerCase() !== 'superman' ? `It's a ${guess}?` : `It's a ${guess}!`),
24 | };
25 | // END
26 |
27 | export default superMan;
28 |
--------------------------------------------------------------------------------
/modules/35-interfaces/20-interface-using/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Вам даны несколько интерфейсов. На их основе создайте интерфейс ISuperman. ISuperMan должен иметь метод guessWho, принимающий и возвращающий строку.
3 |
4 | На основе интерфейса ISuperMan создайте объект `superMan`. Метод guessWho должен работать следующим образом: если в качестве строки в аргументе приходит любое значение кроме superman (в любом регистре), то следует вернуть предположение "It's a ${value}?", иначе "It's a ${value}!".
5 |
6 | ```typescript
7 | console.log(superMan.guessWho('bird')); // "It's a bird?";
8 | console.log(superMan.guessWho('plane')); "It's a plane?";
9 | console.log(superMan.guessWho('superman')); "It's a superman!";
10 | ```
11 |
--------------------------------------------------------------------------------
/modules/35-interfaces/20-interface-using/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Использование интерфейсов
3 | tips:
4 | - >
5 | [Официальная
6 | документация](https://www.typescriptlang.org/docs/handbook/2/objects.html)
7 |
--------------------------------------------------------------------------------
/modules/35-interfaces/20-interface-using/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import superMan from './index';
3 |
4 | test('guess who', () => {
5 | expect(superMan.guessWho('bird')).toBe("It's a bird?");
6 | expect(superMan.guessWho('plane')).toBe("It's a plane?");
7 | expect(superMan.guessWho('SupErMan')).toBe("It's a SupErMan!");
8 | });
9 |
--------------------------------------------------------------------------------
/modules/35-interfaces/30-interface-implementation/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/35-interfaces/30-interface-implementation/index.ts:
--------------------------------------------------------------------------------
1 | type Entry = {
2 | [key: string]: number
3 | };
4 |
5 | interface IPhonebook {
6 | get(key: string): number | null
7 | set(key: string, value: number): void
8 | }
9 |
10 | // BEGIN
11 | class Phonebook implements IPhonebook {
12 | private readonly entries: Entry = {};
13 |
14 | get(key: string): number | null {
15 | return key in this.entries ? this.entries[key] : null;
16 | }
17 |
18 | set(key: string, value: number): void {
19 | this.entries[key] = value;
20 | }
21 | }
22 | // END
23 |
24 | export default Phonebook;
25 |
--------------------------------------------------------------------------------
/modules/35-interfaces/30-interface-implementation/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | С помощью предоставленного интерфейса `IPhonebook` и типа `Entry` реализуйте класс `Phonebook`, который представляет телефонный справочник со следующими свойствами:
3 |
4 | - `entries` — база данных, объект, записи в котором представляют собой имена в качестве ключей и телефоны в качестве значений. Свойство должно быть неизменяемым и доступным только для чтения
5 | - `get` — метод, возвращающий телефон по имени
6 | - `set` — метод, записывающий имя и телефон в справочник
7 |
8 | Примеры:
9 | ```typescript
10 | const myNote = new Phonebook();
11 | myNote.set('help', 911);
12 | myNote.get('help'); // 911
13 | ```
14 |
--------------------------------------------------------------------------------
/modules/35-interfaces/30-interface-implementation/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Реализация интерфейсов классами
3 |
--------------------------------------------------------------------------------
/modules/35-interfaces/30-interface-implementation/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import Phonebook from './index';
3 |
4 | test('myBook', () => {
5 | const myBook = new Phonebook();
6 | myBook.set('test1', 1);
7 | expect(myBook.get('test1')).toBe(1);
8 | expect(myBook.get('test2')).toBe(null);
9 | });
10 |
--------------------------------------------------------------------------------
/modules/35-interfaces/description.ru.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Интерфейсы
3 | description: |
4 | Интерфейсы в TypeScript позволяют по-другому описывать объектные типы в привычном для ООП языков синтаксисе и расширяют семантику — добавляют новые полезные возможности использования.
5 |
--------------------------------------------------------------------------------
/modules/40-generics/10-generics-overview/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/40-generics/10-generics-overview/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | function last(coll: Array): T | null {
3 | return coll.length > 0 ? coll[coll.length - 1] : null;
4 | }
5 | // END
6 |
7 | export default last;
8 |
--------------------------------------------------------------------------------
/modules/40-generics/10-generics-overview/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте дженерик `last()`, который извлекает последний элемент из массива если он есть или null — если его нет:
3 |
4 | ```typescript
5 | last([]); // null
6 | last([3, 2]); // 2
7 | last(['code-basics', 'hexlet']); // hexlet
8 | ```
9 |
--------------------------------------------------------------------------------
/modules/40-generics/10-generics-overview/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Введение в дженерики
3 | definitions:
4 | - name: Обобщенный тип
5 | description: >
6 | абстрактный тип, на место которого можно подставить конкретный тип. Когда
7 | мы пишем код, мы можем описать поведение обобщенных типов или их связь с
8 | другими обобщенными типами, не зная какой тип будет использован в месте их
9 | использования.
10 | tips:
11 | - >
12 | [Параметрический
13 | полиморфизм](https://ru.wikipedia.org/wiki/Параметрический_полиморфизм)
14 | - >
15 | [Полиморфизм простыми
16 | словами](https://medium.com/devschacht/polymorphism-207d9f9cd78)
17 |
--------------------------------------------------------------------------------
/modules/40-generics/10-generics-overview/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import last from './index';
4 |
5 | test('function', () => {
6 | expect(last([])).toBe(null);
7 | expect(last([3, 4])).toBe(4);
8 | expect(last(['cat', 'dog'])).toBe('dog');
9 |
10 | expectTypeOf(last).returns.toMatchTypeOf();
11 | });
12 |
--------------------------------------------------------------------------------
/modules/40-generics/20-generic-types/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/40-generics/20-generic-types/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | type MySet = {
3 | items: Array;
4 | has(value: T): boolean;
5 | add(value: T): number;
6 | };
7 | // END
8 |
9 | export default MySet;
10 |
--------------------------------------------------------------------------------
/modules/40-generics/20-generic-types/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте описание обобщенного типа `MySet`, который представляет из себя аналог множества `Set` из JavaScript. Пример использования объекта этого типа:
3 |
4 | ```typescript
5 | const s: MySet = ...;
6 | // Добавление возвращает количество элементов
7 | s.add(1); // 1
8 | s.add(10); // 2
9 |
10 | s.has(1); // true
11 | s.has(8); // false
12 | ```
13 |
14 | Тип включает в себя два метода: `add()` и `has()`. Данные внутри должны храниться в свойстве `items`.
15 |
--------------------------------------------------------------------------------
/modules/40-generics/20-generic-types/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Дженерики (Типы)
3 | tips:
4 | - >
5 | [Ограничения
6 | дженериков](https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints)
7 | - >
8 | [Параметры типа - extends (generic
9 | constraints)](https://typescript-definitive-guide.ru/book/chapters/Obobshcheniya_(Generics)/#Parametry_tipa_-_extends_(generic_constraints))
10 |
--------------------------------------------------------------------------------
/modules/40-generics/20-generic-types/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import MySet from './index';
4 |
5 | test('function', () => {
6 | const s1: MySet = {
7 | items: [],
8 | has(value) {
9 | return this.items.includes(value);
10 | },
11 | add(value) {
12 | this.items.push(value);
13 | return this.items.length;
14 | },
15 | };
16 |
17 | expect(s1.has(1)).toBe(false);
18 | s1.add(1);
19 | expect(s1.has(1)).toBe(true);
20 |
21 | expectTypeOf(s1.has).parameters.toMatchTypeOf<[number]>();
22 | expectTypeOf(s1.add).parameters.toMatchTypeOf<[number]>();
23 | });
24 |
25 | test('function', () => {
26 | const s1: MySet = {
27 | items: [],
28 | has(value) {
29 | return this.items.includes(value);
30 | },
31 | add(value) {
32 | this.items.push(value);
33 | return this.items.length;
34 | },
35 | };
36 |
37 | expect(s1.has('hexlet')).toBe(false);
38 | s1.add('hexlet');
39 | expect(s1.has('hexlet')).toBe(true);
40 |
41 | expectTypeOf(s1.has).parameters.toMatchTypeOf<[string]>();
42 | expectTypeOf(s1.add).parameters.toMatchTypeOf<[string]>();
43 | });
44 |
--------------------------------------------------------------------------------
/modules/40-generics/30-generic-functions/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/40-generics/30-generic-functions/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | type MyArray = {
3 | items: Array;
4 | push(value: T): number;
5 | filter(callback: (value: T, index: number, array: Array) => boolean): MyArray;
6 | };
7 | // END
8 |
9 | export default MyArray;
10 |
--------------------------------------------------------------------------------
/modules/40-generics/30-generic-functions/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте описание обощенного типа `MyArray`, который представляет аналог массива из JavaScript. Пример использования объекта этого типа:
3 |
4 | ```typescript
5 | const coll: MyArray = ...;
6 | coll.push(1); // 1
7 | coll.push(10); // 2
8 | coll.push(99); // 3
9 |
10 | const newColl = coll.filter((value) => value % 2 == 0);
11 | console.log(newColl.items); // [10]
12 | ```
13 |
14 | Тип включает в себя два метода: [push()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) и [filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), совпадающие по сигнатуре с методами Array. Данные внутри должны храниться в свойстве `items`. Для `push()` примем соглашение, что метод принимает только один параметр. Игнорируйте остальные параметры.
15 |
--------------------------------------------------------------------------------
/modules/40-generics/30-generic-functions/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Дженерики (Функции)
3 | tips:
4 | - >
5 | [Дженерики в TypeScript: разбираемся
6 | вместе](https://habr.com/ru/post/455473/)
7 | - >
8 | [Полиморфизм простыми
9 | словами](https://medium.com/devschacht/polymorphism-207d9f9cd78)
10 |
--------------------------------------------------------------------------------
/modules/40-generics/30-generic-functions/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import MyArray from './index';
4 |
5 | test('MyArray', () => {
6 | const coll: MyArray = {
7 | items: [],
8 | push(value) {
9 | return this.items.push(value);
10 | },
11 | filter(callback) {
12 | const newItems = this.items.filter(callback);
13 | return { ...this, items: newItems };
14 | },
15 | };
16 |
17 | expect(coll.push(1)).toBe(1);
18 | expect(coll.push(2)).toBe(2);
19 | expect(coll.push(5)).toBe(3);
20 |
21 | expectTypeOf(coll.push).parameters.toMatchTypeOf<[number]>();
22 |
23 | const coll1: MyArray = {
24 | items: [],
25 | push(value) {
26 | return this.items.push(value);
27 | },
28 | filter(callback) {
29 | const newItems = this.items.filter(callback);
30 | return { ...this, items: newItems };
31 | },
32 | };
33 |
34 | expectTypeOf(coll1.push).parameters.toMatchTypeOf<[string]>();
35 | });
36 |
--------------------------------------------------------------------------------
/modules/40-generics/40-many-parameters/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/40-generics/40-many-parameters/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | type MyMap = {
3 | values: Map;
4 | set(key: K, value: V): void;
5 | get(key: K): V | undefined;
6 | };
7 | // END
8 |
9 | export default MyMap;
10 |
--------------------------------------------------------------------------------
/modules/40-generics/40-many-parameters/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте описание обобщенного типа `MyMap`, который представляет из себя аналог ассоциативного массива из JavaScript. Пример использования объекта этого типа:
3 |
4 | ```typescript
5 | const map: MyMap = ...;
6 | map.set('one', 1);
7 | map.set('two', 2);
8 |
9 | map.get('one'); // 1
10 | map.get('two'); // 2
11 | ```
12 |
13 | Тип включает в себя два метода `set()` и `get()`. Первый метод принимает два дженерик-параметра: ключ и значение. Второй метод принимает ключ и возвращает значение. Значения хранятся внутри объекта в виде встроенного в JavaScript класса [Map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map).
14 |
--------------------------------------------------------------------------------
/modules/40-generics/40-many-parameters/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Дженерики, как и обычные функции, могут иметь несколько параметров типа. В этом уроке мы разберем такие дженерики.
3 |
4 | Принцип работы дженериков от количества параметров не меняется. Единственное, за чем нужно следить, это имена:
5 |
6 | ```typescript
7 | type Double = {
8 | first: T;
9 | second: U;
10 | }
11 |
12 | const value: Double = {
13 | first: 'code-basics',
14 | second: 1,
15 | }
16 | ```
17 |
18 |
19 |
20 | ## Вывод типа из аргументов функции
21 |
22 | Представим, что нам нужно вызвать функцию с несколькими параметрами. Аргументы представлены дженериками.
23 |
24 | Например, функция `join()` может быть описана следующим образом:
25 |
26 | ```typescript
27 | function join(coll1: (T | U)[], coll2: U[]): (T | U)[] {
28 | return coll1.concat(coll2);
29 | };
30 |
31 | join([1, 2], ['one', 'two']); // [1, 2, 'one', 'two']
32 | ```
33 |
34 |
35 |
36 | Но TypeScript позволяет нам сделать это проще и не указывать типы для всех параметров:
37 |
38 | ```typescript
39 | join([1, 2], ['one', 'two']); // [1, 2, 'one', 'two']
40 | ```
41 |
42 | TypeScript сам выведет типы для параметров функции. Это называется выводом типа из аргументов функции. В данном случае TypeScript выведет типы `number` и `string` для параметров `T` и `U` соответственно.
43 |
44 | В следующих уроках мы познакомимся со встроенными в TypeScript дженериками, у которых два параметра. В реальном же программировании такие дженерики часто встречаются в прикладном коде, например, в React.
45 |
--------------------------------------------------------------------------------
/modules/40-generics/40-many-parameters/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Дженерики с несколькими параметрами
3 | tips:
4 | - >
5 | [Typescript Generics
6 | Explained](https://rossbulat.medium.com/typescript-generics-explained-15c6493b510f)
7 |
--------------------------------------------------------------------------------
/modules/40-generics/40-many-parameters/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import MyMap from './index';
4 |
5 | test('MyMap', () => {
6 | const map: MyMap = {
7 | values: new Map(),
8 | set(key, value) {
9 | this.values.set(key, value);
10 | },
11 | get(key) {
12 | return this.values.get(key);
13 | },
14 | };
15 |
16 | map.set('one', 1);
17 | map.set('two', 2);
18 |
19 | expect(map.get('one')).toBe(1);
20 | expect(map.get('two')).toBe(2);
21 | expect(map.get('three')).toBe(undefined);
22 |
23 | expectTypeOf(map.set).parameters.toMatchTypeOf<[string, number]>();
24 | expectTypeOf(map.get).returns.toMatchTypeOf();
25 | });
26 |
--------------------------------------------------------------------------------
/modules/40-generics/50-async-functions/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/40-generics/50-async-functions/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | const asyncMap = async (arr: Promise[], fn: (item: T, index: number) => P) => {
3 | const promises = arr.map(async (item, index) => {
4 | const result = await item;
5 | return fn(result, index);
6 | });
7 |
8 | return Promise.all(promises);
9 | };
10 | // END
11 |
12 | export default asyncMap;
13 |
--------------------------------------------------------------------------------
/modules/40-generics/50-async-functions/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте асинхронный вариант функции `map()` - `asyncMap()`. Первым аргументом `asyncMap()` принимает массив с Promise. Вторым — функцию, которая применяется к каждому элементу. Функция должна вернуть массив с результатами выполнения функции для каждого элемента:
3 |
4 | ```typescript
5 | const promisedNumbers = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];
6 |
7 | asyncMap(promisedNumbers, (num, index) => num * index).then((result) => {
8 | console.log(result); // [0, 2, 6]
9 | });
10 | ```
11 |
--------------------------------------------------------------------------------
/modules/40-generics/50-async-functions/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | `Promise` стали самым популярным способом работы с асинхронным кодом в JavaScript. Они позволяют избежать callback hell, а также упрощают работу с асинхронными функциями. TypeScript также поддерживает привычный синтаксис для работы с Promise в виде async/await и типизацию.
3 |
4 |
5 |
6 | ```typescript
7 | const promise = new Promise((resolve, reject) => {
8 | setTimeout(() => {
9 | resolve(42);
10 | }, 1000);
11 | });
12 | ```
13 |
14 | `Promise` представляет собой дженерик с типом, который будет возвращен в случае успешного выполнения. В примере выше это тип `number`.
15 |
16 | Чтобы продолжать работать в одном стиле с функциями, которые принимают callback, мы можем промисифцировать их. Для этого нам нужно обернуть функцию в `Promise`:
17 |
18 | ```typescript
19 | const wait = (ms: number): Promise => {
20 | return new Promise((resolve) => {
21 | const timer = setTimeout(() => {
22 | resolve(ms);
23 | }, ms);
24 | });
25 | };
26 | ```
27 |
28 | Мы можем и не описывать тип возвращаемого значения, так как TypeScript сможет его вывести из типа, который мы передаем в `Promise`. К тому же из функции, которая помечена как `async`, `Promise` возвращается автоматически, и тип возвращаемого значения будет обернут в `Promise`:
29 |
30 | ```typescript
31 | const getHours = async () => {
32 | return new Date().getHours();
33 | };
34 |
35 | const hoursPromise: Promise = getHours();
36 | ```
37 |
38 |
39 |
40 | Так как `Promise`, как и контейнер, заворачивает значения внутри себя, мы можем использовать `await` для получения значения из него:
41 |
42 | ```typescript
43 | const hours = await getHours();
44 | ```
45 |
46 | Как и в JavaScript в TypeScript `await` может использоваться только внутри функций, которые помечены как `async`.
47 |
48 | `Promise` вместе с `async/await` позволяют писать асинхронный код в синхронном стиле и сильно упрощают работу с асинхронным кодом. TypeScript поддерживает этот синтаксис и с помощью дженериков позволяет нам использовать его со всей мощью типизации.
49 |
--------------------------------------------------------------------------------
/modules/40-generics/50-async-functions/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Асинхронные функции
3 | tips:
4 | - |
5 | [Что такое Promise](https://doka.guide/js/promise/)
6 | - >
7 | [TypeScript Deep Dive:
8 | Promise](https://basarat.gitbook.io/typescript/future-javascript/promise)
9 | - >
10 | [Awaited](https://www.typescriptlang.org/docs/handbook/utility-types.html#awaitedtype)
11 |
--------------------------------------------------------------------------------
/modules/40-generics/50-async-functions/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 | import asyncMap from './index';
3 |
4 | test('asyncMap', async () => {
5 | const result = await asyncMap([Promise.resolve(1), Promise.resolve(2)], (item) => item * 2);
6 | expect(result).toEqual([2, 4]);
7 |
8 | const result2 = await asyncMap([Promise.resolve('one'), Promise.resolve('two'), Promise.resolve('three')], (item) => item.toUpperCase());
9 | expect(result2).toEqual(['ONE', 'TWO', 'THREE']);
10 |
11 | const result3 = await asyncMap(
12 | [Promise.resolve(1), Promise.resolve(2)],
13 | (item, index) => item * index,
14 | );
15 | expect(result3).toEqual([0, 2]);
16 |
17 | expectTypeOf(result).toMatchTypeOf();
18 | expectTypeOf(result2).toMatchTypeOf();
19 | });
20 |
--------------------------------------------------------------------------------
/modules/40-generics/60-generic-classes/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/40-generics/60-generic-classes/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | class Queue {
3 | private data: T[] = [];
4 |
5 | enqueue(element: T) {
6 | this.data.push(element);
7 | }
8 |
9 | dequeue() {
10 | if (this.data.length === 0) {
11 | throw new Error('Queue is empty');
12 | }
13 | return this.data.shift();
14 | }
15 | }
16 | // END
17 |
18 | export default Queue;
19 |
--------------------------------------------------------------------------------
/modules/40-generics/60-generic-classes/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте класс очереди (`Queue`) с методами `enqueue` и `dequeue`. Метод `enqueue` добавляет элемент в конец очереди, а метод `dequeue` удаляет элемент из начала очереди. Если очередь пуста, то при вызове метода `dequeue` должно быть выброшено исключение `Error`:
3 |
4 | ```typescript
5 | const queue = new Queue();
6 | queue.enqueue(1);
7 | queue.dequeue(); // 1
8 | queue.dequeue(); // Error: Queue is empty
9 | ```
10 |
--------------------------------------------------------------------------------
/modules/40-generics/60-generic-classes/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Дженерик-классы, как и дженерик функции, позволяют создавать классы, которые могут работать с разными типами данных. Например, класс `Triple` может хранить три значения любого типа. В этом случае вместо того, чтобы создавать классы для каждого типа, можно создать обобщенный класс, который будет работать с любым типом данных.
3 |
4 |
5 |
6 | ```typescript
7 | class Triple {
8 | constructor(protected first: T, protected second: U, protected third: V) {}
9 |
10 | getFirst(): T {
11 | return this.first;
12 | }
13 |
14 | getSecond(): U {
15 | return this.second;
16 | }
17 |
18 | getThird(): V {
19 | return this.third;
20 | }
21 | }
22 | ```
23 |
24 | В этом примере класс `Triple` — дженерик-класс, в который мы можем поместить любые типы данных. При этом у нас остаются гарантии безопасности и вывод типов, которые мы получили при использовании обобщенных функций:
25 |
26 | ```typescript
27 | const triple = new Triple(1, 'string', null);
28 | const first = triple.getFirst(); // number
29 | const second = triple.getSecond(); // string
30 | ```
31 |
32 |
33 |
34 | Также можно наследоваться от обобщенных классов. Например, класс `Pair` может быть наследником класса `Triple`, который хранит два значения любого типа:
35 |
36 | ```typescript
37 | class Pair extends Triple {
38 | constructor(first: T, second: U) {
39 | super(first, second, undefined as never);
40 | }
41 |
42 | getFirst(): T {
43 | return this.first;
44 | }
45 |
46 | getSecond(): U {
47 | return this.second;
48 | }
49 | }
50 | ```
51 |
52 | Здесь мы использовали приведение к типу `never`, чтобы пометить третий параметр как отсутствующий.
53 |
54 | Как и обычные классы, обобщенные классы также можно использовать в качестве типов параметров функций:
55 |
56 | ```typescript
57 | function swap(pair: Pair): Pair {
58 | return new Pair(pair.getSecond(), pair.getFirst());
59 | }
60 | ```
61 |
62 |
63 |
64 | Дженерик-классы полезны, когда нам нужно создать какой-нибудь контейнер для хранения данных, как в примере с классом `Pair`. `Array`, `Map`, `Set` — это дженерик-классы, которые хранят элементы заданного типа.
65 |
--------------------------------------------------------------------------------
/modules/40-generics/60-generic-classes/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Дженерики на классах
3 |
--------------------------------------------------------------------------------
/modules/40-generics/60-generic-classes/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import Queue from './index';
3 |
4 | test('Queue', () => {
5 | const queue = new Queue();
6 | queue.enqueue(1);
7 | queue.enqueue(2);
8 | queue.enqueue(3);
9 | expect(queue.dequeue()).toBe(1);
10 | expect(queue.dequeue()).toBe(2);
11 | expect(queue.dequeue()).toBe(3);
12 | expect(() => queue.dequeue()).toThrow();
13 |
14 | const queue2 = new Queue();
15 | queue2.enqueue('one');
16 | queue2.enqueue('two');
17 | expect(queue2.dequeue()).toBe('one');
18 | expect(queue2.dequeue()).toBe('two');
19 | expect(() => queue2.dequeue()).toThrow();
20 | });
21 |
--------------------------------------------------------------------------------
/modules/40-generics/description.ru.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Дженерики
3 | description: |
4 | Дженерики позволяют писать более универсальный код, который может работать с разными типами данных. В TypeScript мощная система дженериков, которая позволяет создавать гибкие и при этом надежные части системы.
5 |
--------------------------------------------------------------------------------
/modules/50-objects/15-object/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/50-objects/15-object/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | const extract = (obj: object, keys: Array): object => {
3 | const entries = Object.entries(obj).filter(([key]) => keys.includes(key));
4 |
5 | return Object.fromEntries(entries);
6 | };
7 | // END
8 |
9 | export default extract;
10 |
--------------------------------------------------------------------------------
/modules/50-objects/15-object/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `extract(object, keys)`, которая возвращает новый объект c указанными ключами. Например:
3 |
4 | ```typescript
5 | const user = {
6 | name: 'Tirion',
7 | email: 'tirion@lanister.got',
8 | age: 35,
9 | }
10 |
11 | extract(user, ['name', 'age']); // { name: 'Tirion', age: 35 }
12 | ```
13 |
--------------------------------------------------------------------------------
/modules/50-objects/15-object/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Иногда нам нужно ограничить входной параметр функции типом «любой объект». Например, это нужно для функции, которая проверяет наличие ключей в объекте. Существует несколько способов сделать такую проверку, но не все из них работают как ожидается.
3 |
4 | Посмотрим на пример:
5 |
6 | ```typescript
7 | // В качестве типа используется {}
8 | function toString(obj: {}) {
9 | return obj.toString();
10 | }
11 |
12 | toString('wow'); // Ok!
13 | toString(123); // Ok!
14 | toString({}); // Ok!
15 | ```
16 |
17 | Пустой объектный тип `{}` подразумевает под собой объект любой структуры и ограничивает множество всех значений за исключением `null` и `undefined`. Пустой интерфейс работает так же, как и пустой объектный тип. Это не то, что мы ожидали.
18 |
19 | Тип `Object` — это тип объекта. Он работает так же, как тип `{}` с некоторыми отличиями. Он предопределяет типы некоторых встроенных методов, например, `toString()`, а тип `{}` этого не делает. Например:
20 |
21 | ```typescript
22 | const foo: {} = {
23 | toString() {
24 | return 1; // Ok!
25 | }
26 | };
27 |
28 | const bar: Object = {
29 | toString() {
30 | return 1; // Error!
31 | }
32 | };
33 | ```
34 |
35 | Второе определение `bar` не работает, потому что тип `Object` указывает на то, что метод `toString()` должен возвращать строку.
36 |
37 | Если мы хотим работать с непримитивными значениями, то для этого существует еще один тип `object` (с маленькой буквы):
38 |
39 | ```typescript
40 | function toString(obj: object) {
41 | return obj.toString();
42 | }
43 |
44 | toString('wow'); // Error!
45 | toString(123); // Error!
46 | toString({}); // Ok!
47 | ```
48 |
49 | С помощью типа `object` нельзя получить доступ к свойствам объекта. Для такой задачи используются уже другие механизмы.
50 |
--------------------------------------------------------------------------------
/modules/50-objects/15-object/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Тип object
3 |
--------------------------------------------------------------------------------
/modules/50-objects/15-object/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import extract from './index';
3 |
4 | test('extract', () => {
5 | const obj = {
6 | name: 'John',
7 | age: 30,
8 | address: {
9 | street: 'Main Street',
10 | number: 123,
11 | },
12 | };
13 |
14 | expect(extract(obj, ['name', 'address'])).toEqual({
15 | name: 'John',
16 | address: obj.address,
17 | });
18 |
19 | expect(extract(obj, ['name', 'lastName'])).toEqual({
20 | name: 'John',
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/modules/50-objects/20-index-signatures/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/50-objects/20-index-signatures/index.ts:
--------------------------------------------------------------------------------
1 | interface SalaryStatistics {
2 | min: number;
3 | max: number;
4 | avg: number;
5 | }
6 |
7 | // BEGIN
8 | interface EmployeeSalary {
9 | [name: string]: number;
10 | }
11 |
12 | const buildSalaryStatistics = (employees: EmployeeSalary): SalaryStatistics => {
13 | const salaries = Object.values(employees);
14 | const min = Math.min(...salaries);
15 | const max = Math.max(...salaries);
16 | const avg = salaries.reduce((acc, salary) => acc + salary, 0) / salaries.length;
17 |
18 | return { min, max, avg };
19 | };
20 | // END
21 |
22 | export {
23 | EmployeeSalary,
24 | buildSalaryStatistics,
25 | };
26 |
--------------------------------------------------------------------------------
/modules/50-objects/20-index-signatures/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте интерфейс `EmployeeSalary`, где ключом выступает имя (`string`), а значением — зарплата (`number`). Также реализуйте функцию `buildSalaryStatistics(employees: EmployeeSalary): SalaryStatistics`, которая должна возвращать минимальную (поле `min`), среднюю (поле `avg`) и самую высокую (поле `max`) зарплату.
3 |
4 | ```typescript
5 | const employees: EmployeeSalary = {
6 | mango: 100,
7 | poly: 50,
8 | ajax: 150,
9 | };
10 |
11 | employees.ironMan = 1000;
12 |
13 | buildSalaryStatistics(employees); // { min: 50, max: 1000, avg: 325 }
14 | ```
15 |
--------------------------------------------------------------------------------
/modules/50-objects/20-index-signatures/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Динамические ключи (Index Signature)
3 | tips:
4 | - >
5 | [Глава о Index Signature в книге TypeScript Deep
6 | Dive](https://basarat.gitbook.io/typescript/type-system/index-signatures)
7 | - >
8 | [Официальная документация про Template Literal
9 | Types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html)
10 |
--------------------------------------------------------------------------------
/modules/50-objects/20-index-signatures/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { buildSalaryStatistics, EmployeeSalary } from '.';
3 |
4 | test('buildSalaryStatistics', () => {
5 | const employees: EmployeeSalary = {
6 | mango: 100,
7 | poly: 50,
8 | ajax: 150,
9 | };
10 |
11 | const expected = {
12 | min: 50,
13 | max: 150,
14 | avg: 100,
15 | };
16 |
17 | expect(buildSalaryStatistics(employees)).toEqual(expected);
18 |
19 | employees.ironMan = 1000;
20 | expect(buildSalaryStatistics(employees)).toEqual({
21 | min: 50,
22 | max: 1000,
23 | avg: 325,
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/modules/50-objects/30-mapped-types/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/50-objects/30-mapped-types/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | const sanitize = (obj: T, keys: Array) => {
3 | const entries = Object.entries(obj).filter(([key]) => !keys.includes(key as K));
4 |
5 | return Object.fromEntries(entries) as Omit;
6 | };
7 | // END
8 |
9 | export default sanitize;
10 |
--------------------------------------------------------------------------------
/modules/50-objects/30-mapped-types/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `sanitize()`, которая принимает на вход объект и массив ключей. Также она должна возвращать новый объект, но уже без указанных полей.
3 |
4 | ```typescript
5 | const user = sanitize({
6 | name: 'John',
7 | password: '1q2w3e',
8 | token: 'test',
9 | }, ['password', 'token']); // { name: string }
10 |
11 | console.log(user); // => { name: 'John' }
12 | ```
13 |
14 | Обратите внимание, что в выходном типе также не должно быть этих полей.
15 |
--------------------------------------------------------------------------------
/modules/50-objects/30-mapped-types/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Сопоставление типов (Mapped Types)
3 | tips:
4 | - >
5 | [Официальная документация Mapped
6 | Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html)
7 | - >
8 | [Официальная документация
9 | keyof](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types)
10 | - >
11 | [Understanding "keyof
12 | typeof"](https://www.huy.rocks/everyday/04-14-2022-typescript-understanding-keyof-typeof-)
13 | - >
14 | [Официальная документация
15 | Pick](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys)
16 | - >
17 | [Официальная документация
18 | Omit](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys)
19 |
--------------------------------------------------------------------------------
/modules/50-objects/30-mapped-types/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import sanitize from './index';
4 |
5 | test('sanitize', () => {
6 | const obj = {
7 | name: 'John',
8 | age: 30,
9 | password: '123456',
10 | };
11 |
12 | expect(sanitize(obj, ['name', 'age'])).toEqual({
13 | password: '123456',
14 | });
15 |
16 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
17 | const user = sanitize(obj, ['password']);
18 |
19 | expectTypeOf(user).toMatchTypeOf<{ name: string; age: number }>();
20 |
21 | const params = {
22 | page: 1,
23 | limit: 10,
24 | filter: {
25 | name: 'John',
26 | },
27 | };
28 |
29 | const query = sanitize(params, ['filter']);
30 | expect(query).toEqual({
31 | page: 1,
32 | limit: 10,
33 | });
34 |
35 | expectTypeOf(query).toMatchTypeOf<{ page: number; limit: number, }>();
36 | });
37 |
--------------------------------------------------------------------------------
/modules/50-objects/35-mapping-modifiers/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/50-objects/35-mapping-modifiers/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | type DeepReadonly = {
3 | readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P];
4 | };
5 |
6 | const deepFreeze = (obj: T): DeepReadonly => {
7 | const freezedObj = Object.freeze(obj);
8 |
9 | Object.values(freezedObj).forEach((value) => {
10 | if (typeof value === 'object' && value !== null) {
11 | deepFreeze(value);
12 | }
13 | });
14 |
15 | return freezedObj;
16 | };
17 | // END
18 |
19 | export default deepFreeze;
20 |
--------------------------------------------------------------------------------
/modules/50-objects/35-mapping-modifiers/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 | Реализуйте функцию `deepFreeze()`, которая принимает на вход объект и делает его самого, его поля и все вложенные объекты неизменяемыми и возвращает этот объект.
2 | Предполагается что поля объекта и поля вложенных объектов не содержат массивы, только простые типы данных и объекты.
3 |
4 | ```typescript
5 | const user = deepFreeze({
6 | name: 'John',
7 | password: '1q2w3e',
8 | token: 'test',
9 | });
10 |
11 | user.name = 'Alex'; // Error: Cannot assign to 'name' because it is a read-only property.
12 | ```
13 |
14 | Нужно использовать встроенный в JavaScript метод [Object.freeze()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze).
15 |
--------------------------------------------------------------------------------
/modules/50-objects/35-mapping-modifiers/ru/README.md:
--------------------------------------------------------------------------------
1 | При сопоставлении типов можно менять атрибуты свойств такие как неизменность (immutability) и необязательность (optionality). Делается это с помощью соответствующих модификаторов: `readonly` и `?`.
2 |
3 | Чтобы добавить или удалить эти модификаторы, можно использовать префиксы `+` или `-`. Если не использовать префикс, то подразумевается что модификатор будет добавлен, то есть по умолчанию префикс `+`.
4 |
5 | Примеры использования модификаторов есть в Utility Types:
6 |
7 | ```typescript
8 | /**
9 | * Делает все свойства типа `T` необязательными,
10 | * то есть добавляет атрибут `?`.
11 | */
12 | type Partial = {
13 | [P in keyof T]?: T[P];
14 | };
15 |
16 | /**
17 | * Делает все свойства типа `T` обязательными,
18 | * то есть удаляет атрибут `?`.
19 | */
20 | type Required = {
21 | [P in keyof T]-?: T[P];
22 | };
23 |
24 | /**
25 | * Делает все свойства типа `T` неизменяемыми,
26 | * то есть добавляет атрибут `readonly`.
27 | */
28 | type Readonly = {
29 | readonly [P in keyof T]: T[P];
30 | };
31 | ```
32 |
33 | Подобным образом можно написать и тип, который делает все свойства типа изменяемыми, то есть удаляет атрибут `readonly`:
34 |
35 | ```typescript
36 | type Mutable = {
37 | -readonly [P in keyof T]: T[P];
38 | };
39 | ```
40 |
41 | Благодаря таким типам легче делать производные типы из уже имеющихся.
42 |
43 | Например, в приложении может быть тип `User` для не авторизованного пользователя у которого все поля не обязательные:
44 |
45 | ```typescript
46 | type User = {
47 | id?: string;
48 | firstName?: string;
49 | secondName?: string;
50 | email?: string;
51 | };
52 | ```
53 |
54 | Из него можно сделать авторизованного пользователя с помощью типа `Required`:
55 |
56 | ```typescript
57 | type AuthorizedUser = Required;
58 | ```
59 |
--------------------------------------------------------------------------------
/modules/50-objects/35-mapping-modifiers/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Модификаторы сопоставления типов (Mapping Modifiers)
3 | tips:
4 | - >
5 | [Официальная документация Mapped
6 | Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html)
7 | - >
8 | [Официальная документация Mapping
9 | Modifiers](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers)
10 | - >
11 | [Официальная документация
12 | Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)
13 | - >
14 | [Официальная документация
15 | Required](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype)
16 | - >
17 | [Официальная документация
18 | Readonly](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype)
19 |
--------------------------------------------------------------------------------
/modules/50-objects/35-mapping-modifiers/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import deepFreeze from './index';
4 |
5 | test('deepFreeze', () => {
6 | const obj = {
7 | name: 'John',
8 | age: 30,
9 | location: {
10 | city: 'York',
11 | coordinates: {
12 | lat: 53.958,
13 | lon: -1.093,
14 | },
15 | },
16 | };
17 |
18 | const user = deepFreeze(obj);
19 |
20 | expect(user).toEqual({
21 | name: 'John',
22 | age: 30,
23 | location: {
24 | city: 'York',
25 | coordinates: {
26 | lat: 53.958,
27 | lon: -1.093,
28 | },
29 | },
30 | });
31 |
32 | expect(() => {
33 | // @ts-expect-error Cannot assign read-only property.
34 | user.age = 20;
35 | }).toThrow();
36 |
37 | expect(() => {
38 | // @ts-expect-error Cannot assign nested read-only property.
39 | user.location.city = 'London';
40 | }).toThrow();
41 |
42 | expectTypeOf(user).toMatchTypeOf,
51 | }>,
52 | }>>();
53 | });
54 |
--------------------------------------------------------------------------------
/modules/50-objects/45-record/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @ test2.sh
3 |
--------------------------------------------------------------------------------
/modules/50-objects/45-record/index.ts:
--------------------------------------------------------------------------------
1 | // BEGIN
2 | type Property = string | number | symbol;
3 |
4 | const createAccessChecker = (
5 | (permissions: Record>) => (
6 | (role: Roles, resource: Resource) => permissions[role].includes(resource)
7 | )
8 | );
9 | // END
10 |
11 | export default createAccessChecker;
12 |
--------------------------------------------------------------------------------
/modules/50-objects/45-record/ru/EXERCISE.md:
--------------------------------------------------------------------------------
1 |
2 | Реализуйте функцию `createAccessChecker()`, которая принимает на вход объект с разрешениями для ролей и возвращает функцию, проверяющую, есть ли у пользователя доступ к ресурсу.
3 |
4 | ```typescript
5 | type UserRole = 'admin' | 'user' | 'guest';
6 | type UserResource = 'document' | 'user' | 'adminPanel';
7 |
8 | const userRolePermissions: Record> = {
9 | admin: ['document', 'user', 'adminPanel'],
10 | user: ['document', 'user'],
11 | guest: ['document'],
12 | };
13 |
14 | const checkUserAccess = createAccessChecker(userRolePermissions);
15 |
16 | const isAdminAllowed = checkUserAccess('admin', 'adminPanel');
17 | console.log(isAdminAllowed); // => true
18 |
19 | const isUserAllowed = checkUserAccess('user', 'adminPanel');
20 | console.log(isUserAllowed); // => false
21 | ```
22 |
--------------------------------------------------------------------------------
/modules/50-objects/45-record/ru/README.md:
--------------------------------------------------------------------------------
1 |
2 | Объекты с динамической структурой, когда мы добавляем в них поля во время исполнения программы, часто используются для построения контекста или хранения данных. Напишем вспомогательный тип для построения такого объекта:
3 |
4 | ```typescript
5 | type Context = {
6 | [Key in K]: V;
7 | }
8 |
9 | const runApp = >(ctx: C) => {};
10 | ```
11 |
12 | Ключ `Key` примет перебором все значения из `K`. В свою очередь `K` является подмножеством `string`, а `V` может быть любым. Таким образом здесь мы создали свой тип `Context` со строковыми полями и неизвестным типом для значения.
13 |
14 | Подобная конструкция, когда мы не задаем дополнительно никаких специфичных полей с динамическими ключами, встречается довольно часто. Встроенные Utility Types предоставляют для этого готовое решение — `Record`. Этот обобщенный тип принимает первым аргументом тип ключа, а вторым — тип значения. Внутри все устроено схожим образом, как в нашем типе `Context`:
15 |
16 | ```
17 | type Rating = 0 | 1 | 2 | 3 | 4 | 5;
18 | type SongsRating = Record;
19 |
20 | const songsRating: SongsRating = {
21 | ratata: 4,
22 | }
23 | ```
24 |
25 | Таким типом `SongsRating` мы можем задать тип объекта с произвольным ключом (именем песни) и рейтингом — числом от нуля до пяти.
26 |
27 | `Record` — более предпочтителен при описании объектных типов в TypeScript. Это позволяет гибко и лаконично описывать динамические структуры и использовать `Record` совместно с другими типами данных.
28 |
--------------------------------------------------------------------------------
/modules/50-objects/45-record/ru/data.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Записи (Record)
3 | tips:
4 | - >
5 | [Record в официальной
6 | документации](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)
7 |
--------------------------------------------------------------------------------
/modules/50-objects/45-record/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, expectTypeOf } from 'vitest';
2 |
3 | import createAccessChecker from './index';
4 |
5 | test('function', () => {
6 | type UserRole = 'admin' | 'user' | 'guest';
7 | type UserResource = 'document' | 'user' | 'adminPanel';
8 |
9 | const userRolePermissions: Record> = {
10 | admin: ['document', 'user', 'adminPanel'],
11 | user: ['document', 'user'],
12 | guest: ['document'],
13 | };
14 |
15 | const checkUserAccess = createAccessChecker(userRolePermissions);
16 |
17 | const isAdminAllowed = checkUserAccess('admin', 'adminPanel');
18 | expect(isAdminAllowed).toBe(true);
19 |
20 | const isUserAllowed = checkUserAccess('user', 'adminPanel');
21 | expect(isUserAllowed).toBe(false);
22 |
23 | expectTypeOf(checkUserAccess).parameters.toMatchTypeOf<[UserRole, UserResource]>();
24 | });
25 |
--------------------------------------------------------------------------------
/modules/50-objects/description.ru.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | name: Типизация объектов
4 | description: |
5 | Объекты в TypeScript можно описать с помощью интерфейсов и типов. В этом разделе мы рассмотрим особенности типизации объектов, получением динамических свойств и особенностями определения объектных типов.
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hexlet-basics",
3 | "scripts": {
4 | "test": "vitest"
5 | },
6 | "type": "module",
7 | "dependencies": {
8 | "sinon": "^19.0.2"
9 | },
10 | "devDependencies": {
11 | "@eslint/js": "^9.21.0",
12 | "@swc/jest": "^0.2.37",
13 | "@types/jest": "^29.5.14",
14 | "@types/sinon": "^17.0.4",
15 | "@typescript-eslint/parser": "^8.25.0",
16 | "eslint": "^9.21.0",
17 | "typescript": "^5.7.3",
18 | "typescript-eslint": "^8.25.0",
19 | "vite": "^6.2.0",
20 | "vitest": "^3.0.7"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/spec.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | language:
4 | docker_image: "hexletbasics/exercises-typescript"
5 | extension: ts
6 | exercise_filename: index.ts
7 | exercise_test_filename: test.ts
8 | learn_as: second_language
9 | progress: in_development
10 | name: TypeScript
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "module": "esnext",
5 | "target": "esnext",
6 | "strict": true,
7 | "moduleResolution": "node",
8 | "allowSyntheticDefaultImports": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 |
3 | export default defineConfig({
4 | test: {
5 | include: ['**/*.test.ts', '**/*.spec.ts', '**/test.ts'],
6 | },
7 | })
8 |
--------------------------------------------------------------------------------