├── .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 | [![Github Actions Status](../../workflows/Docker/badge.svg)](../../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 | [![Hexlet Ltd. logo](https://raw.githubusercontent.com/Hexlet/assets/master/images/hexlet_logo128.png)](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 | ![NumberOrString](../assets/number_or_string.png) 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 | ![NumberOrString](../assets/one_hundred_order.png) 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 | --------------------------------------------------------------------------------