Даниил Шило

+298
с 2022

Frontend Engineer в Firecode

37 подписчиков
6 подписок

Стоит начать, пожалуй, с того что в текущих реалиях большинству людей в моем окружении тяжело сконцентрироваться на всех задачах которые нужно сделать за весь день: уведомления из чатов и новостных каналов, «задачи» которые присылаются в мессенджере, вместо нормального тикета в Jira/Asana/<любой другой таск-менеджер>, просьбы коллег, а также…

1

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

Сегодня рассмотрим этим паттерны, поговорим почему это важно и зачем это нужно.

6
1

Playwright - фреймворк для E2E-тестирования приложений, он хорошо интегрируется с любыми приложениями и сайтами, предлагает большой ассортимент для кастомизации.

Установка

3
👾 Автогенерация превью с помощью Satori

В данной статье рассказывается об автогенерации превью с помощью Satori.

2
Сокращение путей с помощью алиасов

В данной статье рассмотрим как с помощью алиасов сократить пути для импортов. Это бывает полезно, когда вы работаете в больших проектах.

4

Svelte — веб-фреймворк, который отличается принципом работы от React и Vue.

React и Vue формируют приложение прямо в браузере, когда пользователь открывает необходимый ему ресурс, Svelte же заранее компилирует исходный код и предоставляет часть приложения статичной версткой, а затем гидрирует приложение, благодаря чему приложение получается б…

17

TypeScript — язык программирования, представленный Microsoft в 2012 году и позиционируемый как средство разработки веб-приложений, расширяющее возможности JavaScript

TypeScript стал очень популярным проектом. Если ранее на нем писал преимущественно веб-приложения, то сейчас пишут даже логику для сайтов. Данный гайд затронет все основные темы…

46
1

webpack — это сборщик модулей JavaScript с открытым исходным кодом. Он создан в первую очередь для JavaScript, но может преобразовывать внешние ресурсы, такие как HTML, CSS и изображения, если включены соответствующие загрузчики. webpack принимает модули с зависимостями и генерирует статические ресурсы, представляющие эти модули.

Крупный гайд по Webpack
42
1

Сегодня поговорим о том как работает WS, напишем простенький клиент на JS, обсудим как дебажить данный протокол ну и просто обсудим несколько интересных фактов. Погнали😈

WebSocket: смотрим как работает за кулисами
15

zsh — одна из современных командных оболочек UNIX, использующаяся непосредственно как интерактивная оболочка, либо как скриптовый интерпретатор. Zsh является расширенным аналогом, а также имеет обратную совместимость с bourne shell, имея большое количество улучшений.

Wikipedia

Иметь красивый и удобный шелл для человека, который ведет разработку…

21
","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Вместо заключения 🌚"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если вам понравилась данная статья - то вы всегда можете перейти в мой блог, там больше схожей информации о веб-разработке.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если у вас остались вопросы - не стесняйтесь задавать их в комментариях. Хорошего времяпрепровождения! 💁🏻‍♂

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":0,"favorites":1,"reposts":0,"views":1049,"hits":550,"reads":null,"online":0},"dateFavorite":0,"hitsCount":550,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/661531-avtogeneraciya-prevyu-s-pomoshyu-satori","author":{"id":1178100,"name":"Даниил Шило","nickname":null,"description":"Frontend Engineer в Firecode","uri":"","avatar":{"type":"image","data":{"uuid":"59e8fb72-4a49-5932-af48-bdb368b827e7","width":400,"height":400,"size":28098,"type":"png","color":"dfcac6","hash":"73610f6d350e00","external_service":[]}},"cover":{"cover":{"type":"image","data":{"uuid":"ab766887-ce9f-5181-b1e9-e877385348cb","width":5120,"height":2160,"size":561896,"type":"jpg","color":"85adba","hash":"","external_service":[]}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4265614,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4265614"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":642080,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/642080"}],"lastModificationDate":1764920033,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":2}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":661503,"customUri":null,"subsiteId":1178100,"title":"Сокращение путей с помощью алиасов","date":1681145935,"dateModified":1681145935,"blocks":[{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"468a4869-3614-5102-9b0d-e81c4d85efd4","width":4096,"height":2560,"size":136200,"type":"webp","color":"2e0f85","hash":"","external_service":[]}}}]}},{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"

В данной статье рассмотрим как с помощью алиасов сократить пути для импортов. Это бывает полезно, когда вы работаете в больших проектах.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы рассмотрим сокращение путей в Typescript, Vite, Webpack, Rollup и ESBuild, то есть во всех популярных бандлерах для JavaScript.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

С помощью алиасов можно повысить читаемость import. Вот пример как могут выглядеть плохие импорты:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"import Data from '../../../data/someData';","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот пример как выглядят хорошие импорты:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"import Data from '@data/someData'","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

К слову, все примеры внизу будут соответствовать листингу сверху. То есть мы будем рассматривать примеры конфигураций, где вместо того чтобы писать ../../../data/someData у нас была возможность написать @data/someData.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🧱 Typescript"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fwww.typescriptlang.org%3Fref%3Dvc.ru&postId=661503","title":"JavaScript With Syntax For Types.","description":"TypeScript extends JavaScript by adding types to the language. TypeScript speeds up your development experience by catching errors and providing fixes before you even run your code.","image":{"type":"image","data":{"uuid":"c01d8f26-fb40-5e03-ac79-2d422046586a","width":512,"height":512,"size":3668,"type":"png","color":"347cc4","hash":"","external_service":[]}},"v":1,"hostname":"www.typescriptlang.org"}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для того чтобы Typescript понимал алиасы - нам достаточно добавить следующие поля в tsconfig.json.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

tsconfig.json:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"{\n // Добавляем директорию откуда будут строиться пути\n \"baseUrl\": \".\",\n // Добавляем алиасы для путей\n \"paths\": {\n \"@/*\": [\"src*\"],\n \"@data/*\": [\"src/data/*\"]\n }\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь мы можем импортировать файлы из корневой директории с помощью @ и файлы из папки src/data с помощью @data.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"import someData from \"@/data/someData\";\n// Или\nimport someData from \"@data/someData\";","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"⚡ Vite"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fvitejs.dev%3Fref%3Dvc.ru&postId=661503","title":"Vite","description":"Next Generation Frontend Tooling","image":{"type":"image","data":{"uuid":"https://leonardo.osnova.io/ico/vitejs.dev","width":0,"height":0,"size":0,"type":"jpg","color":"","hash":"","external_service":[]}},"v":1,"hostname":"vitejs.dev"}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если мы используем Vite без Typescript, то нам нужно указать алиасы в конфигурации Vite.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

vite.config.js:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"import {fileURLToPath} from \"url\";\nimport {defineConfig} from 'vite';\nexport default defineConfig({\n // ...\n resolve: {\n alias: [\n { find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },\n { find: '@data', replacement: fileURLToPath(new URL('./src/data', import.meta.url)) },\n ],\n },\n // ...\n})","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"💨 Webpack"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fwebpack.js.org%3Fref%3Dvc.ru&postId=661503","title":"webpack","description":"webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.","image":{"type":"image","data":{"uuid":"968e0c2f-bec9-51e8-99c1-080a0072e937","width":512,"height":512,"size":10302,"type":"png","color":"8cd3fb","hash":"","external_service":[]}},"v":1,"hostname":"webpack.js.org"}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если же мы собираем продукт на Webpack, то нам нужно написать следующее в конфигурации Webpack:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

webpack.config.js:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const path = require('path');\nmodule.exports = {\n //...\n resolve: {\n alias: {\n \"@\": path.resolve(__dirname, 'src/'),\n \"@data\": path.resolve(__dirname, 'src/data/'),\n },\n },\n // ...\n};","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🦀 Rollup"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=http%3A%2F%2Frollupjs.org%3Fref%3Dvc.ru&postId=661503","title":"Rollup","description":"The JavaScript module bundler","image":{"type":"image","data":{"uuid":"3a2a5d23-c15f-540a-aa00-507f9eba7168","width":128,"height":128,"size":5197,"type":"png","color":"fbac4b","hash":"","external_service":[]}},"v":1,"hostname":"rollupjs.org"}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для того чтобы использовать алиасы в Rollup нам нужен плагин. Плагин можно установить следующей командой:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"npm i -D @rollup/plugin-alias","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Далее пишем следующий код в конфигурации:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

rollup.config.js:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"import alias from '@rollup/plugin-alias';\nmodule.exports = {\n // ...\n plugins: [\n alias({\n entries: [\n { find: '@', replacement: 'src/' },\n { find: '@data', replacement: 'src/data/' }\n ]\n })\n ]\n // ...\n};","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🌝 ESBuild"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fesbuild.github.io%3Fref%3Dvc.ru&postId=661503","title":"esbuild - An extremely fast bundler for the web","description":"","image":{"type":"image","data":{"uuid":"https://leonardo.osnova.io/ico/esbuild.github.io","width":0,"height":0,"size":0,"type":"jpg","color":"","hash":"","external_service":[]}},"v":1,"hostname":"esbuild.github.io"}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для того чтобы добавить алиасы к ESBuild нужно установить плагин:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"npm i -D esbuild-plugin-alias","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Затем нужно добавить следующее в конфигурацию:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const esbuild = require('esbuild');\nconst alias = require('esbuild-plugin-alias');\nesbuild.build({\n // ...\n plugins: [\n alias({\n '@': path.resolve(__dirname, `./src/*`),\n '@data': path.resolve(__dirname, `./src/data/*`),\n }),\n ],\n // ...\n});","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Вместо заключения 🌚"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если вам понравилась данная статья - то вы всегда можете перейти в мой блог, там больше схожей информации о веб-разработке.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если у вас остались вопросы - не стесняйтесь задавать их в комментариях. Хорошего времяпрепровождения!💁🏻‍♂

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":0,"favorites":3,"reposts":0,"views":1273,"hits":6138,"reads":null,"online":0},"dateFavorite":0,"hitsCount":6138,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/661503-sokrashenie-putei-s-pomoshyu-aliasov","author":{"id":1178100,"name":"Даниил Шило","nickname":null,"description":"Frontend Engineer в Firecode","uri":"","avatar":{"type":"image","data":{"uuid":"59e8fb72-4a49-5932-af48-bdb368b827e7","width":400,"height":400,"size":28098,"type":"png","color":"dfcac6","hash":"73610f6d350e00","external_service":[]}},"cover":{"cover":{"type":"image","data":{"uuid":"ab766887-ce9f-5181-b1e9-e877385348cb","width":5120,"height":2160,"size":561896,"type":"jpg","color":"85adba","hash":"","external_service":[]}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4265614,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4265614"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":642080,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/642080"}],"lastModificationDate":1764920033,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":4}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":605614,"customUri":null,"subsiteId":1178100,"title":"Крупный гайд по Svelte","date":1676066128,"dateModified":1676066128,"blocks":[{"type":"quote","cover":true,"hidden":false,"anchor":"","data":{"text":"

Svelte — веб-фреймворк, который отличается принципом работы от React и Vue.

","subline1":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

React и Vue формируют приложение прямо в браузере, когда пользователь открывает необходимый ему ресурс, Svelte же заранее компилирует исходный код и предоставляет часть приложения статичной версткой, а затем гидрирует приложение, благодаря чему приложение получается быстрым, простым в отладке и надежным😌

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

У Svelte нет Virtual DOM, то есть он напрямую работает с DOM в браузере и изменяет его по мере необходимости.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Установка"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Использовать сам фреймворк можно с помощью нескольких способов:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["С помощью Vite","С помощью npm create svelte@latest
"],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Синтаксис"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Каждый компонент в Svelte — SFC (Single File Component). Как и во Vue или React внутри SFC можно разместить:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Стили","Верстку","Логику"],"type":"UL"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n\n\n","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Все что находится внутри <script> выполняется во время создания компонента.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Внутри <script> также есть дополнительные правила, которые будет рассмотрены ниже.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Важно помнить что Svelte — фреймворк, который компилирует исходный код, а значит у Svelte может быть дополнительные значения у привычных для нас вещей (напр. export, который описан ниже)

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Состояния"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Состояния внутри Svelte объявляются с помощью let и const. Все верно, каждая переменная объявленная в глобальном скоупе является состоянием🫡

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На случай, если это ваш первый реактивный фреймворк:
Состояния нужны для обновления интерфейса. Как только состояние обновляется — обновляется и интерфейс приложения, который показывает актуальные данные.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Шаблоны"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Шаблон компонента можно писать прямо в файле .svelte:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n
\n \n
","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Интерполяция"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для того чтобы использовать состояния внутри шаблона нужно укзаать на имя состояния в фигурных скобках:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n

Hello, { name }

\n","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n
\n

Пользователь старше 18 лет?

\n { age > 18 } \n
","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Также мы можем вставлять HTML с помощью модификатора @html:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n{@html htmlCode}","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Атрибуты"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Интерполяция работает с атрибутами точно также, как и с текстом внутри элемента:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n\"Default\n\n\"Default","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Условный рендеринг"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В HTML нет условий, в Svelte они есть. Использовать условный рендеринг можно следующим образом:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n
\n {#if hasButton}\n \n\t \n\n {:else}\n \n
Тут нет кнопки :(
\n {/if}\n
","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Также мы можем использовать {:else if <условие>}, для того чтобы задать дополнительный логический блок. Прямо как `else if` в JS👀

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Цикличный рендеринг"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Также как и с условным рендерингом — в Svelte есть цикличный рендеринг. Он позволяет проходится по спискам и для каждого элемента рендерить одну и ту же верстку:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n
\n Список имен:\n \n
","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Данный кусок кода выведет следующий список в HTML:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"
\n Список имен:\n \n
","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Изменяемый список"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В случае если список изменяется — нужно дать каждом элементу id, для того чтобы Svelte лучше управлялся с такими элементами и рендерил все правильно:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n
\n Список имен:\n \n
","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Асинхронные блоки"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Svelte позволяет нам отрисовывать разный шаблон по мере жизнедеятельности промиса:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n\n\n\n\n{#await promise}\n

Данные запрашиваются

\n{:then data}\n\n {data}\n\n{:catch error}\n

\n Данные не получены :(\n

\n{/await}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Обратите внимание, что:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["# в начале названия блока используется для объвления блока
",": в начале названия блока используется для того чтобы продлить блок","/ в начале названия блока используется для того чтобы закрыть блок"],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Импортирование компонентов"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Можно импортировать компоненты в другие компоненты:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Кастомные ивенты"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Иногда нам нужны кастомные ивенты, которые бы тригерили функции извне компонента. В Vue для этого есть emit, в Svelte — eventDispatcher 💁🏻‍♂

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Допустим, что у родительского компонента нет доступа к состояниям дочернего компонента. Но, мы все же хотим получить оттуда какие-либо данные. Для этого мы создаем коллбэк, который будет с этими данными взаимодействовать (допустим выводить их с помощью alert):

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n\n","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В дочернем компоненте мы при нажатии на кнопку будем посылать сообщение в родительский компонент:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n\n","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь при клике на кнопку мы создадим специальный ивент, который вызовет коллбэк handleMessage из родительского компонента и передаст ему все данные, которые нам нужны 🙌

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

> К слову мы могли назвать ивент как хотим. Важно, чтобы первый аргумент в dispatch() и слово после on: совпадали.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Всплытие"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кастомные ивенты не всплывают, но если нам нужно, чтобы они всплывали, то у каждого дочернего компонента в иерархии нужно указывать `on:message`. Это довольно редкий кейс, однако подробнее можно узнать тут.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Биндинги"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Биндинги существуют для того чтобы облегчать двухстороннее связывание 🥂

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Двухстороннее связывание — техника, которую в основном применяют к инпутам. Она связывает value и событие oninput, таким образом чтобы пользовательский ввод все время обновлял value.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В Svelte это выглядит следующим образом:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n\n\n

{ inputValue }

","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Однако, все данные действия можно сделать намного проще с помощью инпутов:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n\n\n\n

{ inputValue }

","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Хуки"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Хуки — это методы, которые цепляются за жизненный цикл компонента🪝

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В Svelte существует следующие хуки:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["onMount — хук, который выполняется как только компонент примонтировался к DOM.","beforeUpdate — хук, который работает перед тем как в компонент придут новые данные (например из пропсов)","afterUpdate — хук, который работает после того, как в компонент пришли новые данные
","onDestroy — хук, который работает когда компонент размонтируется (удаляется из DOM)
"],"type":"UL"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Обычно хук onMount используют для того, чтобы подтянуть какие-то данные со сторонних сервисов с помощью fetch и использовать их внутри компонента.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если использовать SvelteKit (для рендеринга на стороне сервера) и расположить fetch внутри onMount, то данные будут запрашиваться на стороне клиента, а если просто внутри <script>, то запрос отправится еще на сервере.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Тик"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Все фреймворки для создания веб-приложений работают следующим образом:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

1. Сбор операций которые нужно выполнить пачкой

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

2. Подбор времени для выполнения

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

3. Оптимизация собраного стека

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

4. Выполнение этих операций (тик)

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В Svelte тоже есть тики. Например, если мы используем специальный метод tick внутри хука beforeUpdate, то после выполнения мы уже будем находиться на ивенте afterUpdate:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Сторы"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как уже понятно — мы можем объявлять состояния внутри компонентов. Однако бывают ситуации, когда нам нужно использовать глобальные состояния, при обновлении которых компоненты также будут обновляться (перерендериваться). Для этого сторы и придумали.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Самый простой стор, который можно придумать — счетчик, однако в сторы можно запихнуть и сложных структуры данных, тут стор используется для примера.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Допустим что у нас есть два разных компонента:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["В одном из них мы управляем нашим счетчиком","В другом мы используем значения из этого счетчика
"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы можем объявлять счетчики вне компонентов в отдельных файлах JS/TS:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"import {writable} from 'svelte';\nexport const counter = writable(0);","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В данном компоненте мы будем управлять счетчиком:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n\n\n","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В данном компоненте мы будем отрисовывать счетчик:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n

Счетчик: { count }

","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Чтобы не делать этого мы можем просто использовать стор добавив к его названию $$counter.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Заключение"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Еще обязательно увидимся в следующих гайдах и туториалах ❤

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":31,"favorites":39,"reposts":0,"views":15616,"hits":17821,"reads":null,"online":0},"dateFavorite":0,"hitsCount":17821,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/605614-krupnyi-gaid-po-svelte","author":{"id":1178100,"name":"Даниил Шило","nickname":null,"description":"Frontend Engineer в Firecode","uri":"","avatar":{"type":"image","data":{"uuid":"59e8fb72-4a49-5932-af48-bdb368b827e7","width":400,"height":400,"size":28098,"type":"png","color":"dfcac6","hash":"73610f6d350e00","external_service":[]}},"cover":{"cover":{"type":"image","data":{"uuid":"ab766887-ce9f-5181-b1e9-e877385348cb","width":5120,"height":2160,"size":561896,"type":"jpg","color":"85adba","hash":"","external_service":[]}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4265614,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4265614"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":642080,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/642080"}],"lastModificationDate":1764920033,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":17}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":423888,"customUri":null,"subsiteId":1178100,"title":"Крупный гайд по TypeScript","date":1652809350,"dateModified":1652809350,"blocks":[{"type":"quote","cover":true,"hidden":false,"anchor":"","data":{"text":"

TypeScript — язык программирования, представленный Microsoft в 2012 году и позиционируемый как средство разработки веб-приложений, расширяющее возможности JavaScript

","subline1":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

TypeScript стал очень популярным проектом. Если ранее на нем писал преимущественно веб-приложения, то сейчас пишут даже логику для сайтов. Данный гайд затронет все основные темы TypeScript, статья достаточно большая, чтобы пройти её за одно чтение, так что советую сразу же добавить её в закладки, если захотите дочитать позже😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🤔 Начинать стоит с проблемы"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте поговорим о том, почему TypeScript вообще появился и почему так быстро стал популярен?🤔

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Рассмотрим преимущества TypeScript:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Статическая типизация. Кто-то легко может засунуть строку вместо числа, там где числа быть не должно. Проблема давняя и её видал наверное каждый разработчик. Без статической типизации у языка появляется много проблем, когда продукт начинает расти.","

Компиляция. TypeScript компилируется и проверяет ошибки. Это позволяет избежать некоторых ошибок ещё до проверки работоспособности самого решения! Если вы написали что-то не так, то TS сразу даст вам знать😊

","

Хорошая документация. TypeScript хорошо задокументирован. Чтобы вы не пытались найти, вы наверняка найдете это в официальной документации.

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

Полноценное ООП. Разработчики TypeScript смогли сделать то, что давно не могли сделать в JavaScript: сделать полноценное ООП с типами, интерфейсами, перечислениями и многим другим

"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте плавно перейдем к обучению😊 Мы начнем с самого начала и закончим достаточно сложными темами, а по пути рассмотрим аспкеты данного ЯП, которые могут быть интересны многим😳

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Спойлер: В статье очень много картинок. Если пользуетесь мобильным интернетом, то не тратьте лишний трафик и добавьте в \"Прочитать позже\"😉

"}},{"type":"incut","cover":false,"hidden":false,"anchor":"","data":{"text":"

Настоятельная рекомендация

Для того чтобы лучше увидеть код, просто нажмите на картинку, она увиличится и будет в высоком разрешении😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🧱 1. Встроенные типы"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Основным \"строительным материалом\" в TypeScript является тип. К слову TypeScript потому так и называется, потому что у него есть типы. Они созданы для того, чтобы заранее указать тип переменной и строго соблюдать его. В TypeScript есть встроенные типы, в основном мы будем пользоваться тремя: string, number, boolean. Мы будем разбирать все основы достаточно динамично, как минимум потому что в документации данные основы описаны более чем понятно. Те, кто уже знаком с JavaScript поймут код, так как он похож как две капли воды, единственное что может показаться необычным — то, что мы везде цепляем двоеточие с типом, но вскоре чувство инородности такой записи уйдет😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Типы в TypeScript объявляются следующим синтаксисом

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"переменная: тип = значение","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы можем присвоить любой тип, который есть в JavaScript:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Явно присваиваем типы переменным","image":{"type":"image","data":{"uuid":"b494cb5d-ad3e-5f4b-b96a-aee3b97545bc","width":4184,"height":1352,"size":91479,"type":"png","color":"323a49","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Типы в TypeScript работают как и в любом строготипизированном языке. Если вы указали тип у переменной и присвоили ей несоответствующий тип, то компилятор выдаст ошибку и будет ругаться🤬

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как мы видим запись типов достаточно легкая. Такой подход позволяет явно указать тип переменной и строготипизировать её. Однако, TypeScript сам умеет определять типы. Если мы объявим переменную и сразу присвоим ей значение, то TypeScript все поймет и запись с двоеточием можно будет опустить:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"TypeScript сам умеет определять типы исходя из инициализированного значения","image":{"type":"image","data":{"uuid":"06897e55-5463-5b5f-9590-34ff61027346","width":4184,"height":980,"size":93439,"type":"png","color":"303847","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Интересным фактом является то, что TypeScript, а точнее автодополнение, которое работает на tsserver (специальный сервер, который является LSP и будет давать вам подсказки по улучшению кода и автозавершению) будет ориентироваться именно на тип данных (кто бы мог подумать🗿). Именно поэтому например при попытке написать 123.toLowerCase(); вы не увидите никакой подсказки, а сам TypeScript подскажет вам, что вы что-то попутали👹 перепутали тип данных.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🤲 1.1. Объединение"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: На такой кейс придумано объединение типов😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Типы можно объединять, сам процесс так и называется объединение. Записывается он так:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"переменная: тип1 | тип2 = значение;","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Такой подход позволяет присвоить значение, которое валидно для первого или второго перечисленного типа. Перечислений в свою очередь может быть сколь угодно много, главное чтобы вы ставили знак объединения множеств (|).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На данный код внизу компилятор не выдаст ошибку:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Объединяем типы","image":{"type":"image","data":{"uuid":"8b44e596-ca89-567d-9ba8-e773d23d81b7","width":4184,"height":980,"size":73541,"type":"png","color":"2f3747","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Минусом объединения является то, что TypeScript не даст нам использовать методы, которые например есть только у number, или только у string. Сам компилятор не будет знать что находится внутри переменной и будет жаловаться, что мы делаем что-то не то (пытаемся вызвать метод, которого нет у типа number | string).

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"📦 1.2. Встроенные типы и неявная типизация"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос: Но как типизировать переменную, которая например содержит дату? Что нам с ней делать?!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: Для таких случаев в TypeScript уже существуют встроенные типы из JavaScript.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы можем объявить дату вот так:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"c3e2c637-e7ea-57f0-a18a-3369cbc50e89","width":4184,"height":908,"size":63781,"type":"png","color":"97a2ac","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как это работает? Просто инициализируйте переменную, при инициализации TypeScript сам подберет нужный тип. Учтите, что при инициализации типа — тип в будущем просто так нельзя будет поменять (без спец. ключевых знаков и слов).

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"c0031bab-abc7-5721-9eed-014f0f60bd80","width":4184,"height":908,"size":61437,"type":"png","color":"97a2ac","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Тот же код, что и вверху. TypeScript сам найдет тип Date и присвоит его переменной🤩 Таким образом нам вообще ничего не нужно запоминать если типы заранее объявлены, то всю работу TypeScript сделает за нас😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Отлично! Мы уже разобрали типы, объединение типов, встроенные типы и неявное объявление типов! Все самое интересное впереди😎

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🤔 2. Типизация функций"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос: Мы научились типизировать переменные, но как типизировать функцию?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: Типизация функции построена точно также, как и типизация переменных. Мы просто указываем типы через двоеточие😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Функции типизируются достаточно легко. Давайте подумаем, что такое функция?

"}},{"type":"incut","cover":false,"hidden":false,"anchor":"","data":{"text":"

Функция — блок кода, который принимает входные значения и отдает выходные значения. Часто она может ничего не принимать и что-то отдавать или наоборот принимать аргументы и ничего не отдавать вызывая коллбэки внутри себя.

В целом все что нам нужно понять, это то, что у функции почти всегда есть параметры и выходное значение😌

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Соответственно, исходя из определения, над которым мы с вами только что подумали — нам нужно типизировать входные и данные и данные которые нам функция отдает (выходные).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Синтаксис достаточно легкий:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"function имя(параметр: тип, параметр: тип, ...): тип выходного значения {}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте рассмотрим пример, где функция принимает два аргумента и отдает нам их сумму:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Объявим и типизируем функцию","image":{"type":"image","data":{"uuid":"633886ff-d8d0-51dd-8b87-808af38b463c","width":4184,"height":1352,"size":104010,"type":"png","color":"323a49","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как мы видим параметрами являются числа (два слагаемых) и выходным значением функции является сумма (тоже число).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте немного разнообразим наш пример. Теперь функция будет принимать имя и количество его повторений и отдавать строку с повторяющимся именем:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"d047ee63-5523-5f28-8744-53b0f0975453","width":4184,"height":1352,"size":107874,"type":"png","color":"323a49","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как мы видим, тут первый параметр - строка, второй - число и отдается строка. Все достаточно просто😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"◀ 2.1. Стрелочные функции"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Синтаксис для стрелочных функций нисколько не меняется. Просто теперь мы добавляем типы, вот и все😊

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"862f8aac-ded5-5fec-84a0-f967f33dc7e8","width":4184,"height":1428,"size":109408,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"😜 2.2. Особые типы в TypeScript (void, never)"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В TypeScript также есть два особенных типа, которые используются с функциями — void и never. Разберем их по очереди.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

void — пустотный тип. Это тип, который используется для выходного значения функции, когда она ничего не возвращает.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Хороший вопрос: Зачем же нам указывать тип функции, если она ничего не возвращает?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Не менее хороший ответ😅: Все функции в JavaScript что-то возвращают ожидаете вы этого или нет..

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Что я имею ввиду? Давайте выполним код в консоли:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Создадим пустую функцию","Выполним его"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Можно открыть консоль в DevTools, а можно зайти на https://jsconsole.com и попробовать поэксперементировать там. Не имеет значение, ведь JS ясное дело везде отработает одинаково👻

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Звучит как бред бешеного, но давайте сравним выходное значение данной функции с undefined:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Сравниваем выходные значения пустой функции с undefined","image":{"type":"image","data":{"uuid":"850cded0-9fba-55c8-bd49-8ca21debde5b","width":1920,"height":285,"size":6470,"type":"png","color":"ecedec","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Собственно, это все что нам нужно было доказать.. Любая функция возвращает undefined, если у неё в return не передается никакого значения😳

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Именно поэтому, когда мы ничего не возращаем в нашем методе, нам необходимо указать void, для того чтобы единственным возможным выходным значением было undefined😊

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Явно типизируем выходное значение функции","image":{"type":"image","data":{"uuid":"310aa83d-958b-52b0-b002-ffa540c4fb85","width":4184,"height":1204,"size":84141,"type":"png","color":"323a4a","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь поговори о never. never - тип, который вообще ничего не отдает🗿🗿🗿

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если в void можно поместить undefined (можно даже вот так:)

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"acfc6520-3767-5a13-ab6c-b6e88625488d","width":4184,"height":832,"size":54911,"type":"png","color":"97a2ac","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

То, тип never вообще не разрешает помещать в себя значения. Это полезно, когда метод один раз запустится и не завершится до окончания выполнения программы или если метод прерывает обычный флоу (кидает ошибку):

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"13f6c931-aba8-5796-aa94-394431088130","width":4184,"height":1428,"size":92487,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🌪 2.3. Любое значение (any, unknown, object, Function)"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

После того как мы поговорили о void и never, которые в основном предназначены для функций — неплохо бы поговорить о any, unknown, object, которые предназначены в основном для переменных😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

any — любой тип. То чего боится каждый TypeScript-разработчик.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Когда TypeScript разрабатывали, то перед разработчиками была определенная диллема: с одной стороны язык строготипизированый, с другой стороны другим разработчикам все ещё может понадобиться динамическая типизация для определенных целей. Ровно в этот момент и пришлось ввести any.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Переменная с типом any — это переменная, у которой включена динамическая типизация и для которой практически не действуют все преимущества TypeScript (ибо с такими переменными про проверку типов можно забыть), именно поэтому данного типа нужно избегать😉

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Объявить переменную с any крайне легко. Достаточно просто объявить переменную и ничего в ней не инициализировать, при этом явно не указав тип:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"ffe76a31-e841-5859-a359-a79363c904f2","width":4184,"height":1576,"size":105135,"type":"png","color":"a3afb9","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Это конечно круто, но жутко небезопасно🥶 У такой переменной могут быть любые свойства и методы, что уж там, в этой переменной может твориться вообще черт знает что, может это вообще функциональное выражение внутри переменной, а может подключаемый модуль, кто знает?👺 Именно поэтому придумали более безопасный тип — unknown

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

unknown — неизвестный тип переменной, у которой нет свойств и методов (только из общего прототипа), однако её можно сравнивать логическими операторами. Когда дело касается чего-то неизвестного, что нужно только проверить только на существовование, то нужно использовать unknown:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"8ae23c6f-8295-58ae-bf48-8f26fa9a91db","width":4184,"height":1280,"size":97592,"type":"png","color":"323a4a","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

object — ещё один интересный тип данных. Он предназначен для того чтобы дать не примитивный тип переменной.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Примитивными типами являются:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["string","number","boolean","symbol","null"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Все остальные типы являются нетривиальными и соответствуют типу object (не путайте с Object!!!):

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"d4e11cb7-71fe-571a-b2eb-b3bc1f945afc","width":4184,"height":1204,"size":82597,"type":"png","color":"323a4a","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Function — последний \"особенный\" тип, который представляет объект с методами bind, call, apply.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

К слову, любая структура данных или функция в JavaScript является объектом.

"}},{"type":"quote","cover":false,"hidden":false,"anchor":"","data":{"text":"

В JavaScript функции являются объектами первого класса, то есть: они являются объектами и с ними можно взаимодействовать и передавать их точно так же как любой другой объект. Если быть точным, функции — это объекты Function.

","subline1":"https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Functions"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос: Чем же отличается object и Function?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: Function отличается от object тем, что у объекта функции есть методы call, bind, apply😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🔫 3. Явное преобразование типов"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Явное преобразование типа переменной может понадобиться нам, когда мы один тип переводим в другой или нам нужны свойства и методы из другого типа. Рассмотрим подробнее👀

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Допустим, что у нас есть вот такая простенькая верстка:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"c5f7c71d-2de4-5e78-8158-a943e23b96cd","width":4184,"height":1204,"size":95461,"type":"png","color":"242c24","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Наша цель примитивная до чертиков - считать инпут с id=\"fname\", как нам это сделать? Возьмем элемент по getElementById:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"0cb971be-964f-50fa-a4af-07a3d913ab9b","width":4184,"height":1132,"size":89930,"type":"png","color":"333b4a","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И.. У нас будет ошибка😔 Все потому что getElementById всегда возвращает нам тип HTMLElement, у которого в свою очередь нет свойства value🤨

"}},{"type":"incut","cover":false,"hidden":false,"anchor":"","data":{"text":"

К слову: getElementById, querySelector, querySelectorAll — всегда возвращают элемент из DOM-дерева с типом HTMLElement, потому что он является родителем всех других элементов

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для того чтобы компилятор не жаловался на инпут, нужно поменять тип у элемента на HTMLInputElement, но как это сделать? Для того чтобы поменять тип у переменной есть два типа записи:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"69bdf9de-7b88-56f9-b366-a8f188c4bce6","width":4184,"height":1652,"size":113501,"type":"png","color":"9da9b4","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🎋 4. Структуры данных"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В данном разделе мы поговорим о разных структурах данных. Мы начнем с массивов, затем поговорим о дженериках, немного поговорим о типах в общей сложности, затем перейдем на интерфейсы и объекты, поговорим об расширении интерфейсов и типов, а также о их имплементации, закочим все это классами, модификаторами доступа, статическими методами, имплементациями класса, дженериками в классах и модулями. Данный раздел обещает быть обширным и интересным😎

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"4.1. 🌈 Массивы, кортежи и дженерики"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос: Если у меня есть массив данных, как мне его записать с помощью типа?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: Для этого есть два варианта записи😊

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"69d9edd3-1e19-5ffc-93ba-859e500f9614","width":4184,"height":832,"size":64122,"type":"png","color":"97a2ac","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Первый вариант записи является синтаксическим сахаром для второй. Вторая запись в свою очередь является дженериком😊 Ниже будет секция посвященная дженерикам👀

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы можем явно не указывать типы данных, и тогда TypeScript сам найдет тип массива и присвоит его переменной:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"f59ffd82-832c-5b14-a69e-36d27a2a1630","width":4184,"height":908,"size":73397,"type":"png","color":"97a2ac","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос: Что же такое дженерики?🤔🗿Что делать, если я передаю аргумент с определенным типом и у меня должен быть выход с точно таким же типом

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: Для таких случаев существуют обобщенные типы, это и есть дженерики😳

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте рассмотрим код, который берет аргумент одного типа и возвращает аргумент такого же типа:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"e7c2d5b5-1421-5f23-936d-7ccd11bc6888","width":4184,"height":980,"size":76451,"type":"png","color":"2f3747","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В данном примере T и является нашим \"любым типом\". Главная прелесть дженериков в том, что мы отдаем значение с таким же типом, с каким были входные данные (хотя и не обязательно. Суть в том, что мы просто опирируем одинаковыми типами). И нам совсем не важно какого там типа входные данные.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте напишем маленькую функцию. Она берет переменную с любым типом и отдает массив с таким же типом:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"cce4755e-5a03-506b-8ef2-d1e2042abaa7","width":4184,"height":1576,"size":112421,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот мы и научились пользоваться дженериками🥳 Ничего сложного, достаточно просто запомнить, что они нужны, когда мы производим действия над сущностями с одинаковым типом😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Также, стоит упомянуть про кортежи. Кортежи, это массивы фиксированной длины, которые можно использовать для разных целей:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"73399257-383c-5507-af32-52e6d03d0695","width":4184,"height":908,"size":68501,"type":"png","color":"97a2ac","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кортежи используются достаточно редко, однако они существуют как структура данных в JavaScript, их удобно типизировать в кастомных типах, которые мы рассмотрим в следующем разделе😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ещё мы должны рассмотреть перечисления🤔 Перечисления это структура которая используется (как не странно🗿) для перечисления свойств:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"5b810383-e10a-5fe4-b7cf-b2c61c653be6","width":4184,"height":1428,"size":87841,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Можно представлять перечисления как массив, в котором пара \"ключ : свойство\" перевернуты — \"свойство: ключ\". По умолчанию все перечисления начинаются с нуля. Если бы мы в примере выше вывели DIRECTION.down, то выход был бы 1, DIRECTION.left — 2 и так далее. Все эти индексы можно задать и явно:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"dadcea29-7dba-574a-97ed-db0d01f41d79","width":4184,"height":1352,"size":87560,"type":"png","color":"323a49","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Однако, для перечислений также можно задавать и строки. Вот например перечисление с позициями:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"cec70d5f-3545-5e83-94d5-0105a981f629","width":4184,"height":1352,"size":91684,"type":"png","color":"323a49","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вообще в перечислениях можно смешивать все что угодно, хоть методы, строки и числа, однако перечисления созданы не для этого😊 Они предоставлены нам, чтобы у нас была удобная индексация свойств. Это полезно, когда входные данные у нас, например поданы в виде массива:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"a0fb7b6b-74bc-50d8-b154-fc78c41c2a4e","width":4184,"height":1428,"size":90051,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Последняя тема, которую стоит обсудить в данном разделе многомерные массивы. Какой тип у данного массива?

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"97cbd5cd-4bf2-5f5a-810a-e24e49ce15d6","width":4184,"height":908,"size":68208,"type":"png","color":"97a2ac","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Тип у данного объекта следующий:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"type sub_arr = (number[] | string[])[];","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🔗 4.2. Кастомные типы и объекты"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"82a775f5-bae7-553e-a10e-598e9911514a","width":4184,"height":1564,"size":110126,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как мы видим создавать новые типы достаточно легко! Типы обычно создаются когда одни те же примитивы используются очень часто или когда нужно определенным типом обозначить определенную переменную.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проведем интересный эксперимент🔬 Давайте объявим переменную и сразу же её инициализируем с объектом:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"d46bd512-427e-5d38-aa35-973990c59833","width":4184,"height":1368,"size":88340,"type":"png","color":"323a49","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если мы посмотрим на тип данной переменной в редакторе, то увидим следующее:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"97743cae-e897-5046-96c6-b1f228146067","width":273,"height":172,"size":5702,"type":"png","color":"f1f1f1","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Интересно🤔 Давайте объявим тип с данными параметрами и попробуем задать его нашей переменной:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"2f37755a-7e7e-53b3-8db5-82ac2f11fe63","width":4184,"height":2036,"size":119767,"type":"png","color":"a5b0bb","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Это сработало!😊 Таким образом мы можем строго типизировать объекты😊

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"ef05748e-2eae-5686-8234-cc0ca54d7af7","width":281,"height":303,"size":8563,"type":"png","color":"365198","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кстати, если мы строго типизировали объект, то мы уже не сможем мутировать его😳 Мы не сможем добавлять в него новые свойства или методы, так как TypeScript вычислил тип во время иницилизации объекта🤔

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"823ea25c-f7e4-5c75-bee3-575a470c9349","width":1064,"height":250,"size":11441,"type":"png","color":"eeeeed","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос: А что если нам нужно добавить необязательный ключ к строго типизированному объекту?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: На этот вопрос есть решение — опциональные параметры.

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"e74859dc-725d-5ea8-8ff3-909e0602cf9e","width":4184,"height":2780,"size":154719,"type":"png","color":"2c3444","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос: А что если у меня есть специфический объект, где должны быть только строчные ключи и значения?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: Для этого можно использовать сигнатуру ключа.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Записывается сигнатура ключа следующим образом:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"{\n [имя ключа: его тип]: тип значения\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Приведем пример:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"0d30d531-8eeb-53b3-95f9-cf6b1cc85efa","width":4184,"height":1576,"size":96466,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кавычки к слову ставить везде не обязательно. Поставил специально, чтобы акцентировать, что все ключи являются строками😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🤩 4.3. Интерфейсы"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Интерфейсы — одна из ключевых фич в TypeScript, они позволяют круто типизировать классы, функции, объекты и все что только можно😊 Интерфейсы созданы для того чтобы давать разработчику удобный инструмент для типизации всего кода💫

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

По синтаксису они немного отличаются от типов:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"interface название {\n ключ: тип\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос: Они отличаются от типов синтаксисом, а смыслом отличаются?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: Практически нет. Ранее типы использовались для одних кейсов, а интерфейсы — для других кейсов. Ныне это два очень похожих решения по типизизации структур данных, однако различия все же есть😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Возможности, которые есть у интерфейсов, но нет у типов:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Декларативное расширение (мерджинг)","Расширение интерфейсов"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И всего-то?! 😅 Давайте рассмотрим сначала две данные фичи, а потом сравним как они реализованы у типов🤔

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🔨 4.3.1. Декларативное расширение (мерджинг)"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если мы объявим два интерфейса с одинаковыми именами, то TypeScript автоматически \"сплюснет\" их в один:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"4a44b91e-88f1-5ce7-a9af-bc99bcb57192","width":4184,"height":2472,"size":135436,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как мы видим два интерфейса с одинаковым именем стали одним целым. И теперь при использовании данного интерфейса TypeScript требует чтобы у объекта были свойства и из первого интерфейса и из второго одноименного😊 Удобно это или нет — решать вам. Я считаю что так легко можно запутаться, именно поэтому люди и придумали расширение😌

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"⚒ 4.3.2. Расширение интерфейса"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Расширением интерфейса называется процесс, когда один интерфейс поглощает все свойства родителя и добавляет свои:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"040fac8d-9654-59fb-bc43-3ffc1aab8bb9","width":4184,"height":2472,"size":143514,"type":"png","color":"a4b0ba","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Само расширение происходит следующим образом:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"interface название extends родитеский_интерфейс {\n // ключи и типы\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос: Это круто, что интерфейсы могут расширяться, но можно же расширить типы, верно?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: Можно, но никто так не делает, ибо это плохая практика😔

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Дело в том, что типы созданы для статического использования. Вы их объявили и используете, особо не расширяя их, когда интерфейсы созданы именно для того, чтобы расширять их🤔

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот пример расширения типа:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"8de9165c-4f6b-51e5-a029-07daf0a26851","width":4184,"height":2472,"size":139960,"type":"png","color":"a4b0ba","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Запись у Programmer выглядит немного кривовато, ибо мы используем оператор пересечения, который по сути просто берет все свойства и специально их пересекает создавая одно большое множество.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вообще пересечение у типов работает немного странновато: так как два типа по сути являются подмножествами типа object, то пересечение у них полное (тоже самое что и object & object), а значит все свойства просто соединяются🤯

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проще всего объяснить пересечение и объединение на примере: Представьте, что у нас в двух комнатах есть собаки и кошки. Кошки мяукают, собаки гавкают. Если мы объединим их, то будут кошки или собаки. Так как мы теперь не знаем кто мяукает, а кто гавкает, то мы можем использовать только общие методы и свойства, например: ходить().

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

А теперь давайте пересечем данные объекты. И кошки, и собаки являются животными, так что у нас по сути полное пересечение, а значит финальный объект сможет и гавкать, и мяукать.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кроме того некоторые правила у типов можно обойти с помощью объединения. Разработчик может ошибиться в операции, поставить по привычке | (оператор объединения), и тогда и вовсе второй объект станет валидным. Нам будет достаточно соответствия хотя бы с одним из типов (достаточно свойств из Person или Programmer):

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"234b6153-0de3-5c56-857a-89451b6f2e1c","width":4184,"height":2844,"size":153647,"type":"png","color":"2c3444","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

При объединении и пересечении запомните простое правило:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Объединение - это всегда или","Пересечение - это всегда и"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Подумайте над небольшой задачкой, что получится, если пересечь object & Date, а если пересечь Number & String (именно конструкторы), будет ли у них что-то общее, если нет, то почему, ведь общие методы же есть (call, apply, bind)?🤔

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Запись с типами кажется сложнее, требуется разглядывать код и разбираться в операторах сложения и умножения для множеств (и/или).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Совет: Старайтесь использовать интерфейсы, если дело касается объектов и сложных структур данных. Используейте типы для создания алиасов (вторых названий) для примитивных типов или для типизации функций.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Типизация функций, кстати у объектов выглядит не очень, а вот у типов вполне себе лаконично:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"1552f080-0a9c-5643-ae0d-317a9401c173","width":4184,"height":2472,"size":168217,"type":"png","color":"a5b0bb","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Запись типизации для функции в интерфейсе выглядит очень инородно🥶

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🎩 4.4. Классы"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Классы в JavaScript являются синтаксическим сахаром для создания объекта, об этом даже написано в документации MDN😳

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

TypeScript предоставляет нам все те же классы, однако с некоторыми улучшениями, а именно:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Поля","Параметры только для чтения","Модификаторы доступа","Перегрузка конструкторов","Наследование классов, а также имплементация интерфейсов","Расширение классов","Дженерики в классах","Параметризированные свойства","Абстрактные классы и инстансы"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Пройдемся по порядку😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🌳 4.4.1 Поля, параметры только для чтения, модификаторы доступа"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ранее в JavaScript мы объявляли свойства следующим образом:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"5bc1a2d1-332c-54c5-8355-6085fc2c7fd2","width":4184,"height":1576,"size":94775,"type":"png","color":"353533","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Единственный аспект, который стоит учесть: в TypeScript все поля нужно указывать заранее, перед тем как использовать их в конструкторе. После того как мы рассмотрим следующую задачку на JS, мы сразу же сможем посмотреть реализацию на TS😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"068126ac-1021-5d0e-85df-279ab1404ac6","width":4184,"height":2620,"size":154203,"type":"png","color":"b4b29d","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Минус такого подхода в том, что приватные свойства в JavaScript поддерживаются далеко не везде. Да и вообще сделав свойство приватным, мы только не даем изменить его извне, внутри класса свойство все ещё можно менять😔

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

TypeScript позволяет сделать следующее:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"2e407a7d-e4f5-563d-bd1e-d3a02d8d1066","width":4184,"height":2916,"size":166167,"type":"png","color":"2c3444","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Таким образом благодаря ключевому слову readonly мы смогли сделать так, чтобы AGE нельзя было изменять вообще нигде. Если мы попытаемся изменить его внутри класса, то TypeScript тут же начнет ругаться.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Круто!😎 Мы познакомились с первым ключевым словом - readonly, а также узнали что поля в TypeScript надо указывать перед тем как инициализировать их в конструкторе. readonly, к слову ведет себя абсолютно идентично const. Мы можем мутировать объект (например массив), который инициазирован с readonly, однако, мы не можем заменить сам объект полностью (перетереть его).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь давайте ещё больше усложним задачу: у нас будет два метода и одно свойство, которое мы применяем только внутри метода нашего класса:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Для того чтобы поздороваться","Для того чтобы попрощаться","Секретный пароль🤭"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Первые два будут публичные (методы, которые можно вызывать вне класса), а вот третий мы сделаем защищенным (protected). Вообще в TypeScript есть три модификатора:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Модификаторы доступа позволяют скрыть метод и свойства из области видимости, то есть просто инкапсулировать их, для того чтобы чужие глаза и руки не начали ничего вызывать за пределами класса☠

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Всего в TypeScript 3 модификатора доступа:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["public - публичный модификатор (по умолчанию)","protected - модификатор, который позволяет использовать поле в пределах класса и дочерних классов","private - модификатор, который позволяет использовать поле только в пределах класса"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На счёт дочерних классов - не стоит беспокоиться. Мы разберем это понятие чуть позже, а пока забудьте, это не столь важно🤗

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Реализовать это на JavaScript вполне возможно:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"c1026d02-e227-56ab-b435-ba4b807f1acf","width":4184,"height":3288,"size":211558,"type":"png","color":"b4b29d","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На TypeScript это реализовать ещё проще:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"e6971dfd-92e5-51ec-9c38-9c7d50bcdb4d","width":4184,"height":3660,"size":197212,"type":"png","color":"2c3444","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Синтаксис с private — намного приятнее, не правда ли?

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"📈 4.4.2 Перегрузка конструкторов"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Допустим, что у нас есть следующая задача: перегрузить конструктор, чтобы он принимал или одно значение (имя), или два значения (имя и возраст)

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте попробуем это сделать на JavaScript:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"c17d0e9c-3f7d-5e40-9e27-bd0d7f3c82c0","width":4184,"height":3812,"size":234177,"type":"png","color":"b4b29e","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кажется у нас неприятности.. Наш конструктор начинает расти... Кроме того я добавил гарды для входных значений (ну, а вдруг что?!). Можно было бы отделаться JSDoc вместо гардов, но это не самый лучший способ.. В будущем нам возможно нужно будет сделать определение имени через сеттеры, а не пихать все в конструктор, но по заданию мы должны сделать перегрузку. Так как JavaScript её не поддерживает, то приходится выкручиваться🙄

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь попробуем сделать то же самое на TypeScript:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"5f23fada-c1b8-5cdd-a5bc-f937a124e69a","width":4184,"height":4256,"size":250210,"type":"png","color":"2c3444","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как мы видим, в начале мы объявили два конструктора без реализации. Это для того чтобы при перезгрузке TypeScript выводил правильные подсказки. Последний (третий) конструктор описывает уже саму реализацию. Проверяем мы только на наличие самого параметра😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"😱 4.4.3. Имплементация интерфейсов и классов. Абстрактные классы."}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Замечательно, мы уже дошли до имплементации интерфейсов! Вы заметили как вырос наш класс? Уже немного сложно по нему ориентироваться и изменять что-то в нем, тем более в будущем мы создадим классы профессий, а они тоже будут изменяться🥶

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следить за всем этим адом из типов становится понемногу неприятно, поэтому давайте-ка быстро напишем интерфейс:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"e9a36de9-3c5e-5ee9-bebd-28375d9421d4","width":4184,"height":3960,"size":235317,"type":"png","color":"2c3444","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Я немного подчистил комменты и создал интерфейс, однако, тут невооруженным глазом заметно, что чего-то не хватает👁

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Не хватает тут конструктора и приватных полей. Но почему?!

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Приватные поля нельзя объявлять в интерфейсах. Они просто не созданы для этого.","С конструкторами та же история😔"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Чтобы объявить конструкторы вне класса и присвоить стартовые значения переменных, были созданы абстрактные классы. Абстрактные классы это своего рода прородители для классов. Весь синтаксис заключается в том, чтобы написать abstract рядом с классом, и в самом классе не реализовывать никакого функционала:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"d9109907-a24c-50d4-8d74-c0dd073dd732","width":4184,"height":4556,"size":266017,"type":"png","color":"2c3444","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос: А чего мы добились таким разделением?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ: Теперь каждый класс, который наследуется от APerson обязан инициализировать методы sayHello и sayGoodbye, мы также избавились от инициализации name, которая была в дочернем классе. Огромным плюсом также является то, что теперь все классы, которые наследуются от нашего абстрактного класса будут иметь схожую структуру. Хорошей практикой является объявлять все публичные методы именно в абстрактном классе😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вы могли заметить, что в случае с интерфейсами мы использовали ключевое слово implements, которое буквально говорит компилятору, что все что есть в интерфейсе должно быть сделано внутри класса. Если бы мы применяли интерфейс к многим классам, то вполне возможно, что у нас бы было много одинаковых методов, которые мы бы писали из разу в раз. extends же в свою очередь расширяет дочерний класс. Он берёт все методы из родительского класса, проверяет есть ли там абстрактные методы и свойства, если есть то, заставляет их реализовать в дочернем классе, а также берет и выполняет за нас всю суетливую работу. В данном случае все тривиально, он просто инициализирует имя существа, но таких \"общих\" моментов в классе родителе может быть очень много, в этом нам и помогает абстрактный класс.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Стоит также упомянуть, что мы можем наследоваться и от обычного класса (расширяя его функционал), однако в обычном классе нам придётся переобъявлять методы, а тут мы просто инициализируем абстрактные методы, которые до этого не были реализованы😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"🤖 4.4.4. Дженерики и параметризированные свойства"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"9f72323c-5bc2-5b16-986b-6ce28ae2e969","width":4184,"height":2320,"size":144839,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы просто добавили модификатор доступа внутрь аргументов конструктора. Таким образом конструктор может создавать поля автометически, больше ничего указывать не нужно😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь про дженерики😊 Дженерики как мы уже знаем созданы для того чтобы обощать тип, тут происходит то же самое:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"d216a106-310e-5166-bc4b-5206f16d1dd2","width":4184,"height":1876,"size":123548,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"📦 4.5. Модули"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Модули в TypeScript экспортируются с помощью нескольких способов:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"a960e19a-885e-5cfe-818e-1732fbb8b982","width":4184,"height":1428,"size":92760,"type":"png","color":"a4afba","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"♻ 4.5.1 CommonJS"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Модули в CommonJS импортируются и экспортируются с помощью require и module.exports. CommonJS создан для Node.js и не поддерживается в браузере без специальных библиотек

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Синтаксис достаточно простой:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"159c6e68-5e55-5b57-ad6b-450e0077a5bf","width":4184,"height":1652,"size":92449,"type":"png","color":"a4afb9","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Такие модули в основном используются для Node.js, а также Webpack. Они очень удобны для разделения модулей между файлами. Вам ничего не мешает экспортировать пару модулей в объекте.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Более того синтаксис в CommonJS один из самых простых в использовании, Node.js вообще использует его по умолчанию😊 CommonJS позволяет импортировать модули прямо внутри условий (что недоступно в некоторых системах загрузки модулей)

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Можно экспортировать модули ещё более удобно без использования module.exports:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"8fb6477d-cb8f-5e20-89d6-36d5b5436774","width":4184,"height":1728,"size":102331,"type":"png","color":"a4afb9","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"😌 4.5.2 ES6 модули"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

ES6 модули позволяют экспортировать и импортировать модули немного по-другому:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"eca5e6e1-67f4-546e-a127-926f993d481e","width":4184,"height":1788,"size":101272,"type":"png","color":"a3afb9","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Также, ES6-модули позволяют экспортировать дефолтные модули - модули, которые будут отдавать по дефолту, если мы не укажем что именно экспортируем:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"a369a227-286b-5ae6-b470-9e5e6329cf3b","width":4184,"height":1876,"size":102916,"type":"png","color":"a3aeb9","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Данные модули можно использовать в браузере. Для этого достаточно импортировать все модули в ваш главный файлик (пусть будет main.js), а затем написать следующее в HTML-файле:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"1002b038-dd22-52be-9dd2-a5a74ba778ed","width":4184,"height":1800,"size":130594,"type":"png","color":"343432","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"😍 4.5.3. Внешние модули"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ясное дело, что мы будем использовать не только свои модули в нашем приложении. Часто нам приходится работать с модулями из NPM. Но, что будет, если мы их импортируем?

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"b6bf18f4-a75b-512c-9fc0-62fd44916b6d","width":1717,"height":976,"size":15111,"type":"png","color":"2c2c34","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"efcdd383-5104-5c0d-880f-ae9a66e9ec54","width":1717,"height":976,"size":10420,"type":"png","color":"2c2c34","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Программа скомпилировалась, круто😎 Теперь давайте попробуем поработать с самим кодом:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"9859d685-3d86-5f27-be5c-0a067b9cb775","width":1717,"height":976,"size":50059,"type":"png","color":"2f3138","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Наш модуль импортировался, но у него тип any!!! Поддержка сторонних модулей в TypeScript что невозможна?! Никак нет😊 Нам нужно просто скачать расширение для нашего плагина, которое тоже начинается на @types. В данном случае @types/ws. Установили и... Опять та же беда(((

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Однако, если мы используем немного другую запись (ES6-модули), то все заработает:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"2bd49b87-8484-5371-808b-5b1746746d46","width":1717,"height":976,"size":77887,"type":"png","color":"2e3037","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот и все мы рассмотрели основные аспекты данного ЯП. В будущем будет ещё одна статья, но уже поменьше😅 про конфигурацию проектов и файлы декларации, где мы уже будем смотреть на примере проекта как правильно и красиво настроить файлы с типами в TypeScript😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если вам было интересно читать данную статью, то вы можете перейти на мой канал в телеге, там много всего интересного!😳 Приятно было рассказать об этом прекрасном ЯП, в будущем ещё не раз увидимся😊

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":36,"favorites":177,"reposts":1,"views":288,"hits":55596,"reads":null,"online":0},"dateFavorite":0,"hitsCount":55596,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/423888-krupnyi-gaid-po-typescript","author":{"id":1178100,"name":"Даниил Шило","nickname":null,"description":"Frontend Engineer в Firecode","uri":"","avatar":{"type":"image","data":{"uuid":"59e8fb72-4a49-5932-af48-bdb368b827e7","width":400,"height":400,"size":28098,"type":"png","color":"dfcac6","hash":"73610f6d350e00","external_service":[]}},"cover":{"cover":{"type":"image","data":{"uuid":"ab766887-ce9f-5181-b1e9-e877385348cb","width":5120,"height":2160,"size":561896,"type":"jpg","color":"85adba","hash":"","external_service":[]}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4265614,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4265614"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":642080,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/642080"}],"lastModificationDate":1764920033,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":46},{"id":2,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":418917,"customUri":null,"subsiteId":1178100,"title":"Крупный гайд по Webpack","date":1652189619,"dateModified":1652189619,"blocks":[{"type":"quote","cover":true,"hidden":false,"anchor":"","data":{"text":"

webpack — это сборщик модулей JavaScript с открытым исходным кодом. Он создан в первую очередь для JavaScript, но может преобразовывать внешние ресурсы, такие как HTML, CSS и изображения, если включены соответствующие загрузчики. webpack принимает модули с зависимостями и генерирует статические ресурсы, представляющие эти модули.

","subline1":"Wikipedia"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"644d6c04-b6ee-59b7-a26e-bf6c3ff36c2e","width":1157,"height":557,"size":17850,"type":"png","color":"2c3d45","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В данной статье полностью разберем как работать с данной утилитой для сборки всего на свете в один файл: пройдемся по концептам и закончим реальным использованием с TypeScript и модулями из node_modules, чтобы заставить код работать в браузере😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Какую проблему решает Webpack

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Webpack решает проблему вечного подключения библиотек и фреймворков к HTML. В каком порядке их подключать, как решать конфликты, как сразу же оптимизировать картинки, как включать css в JavaScript, подобно тому, как это делается в React? Всеми этими вопросами занимается Webpack👍

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Концепты"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Webpack — статический сборщик модулей. Это значит что после компиляции у вас будет один файлик в котором все зависимости будут в правильном порядке, а все ассеты и стили будут зашиты в один файл.У самого WebPack есть несколько основных терминов:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Конфигурационный файл (Configuration)","Входная точка (Entry)","Точка выхода (Output)","Загрузчики (Loaders)","Плагины (Plugins)","Режимы (Modes)"],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Конфигурационный файл"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сам конфигурационный файл всегда один — webpack.config.js. В нем мы указываем экспортируемый объект, которй и является конфигурацией для Webpack:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n\n // Конфигурация\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Тут нет ничего сложного, в следующих пунктах мы разберем основные свойства для данного объекта, чтобы заставить Webpack работать🔫

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["webpack.dev.js","webpack.prod.js"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Названия файлов могут быть любые, это не стандарт, однако, внегласно принято называть их именно так😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В package.json нужно просто указать команды для сборки, чтобы Webpack знал какую конфигурацию ему таскать:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"{\n ../\n \"scripts\": {\n \"build:dev\": \"webpack --config webpack.dev.js\",\n \"build:prod\": \"webpack --config webpack.prod.js\"\n }\n ../\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

А куда делся webpack.config.js?!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы его в данном случае просто не используем Если мы просто запустим Webpack безо всяких флагов, то он будет искать именно webpack.config.js, однако если мы дали точное имя конфигурации, то он не будет ничего искать и просто сжует👄 данную конфигурацию и будет работать по ней👀

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Входная точка"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сам Webpack строит граф (дерево, в случае если входной файл - один) зависимостей. Он проходится по всем импортам внутри ваших файлов, строит граф, а затем начинает импортировать все прямо в финальный файл.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Обычно данный файл является ./src/index.js, однако данную входную точку конечно же можно поменять:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n\tentry: './path/to/index.js',\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Таким образом мы поменяем входной файл и Webpack будет работать начиная с файла, который мы указали в пути.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Точка выхода"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Не секрет, что Webpack отдает нам один файл (спойлер: можно настроить так, чтобы отдавал несколько, но не суть сейчас). Именно этим \"одним файлом\" и является точка выхода 📤

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

По умолчанию этот файл находится в ./dist/main.js, однако вы можете также поменять это с помощью конфигурации:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// Импортируем модуль для работ с путями\nconst path = require('path');\n\nmodule.exports = {\n\n // Указываем входную точку\n entry: './path/to/index.js',\n\n // Указываем точку выхода\n output: {\n\n // Тут мы указываем полный путь к директории, где будет храниться конечный файл\n path: path.resolve(__dirname, 'dist'),\n\n\t// Указываем имя этого файла\n filename: 'my-first-webpack.bundle.js',\n },\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Хороший вопрос: почему бы нам просто не указать имя точки выхода и на этом все. Зачем этот path, зачем разделять имя файла от пути?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Не менее хороший ответ😁: Webpack может упаковывать все что угодно, даже картинки формата png. Сами картинки он как не странно в код не запихнет, однако он перенесет их. Для переноса ему нужен точный путь к директории, где будет точка выхода, чтобы построить правильный путь. Имя файла и путь мы разделяем именно поэтому.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Лоадеры"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Из коробки Webpack понимает только JavaScript и JSON, что будет если мы попытаемся запихнуть внутрь cjs, ejs, html, typescript? Ничего, Webpack отдаст нам ошибку. Для таких вещей придумали лоадеры😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Лоадеры 📂 — специальные модули, которые созданы для того, чтобы считывать и обрабатывать файлы. Для лоадеров в свою очередь придумали правила.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Правила 📏 — это в свою очередь просто объекты в конфигурации, которые указывают на файлы, которые нужно обработать и указывают лоадер

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Все лоадеры указываются в конфигурации следующим образом:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const path = require('path');\n\nmodule.exports = {\n entry: './path/to/index.js',\n output: {\n path: path.resolve(__dirname, 'dist'),\n filename: 'my-first-webpack.bundle.js',\n },\n\n // Указываем тут, что будем использовать спец. модуль для определенных файлов (лоадер)\n module: {\n\n\t// Указываем правила для данных модулей\n\trules: [\n\n\t\t// Указываем правило для каждого лоадера\n\t\t{test: /.txt$/, use: 'raw-loader'},\n\t],\n },\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как можно увидеть есть свойство module, которое содержит в себе правила, массив rules в свою очередь уже содержит сами правила: в него включаются правила в виде объекта, где test - путь в виде Regex, use - сам лоадер, который должен быть использован.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Стоит уточнить, что перед тем как использовать лоадеры — их надо найти и скачать с помощью NPM😳

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"npm i -D raw-loader","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Существует очень много лоадеров, от CSS и Sass до png и svg, рассмотрим их чуть позже в практическом примере😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Плагины"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если лоадеры используются для того чтобы просто обработать файл, который мы импортируем в js, то плагины используются для того чтобы управлять не только импортами в JS.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Плагины🔌 — внешние модули для Webpack, которые позволяют управлять и обрабатывать файлы, которые не импортируются в JavaScript

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Рассмотрим легенький пример, где мы будем импортировать index.html, это позволит не держать его в директории dist (то есть саму директорию можно будет удалять и ничего не потеряем), а также вставлять внутрь переменные окружения и пути к файлам.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот как это будет выглядеть в конфигурации (для упрощения часть кода с входной и выходной точкой - пропущена):

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// Для того чтобы достучаться до плагина\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\n\n// У самого Webpack уже есть встроенные плагины, их неплохо тоже импортировать\nconst webpack = require('webpack');\n\nmodule.exports = {\n module: {\n rules: [{ test: /\\.txt$/, use: 'raw-loader' }],\n },\n\n // Указываем новые плагины для обработки файлов\n plugins: [\n\n\t// Указываем что будем обрабатывать HTML с помощью плагина\n\tnew HtmlWebpackPlugin({ template: './src/index.html' })\n\t],\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как мы можем увидеть тут мы используем плагин html-webpack-plugin для того чтобы обрабывать HTML. В данном случае он просто перенесет файл из ./src в ./dist 😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Режим"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

У Webpack есть три режима (на самом деле два, но один из трёх является отсутсвием любого режима). По умолчанию без конфигурации у Webpack нет режима — none. Если мы напишем конфигурацию, то Webpack настоятельно посоветует (с вот таким лицом: 😠) добавить режим.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Делается это проще простого:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n\tmode: 'production',\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Всего у Webpack (по-настоящему) существуют два режима:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Режим для разработки — development. Данный режим как не сложно догадаться рассчитан на разработку, он не сильно сжимает бандл (точку выхода, файл который получится в конце), а также может привязывать карты исходников и кучу других мелких фич","Режим продакшена — production. Режим, который будет сжимать ваш бандл, покуда сможет и делать совершенно нечитабельный (такой, что и обфускатор не потребуется), но оптимизированный код."],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Продвинутое использование"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Замечательно, вот мы и прорвались с основ к более продвинутому использованию🤩 Покрыть одной статьей асболютно все кейсы использования просто напросто невозможно, тут дело ограничивается только вашим воображением и условиями, при которых вам потребуется Webpack, однако пройтись по основным все же стоит😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Входная точка"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И по новой..♻ Снова пройдемся по всем концептам, только уже с более продвинутыми конфигурациями😳 Допустим, что у вас есть несколько файлов, которые сами по себе являются входными точками. Часто для сайтов например указывают следующие файлы:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["main.js - главный файл, который выполняет функционал сайта","vendor.js - файл, который работает непосредственно с модулями"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Что будем делать в таком случае? Не придётся же мерджить два файла?!😱

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Две и более входные точки"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы можем разделять файлы на две и более входных точки. Мы можем компилировать их в отдельные точки выхода, а также в один.С помощью данной конфигурации можно указать входные файлы:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n entry: {\n main: './src/main.js',\n vendor: './src/vendor.js',\n },\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы также можем передать в entry не объект с названиями файлов, а массив:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n entry: ['./src/main.js', './src/vendor.js'],\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Это хорошо, но как нам понять куда будут собираться данные исходники?🤔

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В webpack.config.js указываем следующее:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const path = require('path');\n\nmodule.exports = {\n output: {\n\tpath: path.resolve(__dirname, 'dist'),\n\n\t// Тут мы указываем, что будем компилировать каждый файл с таким же именем, но с постфиксом bundle.js\n filename: '[name].bundle.js',\n },\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

[name] — в данном случае имя входного файла Данная строка является шаблонной (не в привычном понимании JavaScript), ибо внутри квадратных скобок мы указываем шаблон наименования. Все шаблоны можно посмотреть вот тут

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Одним из полезных свойств является [contenthash]. Он используется в основном для продакшена, чтобы каждый раз при компиляции создавался новый хэш (хэш создается по контенту в файле, так что если файл изменился, то хэш будет новый💫). Данное свойство активно используется для обхода кэширования файлов в браузерах. Дело в том, что браузер не будет грузить все ваши файлы с одним и тем же названием каждый раз, как вы переходите на сайт, ибо это очень долго и влияет на время загрузки, даже если файлы поменялись. Проблема решается \"в лоб\", если так можно сказать😅 — мы просто меняем название файла, если он сам изменился.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

А вдруг хочется один выходной файл, что делать тогда?!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ на этот вопрос очень легкий😅 Просто включите файлы, которые вам нужны в одном бандле в main.js. Тот же vendors.js можно включить внутрь main.js и все😳

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// File: main.js\n\n// Импортируем работу с вендорами\nimport './vendors.js'","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Лоадеры"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Точки выхода мы пропустим, так как я думаю мы уже обсудили их выше достаточно😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Лоадеры в свою очередь могут таскать больше чем просто файлики .txt, естественно. Они используются для той же компиляции TypeScript:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n module: {\n rules: [\n { test: /\\.ts$/, use: 'ts-loader' },\n ],\n },\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Важно: Помните, что перед тем как использовать лоадер - его нужно установить🚧

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"npm i -D ts-loader","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

А что если с файлом определенного типа нужно провести не одну операцию?🤔

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Процессинг файлов с помощью лоадеров"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Допустим у нас есть задачка: мы хотим процессить CSS-файлы с помощью SASS, а также включать их в JavaScript. Реализация у данной идеи будет следующая:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n module: {\n\trules: [\n\n\t // Правило для CSS\n\t {\n\t\ttest: /\\.css$/,\n\t\tuse: [\n\t\t {loader: 'style-loader'},\n\t\t {loader: 'css-loader'},\n\t\t {loader: 'sass-loader'}\n\t\t]\n }\n\t]\n }\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для плагинов последовательности и сам процессинг работает точно также😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Цели (Target)"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

То, о чем мы ещё не поговорили - цели😳

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

JavaScript ныне работает как на клиентской стороне, так и на стороне сервера.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы можем создавать множественные цели, чтобы решать такие проблемы:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const path = require('path');\nconst serverConfig = {\n target: 'node',\n output: {\n path: path.resolve(__dirname, 'dist'),\n filename: 'main.node.js',\n },\n //…\n};\n\nconst clientConfig = {\n target: 'web',\n output: {\n path: path.resolve(__dirname, 'dist'),\n filename: 'main.js',\n },\n //…\n};\n\nmodule.exports = [serverConfig, clientConfig];","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Однако опыт показывает что так делать не всегда удобно, а иногда и вовсе нежелательно🤔 Держать все конфигурации в одном файле - нехорошая идея, лучше создать две директории и два независимых webpack.config.js.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

А что же на счёт target? Данное свойство указывает цель для чего мы создаем бандл, это может быть:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["async-node - бандл для асинхронной ноды, который будет тянуть модули с промисами","node - бандл для синхронной ноды","electron-renderer - бандл для рендер-процесса в Electron","electron-preload - бандл для прелоудера в Electron","web - бандл для браузера. Данный таргет является дефолтным.","webworker - бандл для воркера.В зависимости от выбора бандла Webpack будет по-разному импортировать модули и обращаться с ними."],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Кэширование"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы можем кэшировать неизменные части нашего приложения, для того чтобы Webpack быстрее собирал наше приложение😌

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

По умолчанию файлы кэшируются в памяти в mode: development и не кэшируются вовсе, если mode: production🤔

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для того чтобы Webpack кэшировал все не в оперативной памяти, а в постоянной — достаточно просто указать следующее свойство:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = { \n cache: { \n type: 'filesystem', // По умолчанию 'memory'\n }, \n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Webpack будет кэшировать билд в node_modules/.cache/webpack и автоматом пропускать то, что не изменилось!😱

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Директорию для кэша можно изменить с помощью cacheDirectory:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const path = require('path');\nmodule.exports = { \n cache: { \n type: 'filesystem', // По умолчанию 'memory'\n\n\t// Устанавливаем диреторию для кэша\n\tcacheDirectory: path.resolve(__dirname, '.temporary_cache')\n },\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для того чтобы отключить кэш вовсе достаточно просто добавить cache: false, интересным замечанием будет то, что cache: true — то же самое, что и cache: {type: 'memory'} 😉

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Watch или как Webpack подсматривает 👁"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Webpack может компилировать изменения каждый раз, как только мы перекомпилируем файлы. Это очень полезно при разработке. Для того чтобы Webpack \"подсматривал\" за нашими файлами достаточно просто указать:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n //...\n watch: true,\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если после того, как вы изменили файл сборка долго собирается и вообще вся ваша система на время повисла😡 это может быть следствием того, что Webpack начал подсматривать не за теми файлами. Это можно легко исправить, просто исключите огромные директории, которые не изменяются во время разработки:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n //...\n watch: true,\n\n // Настройки для watch\n watchOptions: {\n\n\t// Директории, которые watch будет игнорировать\n\tignored: [/node_modules/]\n }\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если случилось так, что вы работаете с pnpm, который берёт модули из ссылок (то есть он не создает все время node_modules, а скачивает модули только один раз), то вам понадобится следующее свойство:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n //...\n watch: true,\n\n // Настройки для watch\n watchOptions: {\n\n\t// Разрешать Webpack следить за символьными ссылками\n\tfollowSymlinks: true\n }\n};","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"DevServer 🛎"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Webpack ещё и умеет запускать свой http-сервер, для того чтобы у вас была live-reload (перезагрузка при рекомпиляции)🌈

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для того чтобы заставить🔫 Webpack использовать devServer, достаточно просто указать следующие свойства в конфигурации:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const path = require('path');\n\nmodule.exports = {\n\n // Конфигурация для нашего сервера\n devServer: {\n\n\t// Здесь указывается вся статика, которая будет на нашем сервере\n static: {\n directory: path.join(__dirname, 'public'),\n },\n\n\t// Сжимать ли бандл при компиляции📦\n compress: true,\n\n\t// Порт на котором будет наш сервер\n port: 9000,\n },\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы также можем настроить Webpack так, чтобы он показывал нам ошибки, если вдруг что-то пошло не так, прогресс-бар при компиляции:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"module.exports = {\n //...\n devServer: {\n\t// ...\n client: {\n\n\t // Показывает ошибки при компиляции в самом браузере\n overlay: {\n\n\t\t// Ошибки\n errors: true,\n\n\t\t// Предупреждения\n warnings: false,\n },\n\n\t // Показывает прогесс компиляции\n\t progress: true\n },\n },\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

У самого сервера есть безграничные возможности: от поддержки Hot Module Replacement до поддержки WS-сервера и HTTPS соединения, все их можно посмотреть здесь😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Пример на TypeScript"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как и обещал разберем пример😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Постановка задачи: Написать конфигурацию для TypeScript с подтягиванием файлов [.ts, .tsx, .js], картами для исходников.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Задачу поставили, выполняем😇

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// Подтягиваем модуль для удобной работы с путями\nconst path = require('path');\n\nmodule.exports = {\n\n // Точка входа\n entry: './src/index.ts',\n mode: 'development'\n // Говорим, что нам нужна карта исходников🗺️\n devtool: 'inline-source-map',\n module: {\n rules: [\n\t // Компилируем TypeScript\n {\n test: /\\.tsx?$/,\n use: 'ts-loader',\n exclude: /node_modules/,\n },\n ],\n },\n\n // Говорим что если не указано расширение файла, то пробуем эти варианты\n // @see https://webpack.js.org/configuration/resolve/#resolveextensions\n resolve: {\n extensions: ['.tsx', '.ts', '.js'],\n },\n // Точка выхода\n output: {\n filename: 'bundle.js',\n path: path.resolve(__dirname, 'dist'),\n },\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На этом можем завершать статью🥳 Мы научились пользоваться Webpack'ом, а также рассмотрели некоторые аспекты, которые позволят нам использовать его ещё эффективнее. Если вам понравилась статья, то вы можете перейти в мой канал, там ещё много всего интересного😉 Ещё увидимся!

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":21,"favorites":149,"reposts":1,"views":231,"hits":46039,"reads":null,"online":0},"dateFavorite":0,"hitsCount":46039,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/418917-krupnyi-gaid-po-webpack","author":{"id":1178100,"name":"Даниил Шило","nickname":null,"description":"Frontend Engineer в Firecode","uri":"","avatar":{"type":"image","data":{"uuid":"59e8fb72-4a49-5932-af48-bdb368b827e7","width":400,"height":400,"size":28098,"type":"png","color":"dfcac6","hash":"73610f6d350e00","external_service":[]}},"cover":{"cover":{"type":"image","data":{"uuid":"ab766887-ce9f-5181-b1e9-e877385348cb","width":5120,"height":2160,"size":561896,"type":"jpg","color":"85adba","hash":"","external_service":[]}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4265614,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4265614"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":642080,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/642080"}],"lastModificationDate":1764920033,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":42},{"id":2,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":417980,"customUri":null,"subsiteId":1178100,"title":"WebSocket: смотрим как работает за кулисами","date":1651995514,"dateModified":1651995514,"blocks":[{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"

Сегодня поговорим о том как работает WS, напишем простенький клиент на JS, обсудим как дебажить данный протокол ну и просто обсудим несколько интересных фактов. Погнали😈

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"ef244294-e8ef-539e-a1a0-f062287e398f","width":4984,"height":3323,"size":1080627,"type":"jpg","color":"121417","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Первая ступень развития: HTTP"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для начала стоит начать с того что такое HTTP?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

HTTP - Протокол для передачи гипертесктовых данных (Hyper Text Transfer Protocol), который используется повсеместно. HTTP используется в клиент-серверной архитектуре, там всю работу можно показать с помощью одной диаграммы:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"7b7e03e6-8660-5ae1-b7b7-a8d1bcfd2a72","width":3244,"height":1084,"size":73276,"type":"jpg","color":"d9d9d9","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Особенности HTTP"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["HTTP не поддерживает соединение, после того, как отдает ответ на запрос.","HTTP обязует клиентов заранее оговаривать действие, которое клиент хочет сделать в заголовке (HTTP Headers) - GET, POST, PUT, DELETE","Мы отправляем заголовок что хотим сделать каждый раз, как обраемся к серверу"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Существует большое количество сайтов, с помощью которых вы можете посмотреть как работает HTTP, давайте возьмем забавный сайт с REST API по мультивселенной Рик и Морти — https://rickandmortyapi.com/documentation.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вы можете отправить запрос с помощью Postman или обычного cURL, я буду использовать второй😊 Давайте возьмем информацию о персонаже (Рике) и посмотрим что нам пришлёт сервер, для этого используем данную ссылку: https://rickandmortyapi.com/api/character/1

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"# Отправляем запрос с помощью cURL и парсим пришедший ответ с помощью JQ\ncurl https://rickandmortyapi.com/api/character/1 | jq","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

После данной команды мы получим следующий ответ. Как мы видим мы просто отправили запрос на получение информации с сервера (GET), сервер отдал нам информацию и после этого мы разрываем соединение. После того как мы получим ответ мы ничего не знаем о сервере👀

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Нам пришли данные в формате JSON как ответ от сервера
","image":{"type":"image","data":{"uuid":"b0cc2347-6e73-508b-afb6-c9c8046c8b03","width":1920,"height":1080,"size":416230,"type":"png","color":"c2c2cb","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Вторая ступень: AJAX"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

AJAX - асинхронные запросы с помощью JavaScript (Asynchonous JavaScript and XML). AJAX преследует все те же цели, что и HTTP, только делает это уже асинхронно. Если ранее нужно было для каждого запроса прописывать свой URL и перезагружать страницу, то теперь можно просто использовать AJAX и он сам будет отправлять нужные URL серверу и получать данные.

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"0ce600fc-d5cb-5d7b-a6bd-a4dff92b09a4","width":4055,"height":1905,"size":148629,"type":"jpg","color":"040404","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Особенности AJAX"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Все ещё обычный запрос, который не поддерживает соединение, после того, как отдает ответ на запрос.","Все ещё заранее оговариваем действие, которое клиент хочет сделать в заголовке (HTTP Headers) - GET, POST, PUT, DELETE","Мы отправляем заголовок что хотим сделать каждый раз, как обраемся к серверу","Теперь мы делаем это асинхронно благодаря JavaScript"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Самым простым примером AJAX является следующая реализация:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// Реализация на NodeJS ^17.5\n// Будет работать в браузерах ~если вы не на Internet Explorer 8~\n\n/**\n * Метод для того чтобы показать вывод AJAX-запроса\n * @param {string} url - ссылка, которую будем подтягивать\n * @returns {void}\n */\nfunction showAJAXResponse(url) {\n\n\t// Выполняем запрос на сервер\n\tfetch(url)\n\t\t.then(res => res.json()) // Формируем JSON\n\t\t.then(data => console.log(data)); // Выводим\n}\n\n// Отправляем AJAX запрос😊\nshowAJAXResponse('https://rickandmortyapi.com/api/character/1');","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот что мы получим в итоге:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"284a9f63-0df3-5956-ad4b-03981e0f4527","width":1920,"height":1080,"size":424015,"type":"png","color":"c2c2cb","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы можем выполнить множество таких запросов (серьзено, хоть 1000, если сервер позволит), как мы можем увидить мы ничего не перезагружаем (нам даже перезагружать нечего😅, мы делаем все на бэк-энде Node.js)

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как мы можем увидеть, мы все ещё не держим связь с сервером. Мы отправили запрос, получили ответ и все😳 Что дальше происходит с сервером нам неизвестно.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Третья ступень: WS или WebSocket"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

WebSocket - протокол для общения между клиентом и сервером, предоставляющий двухсторонне общение сверх протокола TCP.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы подключаем WS один раз, а затем сервер может отдавать нам ответы тогда, когда посчитает нужным:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"c1e946d2-7325-5e94-a41b-5185a8867552","width":4055,"height":1905,"size":180440,"type":"jpg","color":"dedede","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Как это работает?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Первое что мы делаем — отправляем обычный TCP-запрос на сервер, мы говорим, что хотим подключиться к серверу и ждём от него ответа. Такой процесс называется “рукопожатие” (Handshake), он используется повсеместно, например когда вы подключаетесь к роутеру ваш телефон отправляем запрос роутеру с ключами, роутер отвечает ОК и вы успешно подключаетесь.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Затем происходит обмен данными: допустим один из множества клиентов отправил HTTP-запрос серверу и нужно отдать ответ не только одному клиенту, а целой сети! Сервер в таком случае отдаст обычный ответ отправителю запроса, а всем другим пришлёт пакеты по WebSocket-соединению с полезными данными.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Разберем более подробно на примере. Вы — клиент, отправляете запрос серверу на подключение. Запрос и ответ будут выглядеть примерно так👁:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// Отправляем запрос серверу по ссылке example.com/connect-to-ws\n// Вот что примерно мы пришлём:\nGET /connect-to-ws HTTP/1.1\nHost: example.com:8000\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\nSec-WebSocket-Version: 13\n\n// А вот что нам на такой запрос ответит сервер при успешном рукопожатии:\nHTTP/1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как мы видим сервер ответил не кодом 200 (успешное завершение запроса), а 101 — переключение протоколов. Это происходит потому, что мы отправили HTTP запрос, а хотим получить не только HTTP-ответ, а ещё и другие ответы по WS, сервер как бы предупреждает клиент, что будет присылать ответы множество раз🔙

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"А как сервер узнает, что мы до сих пор подключены?😅"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ответ на данный вопрос достаточно легкий — сервер и клиент играют в пинг-понг😁🏓😳

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сервер периодически присылает ответ по WS с просьбой о действии - послать запрос на сервер. Если клиент отвечает до истечения тайм-аута — он подключен, если нет, то происходит разрыв соединения до следующего рукопожатия👋

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Полезно знать, что все ответы от сервера по WS игнорируются клиентом и сервером, до того как случится рукопожатие🤝

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Почему соединение называется двухсторонним (дуплексным), а ответы мы получаем только от сервера?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На самом деле мы не только получаем ответы от сервера, а ещё и можем в двухстороннем порядке отправлять через WS запросы!😳

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Чтобы лучше понять, давайте рассмотрим лёгенький и полностью задокументированный код на JavaScript:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// Создаем WS-объект, с помощьюю него будем рулить потоками\n// (отправка, принятие запросов)\nconst myWS = new WebSocket(url, protocols);\n\n// До того как сервер и клиент совершат рукопожатие\n// статус у WS-клиента будет CONNECTING\nconsole.log(myWS.readyState); // CONNECTING\n\n// После того как рукопожатие (Handshake) пройдет успешно\n// readyState будет OPEN\nconsole.log(myWS.readyState); // OPEN","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

После того как мы открыли соединение по WS мы сразу же можем отправить сообщение серверу:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"/*\nНе забываем, что мы можем отправить сообщение серверу\nтолько если соединение открыто\nПоместим все общение с сервером внутрь ивента onopen,\nименно он срабатывает, когда соединение открыто\n*/\nmyWS.onopen = function (event) {\n\n\t// Отправляем сообщение по WS\n\tmyWS.send('Привет, сервер!');\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сервер получит данный запрос и возможно захочет ответить, но мы не сможем прочитать ответ! Почему? Да просто потому что у нас нет слушателя на событие получения сообщения от сервера😊 Сделаем же его:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// Вешаем слушатель на принятие сообщений\nmyWS.onmessage = (event) => {\n\t\t\n\t// Делаем с данными все что захотим, но я их просто выведу😊\n\tconsole.log(event.data);\n}","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Закрытие соединения"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// Закрываем соединение\nmyWS.close();\n\n// Ну и естественно слушаем событие onclose, чтобы выполнить какие-то действия\nmyWS.onclose = (event) => {\n\t// ...\n};","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Особенности WS"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Поддерживает двухсторонее соединение в реальном времени","Отправляет заголовок только один раз"],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Дебаггинг WS"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Отлаживать WS-соединение совсем несложно😊 Рассмотрим пример отладки WS на Google Chrome🌎, перейдем на данный сайт: https://websocketstest.com/

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Откроем DevTools, выберем вкладку Networks и перейдем в таб WS:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"46518f88-0e25-5fda-8d2f-1517aea1da93","width":1920,"height":1080,"size":409888,"type":"png","color":"cbcdd5","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Как мы видим ответ от сервера действительно 101 Switching Protocols, однако как нам увидеть данные, которые приходят по WS, вкладки Reponse же нет🤔

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вкладки Response нет, зато появилась новая - Messages. Открываем её и видим там примерно следующее:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"c4eb407f-e4f0-533f-9dfc-f6d0b2cf5fec","width":1920,"height":1080,"size":399823,"type":"png","color":"d0d1d7","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Красной стрелкой вниз показаны пакеты, которые пришли нам (пусть вас не вводит в заблуждение красная стрелка, это не упавшие, а пришедшие пакеты), отправленные пакеты в свою очередь будут показаны зелёной стрелкой, которая стремится вверх⬆

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот и все😊 Если вам было интересно читать статью и вы хотите больше такого контента, то можете перейти в телеграм-канал и подписаться, там много интересного материала✨ Был рад поделиться информацией, увидимся ещё не раз☺

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":11,"favorites":63,"reposts":0,"views":196,"hits":12490,"reads":null,"online":0},"dateFavorite":0,"hitsCount":12490,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/417980-websocket-smotrim-kak-rabotaet-za-kulisami","author":{"id":1178100,"name":"Даниил Шило","nickname":null,"description":"Frontend Engineer в Firecode","uri":"","avatar":{"type":"image","data":{"uuid":"59e8fb72-4a49-5932-af48-bdb368b827e7","width":400,"height":400,"size":28098,"type":"png","color":"dfcac6","hash":"73610f6d350e00","external_service":[]}},"cover":{"cover":{"type":"image","data":{"uuid":"ab766887-ce9f-5181-b1e9-e877385348cb","width":5120,"height":2160,"size":561896,"type":"jpg","color":"85adba","hash":"","external_service":[]}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4265614,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4265614"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":642080,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/642080"}],"lastModificationDate":1764920033,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":15}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":417909,"customUri":null,"subsiteId":1178100,"title":"Пишем конфигурацию для zsh","date":1651944448,"dateModified":1651944448,"blocks":[{"type":"quote","cover":true,"hidden":false,"anchor":"","data":{"text":"

zsh — одна из современных командных оболочек UNIX, использующаяся непосредственно как интерактивная оболочка, либо как скриптовый интерпретатор. Zsh является расширенным аналогом, а также имеет обратную совместимость с bourne shell, имея большое количество улучшений.

","subline1":"Wikipedia"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Иметь красивый и удобный шелл для человека, который ведет разработку в терминале или просто пользуется им 90% рабочего времени — дело хорошее, ещё лучше если этот шелл хорошо настроен, не подлагивает в больших репозиториях git и предоставляет удобные алиасы, этим сегодня и займемся😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В результате всех взаимодействий у нас выйдет такой шелл:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"556eacdb-813a-52c7-b885-3c8fcf2f3ab8","width":1920,"height":1080,"size":156407,"type":"png","color":"2c2f39","hash":"","external_service":[]}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Менеджер плагинов"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Можно конечно копировать все репозитории ручками, однако, если вы перейдете с одной системы на другую, то вам придётся хранить список репозиториев, которые нужно подкинуть себе😳 Это крайне неудобно, да и на дворе 22-й год, поэтому давайте разберемся в том, какие менеджеры плагинов есть у zsh:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["antibody","antigen","zinit","zgen","zplug","zpm"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Поговорим о каждом из них чуть подробнее:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

antigen - Стандарт де-факто среди менеджеров плагинов для zsh. С помощью него удобно устанавливать файлы, т.к. достаточно просто указать команды в .zshrc, сам пакетный менеджер подхватит все команды и выполнит их. У данного пакетного менеджера есть поддержка установки плагинов из репозитория Oh My Zsh, что делает использование самого шелла ещё более удобным, т.к. для Oh My Zsh написали очень много прекрасных плагинов.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

antibody - Предоставляет те же функции, что и antigen, однако является быстрее и делает упор на производительность⚡ Единственный минус у данного менеджера плагинов - то, что он перешёл в maintaince mode, это означает что для него больше не выходят фичи, только фиксятся критические баги.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

zgen - Легковесный и простой менеджер плагинов для zsh. Он генерирует отдельный файлик со списком плагинов, проверяет обновления автоматически и практически не влияет на startup time (время запуска шелла в первый раз). Данный менеджер плагинов также поддерживает установку плагино из репозитория Oh My Zsh😊

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

zinit - Данный менеджер плагинов является очень гибким в настройке. Это единстенный менеджер плагинов, у которого есть Turbo-режим (как написано в самом репозитории). Также сам менеджер плагинов является расширяемым. Для него написаны расширения (annexes), которые добавляют новые команды и функционал.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

zplug - Один из очень медленных менеджеров плагинов, который по иронии является самым удобным😅 Обладает функцией параллельной установки/обновления, поддерживает ленивую загрузку плагинов, есть возможность написать пост и пре-хуки для плагинов, не создает лишние файлы при установке плагинов, есть механизм кэша, для того чтобы уменьшать и без того большую задержку startup time’а.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

zpm - Разработчик указывает что это один из быстрейших менеджеров для плагинов в zsh (внизу будет график, посмотрим, правда ли это). Поддерживает Oh My Zsh плагины, поддерживает множество систем (Linux, Android, OpenWrt, FreeBSD and macOS), асинхронную загрузку, хуки, сам является расширяемым менеджером для плагинов.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Далее будет информация предоставленная с вот этого репозитория😊 Все картинки распространяются по вот этой лицензии.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте глянем на время установки плагинов различными менеджерами:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"1fd9fd96-bd82-5846-ada7-314986d84221","width":1280,"height":960,"size":16834,"type":"png","color":"e9e2ed","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И действительно автор zpm не соврал нам и менеджер быстрее всех устанавливает плагины. Однако нас мало волнует время установки, нам нужно быстрое время запуска самого шелла, так как в будущем при работе с шеллом долгий запуск будет нас только раздражать. Вот startup time у разных менеджеров:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"18a1cf42-f042-5826-a21a-1dace58e829d","width":1280,"height":960,"size":16739,"type":"png","color":"e9dee5","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Тут мы ясно и четко видим, что время загрузки самих плагинов у antibody, antigen, sheldon и zgen — примерно одинаковое, а вот zpm грузит плагины достаточно долго😳, не говоря уже про zplug🐌

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И все-таки какой менеджер плагинов использовать? Одним из лучших по юзабельности является antigen, он является стандартом не просто так. Он достаточно быстро грузит все плагины (как и устанавливает) и не мельтешит перед глазами лишний раз. Один раз установим плагины и забудем даже, что он у нас есть😊

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Конфигурация"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для начала нужно создать .zshrc, который будет находится в корневой директории пользователя ($HOME).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Внутри самого .zshrc следует поместить следующее:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"### PATH VARIABLES ###\nANTIGEN_PATH=\"$HOME/.config/antigen.zsh\" # Путь к самому antigen\nCARGO_PATH=\"$HOME/.cargo/bin\" # Путь для cargo, если у вас есть установленные утилиты на Rust\nPYTHON_PATH=\"$HOME/.local/bin\" # Путь к бинарникам Python, если у вас есть они\nNVM_DIR=\"$HOME/.nvm\" # Путь для Node Version Manager, если он вам нужен\nHISTFILE=\"$HOME/.zsh_history\" # Файл, в который будет записываться вся история команд на zsh\nEDITOR=\"nvim\" # Редактор по умолчанию. Можете подставить любой, который вам нравится\nHISTSIZE=10000 # Количество сохраненных команд в файле\nSAVEHIST=10000 # Как много команд шелл должен помнить в одной сессии (по команде history)\nsetopt appendhistory # Сохраняем всю историю в файл, чтобы она не стиралась, если мы выключим и включим шелл","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Данные настройки у каждого пользователя разные. Хорошей практикой в конфигурации шелла является объявлять все переменные вверху файла, а также допиливать функционал в шелле, которого не хватает стандартными методами (как допиливание сохранения истории из сессии в сессию).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Далее пойдут обычные условия на дополения переменной PATH, данная специальная переменная является строкой, которая подсказывает шеллу откуда таскать бинарники для команд:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"# Проверяем есть ли у нас бинарники Cargo\nif [ -d $CARGO_PATH ]; then\n\texport PATH=$CARGO_PATH:$PATH # Добавляем переменную CARGO к PATH\nfi\n\n# Проверяем есть ли у нас бинарники Python\nif [ -d $PYTHON_PATH ]; then\n\texport PATH=$PYTHON_PATH:$PATH # Добавляем переменную PYTHON к PATH\nfi\n\n# Проверяем скачан ли Antigen\nif [ ! -f $ANTIGEN_PATH ]; then\n echo \"Antigen is not found!\" >&2\n\t\t\n\t# Если antigen не скачан, то качаем его\n\tcurl -L git.io/antigen > $ANTIGEN_PATH\nfi\n\n# Опционально! Все что начинается с данной строки не обязательно должно попадать в ваш .zshrc\n# Я добавил данный кусочек конфигурации, чтобы вы могли увидеть как можно быстро и удобноо скачать NVM😊\n# Проверяем есть ли на системе Node Version Manager\nif [ -d $NVM_PATH ]; then\n\n\t# Если есть - включаем\n\texport NVM_DIR=\"$([ -z \"${XDG_CONFIG_HOME-}\" ] && printf %s \"${HOME}/.nvm\" || printf %s \"${XDG_CONFIG_HOME}/nvm\")\"\n[ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\"\nelse\n\n\t# Если нет - качаем\n\tcurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash\nfi","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Далее наконец-то перейдем к самой установке плагинов🎉 Всё, что нам нужно сделать — подключить сам Antigen, а затем объявить плагины, которые нам нужно скачать😊

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"### PLUGIN MANAGER ###\nsource $ANTIGEN_PATH # Подключаем сам antigen\n\nantigen bundle autojump # Плагин для прыжка по директориям\nantigen bundle git # Плагин для удобных алиасов git\nantigen bundle zsh-users/zsh-syntax-highlighting # Подсветка синтаксиса\n\n# Проверяем есть ли у нас команда exa\ntype exa &> /dev/null &&\n antigen bundle DarrinTisdale/zsh-aliases-exa # Если есть - добавляем для неё алиасы\n\nantigen bundle zsh-users/zsh-autosuggestions # Автодополнение как в Fish\nantigen bundle reobin/typewritten@main # Тема для zsh\n\n# Применяем все настройки, что мы указали😊\nantigen apply","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте пройдемся по всем плагинам, что я здесь указал😊

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Autojump — плагин, который позволяет быстро перемещаться по директориям. Как это работает? Мы перемещаемся по директориям и пишем в них любые команды (напр. ls), autojump запоминает данные директории. Затем вы можете просто написать j dev, и autojump сразу прыгнет к папке dev, если у вас она есть и вы в ней работали🤩","Git — данный плагин является стандартным в Oh My Zsh. Он добавляет алиасы (сокращения для команд) в шелл. Например git status можно написать как gss, git commit - gc, и так далее😊","zsh-syntax-highlighting — плагин, который будет выделять зеленым цветом правильно введенные команды, а красным - нет.","zsh-aliases-exa — плагин, который заменяет стандартный ls, на более продвинутый аналог https://github.com/ogham/exa. Exa в свою очередь удобен тем, что может выводить содержимое директорий вместе с иконками из Nerd Fonts, умеет красиво строить колонки из файлов, раскрашивает вывод самостоятельно.","zsh-autosuggestions — является одним из самых полезных плагинов в списке, он добавляет автодополнение команд. Если до этого нам приходилось нажимать Ctrl + R, для того чтобы найти команду в поиске истории, то теперь это все делается в реальном времени просто при введении символов. Для того чтобы завершить автодополнение достаточно нажать Ctrl + I или Tab, или Ctrl + E."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Последним плагином перед antigen apply является тема для самого шелла😊 Это Typewritten, минималистичная тема, которая имеет асинхронный Git Status

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если вам была интересна данная статья и вы хотите больше подобного материала, то советую вам подписаться на мой канал, я хоть и не часто, но выкладываю там что-то интересное✨

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":18,"favorites":51,"reposts":1,"views":192,"hits":17320,"reads":null,"online":0},"dateFavorite":0,"hitsCount":17320,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/417909-pishem-konfiguraciyu-dlya-zsh","author":{"id":1178100,"name":"Даниил Шило","nickname":null,"description":"Frontend Engineer в Firecode","uri":"","avatar":{"type":"image","data":{"uuid":"59e8fb72-4a49-5932-af48-bdb368b827e7","width":400,"height":400,"size":28098,"type":"png","color":"dfcac6","hash":"73610f6d350e00","external_service":[]}},"cover":{"cover":{"type":"image","data":{"uuid":"ab766887-ce9f-5181-b1e9-e877385348cb","width":5120,"height":2160,"size":561896,"type":"jpg","color":"85adba","hash":"","external_service":[]}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4265614,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4265614"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":642080,"userId":1178100,"count":0,"shareImage":"https://api.vc.ru/achievements/share/642080"}],"lastModificationDate":1764920033,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":21}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}}],"cursor":"PuR2GsZKFTvhhG5QAofi4drVS6IzQAa09I6wtbz/zlbeaPDONLWrxkRf5m5AnW4=","isAnonymized":true}};