progway — программирование, IT
2.68K subscribers
25 photos
1 video
246 links
Чат: @prog_way_chat

Разборы вопросов и задач с собеседований, мысли, полезные материалы и просто вещи, что мне интересны из мира IT

Полезности и навигация в закрепе

По всем вопросам: @denisputnov
Download Telegram
Дженерики

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

Generic Type — обобщённый тип — это тип, который позволяет создавать по своему подобию другие типы или же динамически вычислять типы в зависимости от входных данных. Тут я максимально отойду от какой-то академичности и приведу простую аналогию:

Тип — это переменная
Дженерик тип — это функция, которая возвращает тип

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

const name = "Denis"

const getPerson = (name) => ({
name,
type: "person"
})

const person = getPerson(name)


В этом примере у нас есть переменная, на основе которой мы можем получить некоторый объект person используя функцию getPerson. А теперь рассмотрим нечто похожее в типах:

type Status = "success" | "error"

type Response<T> = {
status: T,
message: string,
code: number,
}

type ApiResponse = Response<Status>


Уже в этом примере мы наблюдаем чем-то похожую ситуацию коду выше — мы создаём какую-то новую сущность на основе уже существующей. В первом случае — новый объект, а во втором — новый тип.

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

На этом моменте у многих новичков часто возникает закономерный вопрос — а зачем их использовать? И ответ на него очень прост — почти за тем же, что и обычные функции.

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

// объявим сущности приложения
type User = {
name: string,
age: number,
}

type Task = {
completed: boolean,
title: string,
}

// ответ api в случае ошибки
type ResponseError = {
code: number;
message: string;
}

// ответ api в случае успеха
type ListResponseSuccess<T> = {
data: T[],
total: number;
}

// общий тип со всеми возможными
// состояниями ответа
type ListResponse<T> = ListResponseSuccess<T> | ResponseError

// типы для наших сущностей
type UserListResponse = ListResponse<User>
type TaskListReponse = ListResponse<Task>

// и их может быть сколько угодно...
type CarListResponse = ListResponse<Car>
type RoleListResponse = ListResponse<Role>
type UserGroupListResponse = ListResponse<UserGroup>
// и тд


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

Это самый базовый пример использования дженериков, который может встретиться на практике. Не забывайте, что дженерики есть не только у типов, а и у интерфейсов, классов, функций… Даже у React-компонентов и хуков (что в целом тоже либо класс либо функция).

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

Спасибо за прочтение, это важно для меня ❤️

#web #theory #typescript
🔥29👍124🤯1🐳1
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * this.radius,
};

console.log(shape.diameter());
console.log(shape.perimeter());


@prog_way_blogчат#quiz
🔥7🐳3
Что будет в консоли?
Anonymous Quiz
38%
20 и 62.83185307
41%
20 и NaN
6%
20 и 63
14%
NaN и 63
👍13🐳4🔥1
Шпаргалка по Utility-типам в TypeScript

В этом посте разберу только самые часто используемые, посмотреть все utility-типы можно в официальной документации.

type Person = {
name: string;
surname: string;
age: number
}


Partial<T>
Делает все ключи необязательными
type PartialPerson = Partial<Person>

// то же самое, что
type PartialPerson = {
name?: string;
surname?: string;
age?: number
}


Required<T>
Обратное действие Partial — делает все ключи обязательными
Required<Partial<Person>> === Person


Readonly<Type>
Запрещает изменять поля объекта
const person: Readonly<Person> = {
name: "Denis",
surname: "Putnov",
age: 22
}

// ошибка, так как
// person нельзя изменять,
// только считывать (readonly)
person.name = 'Денис'


Record<Keys, Values>
Создаёт тип из ключей и значений, указанных отдельно
// ключи могут быть только строкой
// значения - только числом
type WordCounter = Record<string, number>

type Status = 'success' | 'error'
type StatusCounter = Record<Status, number>

// то же самое, что и Record ранее
type StatusCounter = {
success: number
error: number
}


Pick<Type, Keys>
Выбирает только нужные ключи из типа и возвращает новый тип
// из типа Person выбираем только свойства
// name и surname
type PersonName = Pick<Person, 'name' | 'surname'>

// то же самое
type PersonName = {
name: string
surname: string
}


Omit<Type, Keys>
Удаляет ненужные свойства из типа и возвращает новый тип
// Из типа Person исключаем свойство 'age'
type PersonName = Omit<Person, 'age'>;

// то же самое
type PersonName = {
name: string
surname: string
}


Parameters<Function>
Возвращает тип параметров функции
type Foo = (a: number, b: number) => string
type Params = Parameters<Foo>

Params // [a: number, b: number]


ReturnType<Function>
Возвращает тип возвращаемого параметра из функции
type Foo = (a: number, b: number) => string
type Return = ReturnType<Foo>

Return // string


Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#theory #typescript #useful
🔥408👍5🐳4
Че там в React 19

У меня сейчас, к сожалению или к счастью, нет времени полностью посмотреть React Conf, которая длится аж 4 часа, что имхо очень много.
Я пролистал конфу и нашёл пару интересных моментов, которыми хочу поделиться тут.

Как я понял, React Compiler — это что-то, что стоит сравнивать скорее с компилятором TypeScript. Также один из спикеров сравнивает его с компиляторами типа WebAssembly и Hermes, который сейчас используется в React Native. По сути, всё, что он делает — разбирает весь ваш код и представляет его в виде полностью контролируемого графа. Сам по себе компилятор содержит очень много оптимизаций, типа автоматического удаления неиспользуемого кода, распространения констант и ещё куча умных слов. Все эти техники оптимизации не новы, но спикеры подмечают, что, конечно же, подсосали всё из Rust и C++, что не удивительно.

Цель такого компилятора — дополнительно оптимизировать весь наш код без видимых изменений для разработчика, тем самым улучшить и DX, и UX.

Что наиболее интересно — компилятор приносит нам такое интересное понятие как Computation Graph. Как я понимаю, это понятие мы ещё не раз услышим в будущем.

Computation Graph — граф, который отображает вычислительные зависимости между сущностями в коде. Показывает то, каким образом данные влияют на другие данные и на вёрстку в целом. Что-то типа useMemo и других хуков, когда мы напрямую указывали реакту, изменение чего конкретно нужно отслеживать и что конкретно нужно пересчитать. Теперь этим полностью занимается компилятор.

Тут обращаем внимание на скрины. Со 2 по 6 мы видим то, что такое Computation Graph и как он получается из нашего кода. На 7 скрине мы видим как это работает. Из полученного графа мы можем узнать минимально нужный перечень сущностей для обновления, то есть что конкретно мы хотим обновить, при этом не затрагивая остальные несвязанные ветви графа.

Во что конкретно компилятор собирает это — я пока не досмотрел. Но даже эта ограниченная информация — очень интересно)

@prog_way_blogчат#theory #react #news
👍245🔥2🤯1🐳1
Какие ошибки есть в JavaScirpt?

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

Что вообще такое ошибка? Ошибка — ответ программы на возможное неожиданное, некорректное поведение. Всего в JavaScript существует всего 7 встроенных ошибок, но также есть возможность создавать собственные, что я уже разбирал в отдельном посте ранее.

Также важно знать, что в языке есть встроенная конструкция

try {
// потенциально ошибочный код
} catch (error) {
// обработка ошибки из
// участка кода выше
}


Этой вводной должно быть достаточно, перейдём к самим ошибкам:

SyntaxError — ошибка, связанная с некорректным синтаксисом в программе, то есть некорректной, постановкой скобок, точек с запятой и прочих символов:

console.log(()
// Uncaught SyntaxError: Unexpected token ')'


Reference Error — возникает при попытке обратиться к несуществующей переменной

progway.length
// ReferenceError: progway is not defined


Type Error — возникает при попытке обратиться к несуществующему свойству объекта или попытке вызвать то, что вызвать нельзя

console.log(null.length)
// TypeError: Cannot read property 'length' of null

undefined()
// TypeError: undefined is not a function


Range Error — возникает, когда мы выходим за диапазон допустимых значений

new Array(10_000_000_000)
// RangeError: Недопустимая длина массива


URIError — возникает при некорректной обработке URI встроенными средствами языка

decodeURIComponent('%')
// URIError: URI malformed


Eval Error — по сути, любая вышеперечисленная ошибка внутри функции eval

eval('progway.length')


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

Пост вдохновлён статьей с доки

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#theory #javascript
🔥20👍94🐳1🤓1
В чём разница между функцией, методом и процедурой

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

1. Функция — полностью независимый блок, который может быть объявлен где угодно в коде. Каждая функция возвращает значение и может принимать аргументы:

// самая настоящая функция, причём чистая
function add(a, b) {
return a + b;
}


2. Процедура — то же самое, что и функция, но процедура лишь выполняет какие-то действия, но ничего не возвращает:

// что-то делаем, но ничего не возвращаем
function greet(name) {
console.log("Hello, " + name);
}


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

let obj = {
x: 4,
// метод
double: function() {
return this.x * 2;
}
};

let result = obj.double();
console.log(result); // Выведет: 8


Принципиально ли использовать правильные названия для каждого из случаев? Для такого душнилы, как я, — да. Я считаю, что верная терминология делает любой разговор более продуктивным и предметным, чем “ну вот это фигня там вот с этой фигней”. Понять друг друга можно и без терминологии, но с ней — гораздо проще. Да и звучите вы профессиональнее, если это для кого-то важно.

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#theory #javascript
🔥33👍175👏1🐳1
Семантическое версионирование

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

<major>.<minor>.<patch>-<preRelease>


1. Мажорные версии (major): Увеличиваются, когда вносятся несовместимые изменения API.
2. Минорные версии (minor): Увеличиваются, когда добавляются новые функции, сохраняя обратную совместимость.
3. Патч-версии (patch): Увеличиваются, когда выпускаются исправления ошибок, сохраняя обратную и прямую совместимость.
4. Предрелизные версии (preRelease): Можно добавлять к номеру версии суффиксы, чтобы обозначить предварительные версии (например, alpha, beta, rc).

Примеры:

1.0.0
1.2.3
2.0.0
1.5.0-alpha.1
1.5.0-rc.2
4.2.1-snapshot.4
5.18.2-test.1


Кстати, обычно суффиксы встречаются такие:
1. dev — версия, которая может содержать изменения, находящиеся в процессе разработки, и не является полностью стабильной
2. alpha — первая версия программного продукта, которая обычно имеет ограниченный набор функций и может содержать много ошибок
3. beta — версия, которая следует за альфа-версией и обычно уже более стабильна. В бета-версию могут быть добавлены новые функции, но она все еще может содержать некоторые ошибки
4. release-candidate (rc) — версия, которая считается готовой к выпуску, но перед официальным релизом требует дополнительного тестирования и обратной связи от пользователей
5. preview — версия, которая предварительно показывает новые функции или изменения, которые будут включены в будущую версию
6. test — версия, которая используется для тестирования новых функций или исправлений перед их включением в основную ветку разработки

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#theory #useful
19👍8🔥6🤔1🐳1
💬Напоминаю, что у канала есть чат

Хочется создать очень ламповое и приятное место для общения на основе канала

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

Обязательно присоединяйтесь, приглашайте друзей — давайте общаться 🤝🤝

@prog_way_blogчат#blog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥3🐳3💯1
Что такое мемоизация

Мемоизация — техника оптимизации кода, сокращающая время его исполнения.

Представим, что у нас есть функция foo, которая выполняется 10 секунд. Нам нужно вызвать эту функцию 3 раза подряд с одними и теми же аргументами. Несложно посчитать, что такой код будет выполняться 30 секунд. Но мы можем применить мемоизацию:
1. Вызовем функцию, подождём 10 секунд и сохраним результат её выполнения в кэш
2. Вызовем функцию ещё раз с теми же аргументами. Вместо того, чтобы исполнять тяжелый код, обратимся к кэшу и проверим нет ли там нужного для нас результат вычислений
3. Если нужный результат есть, вернём его. Если нет, см. шаг 1

Любую ли функцию можно мемоизировать? Конечно же нет. Мемоизировать можно только чистые функции. О том, что это такое, я писал отдельный пост.

Также стоит учитывать, что со временем жизни программы кэш может очень сильно разрастись. Из-за этого сама программа может занимать очень много памяти. Решением может быть несколько подходов:
1. Установить время жизни для каждого из значение кэша
2. Подход LRU (Last Recently Used), когда мы удаляем из кэша значения, которые дольше всего не запрашивались. Такой подход позволяет сохранять в кэше только самые часто используемые данные.
3. Ограничение размера кэша, то есть, когда кэш достигает порогового значения по количеству записей или памяти, старые записи удаляются для освобождения места под новые
4. Просто удалять весь кэш по какому-то таймауту

Сложность тут в том, что нам необходимо реализовать собственный кэш с поддержкой всех необходимых апи для того, что мы описали выше. В JavaScript, например, LRU кэша из коробки, то есть его придётся написать самостоятельно. Самая примитивная же форма кэша для мемоизации, которая и используется чаще всего — Map или же обычный объект.

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

В следующем посте рассмотрим реализацию примитивной мемоизации в коде.

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#theory #javascript #patterns #data
👍30🐳5🔥42
+true;
!'progway';
!!true


@prog_way_blogчат#quiz
🐳6
Реализация мемоизации в JavaScript

О том, что такое мемоизация, я рассказывал в прошлом посте.

На самом деле, всё это довольно просто. Попробуем написать функцию-обёртку memoize, с помощью которой можно мемоизировать любую другую функцию. Начнём как всегда с интерфейса:

function memoize(func) {
// ...
}


Наша функция-обёртка будет принимать только целевую функцию, которую мы хотим мемоизировать. В качестве кэша будем использовать Map:

const cache = new Map();


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

return function(...args) {
// ключ сериализуем в строку, чтобы
// не было проблем с аргументами
// объектами, массивами и т.д.
const key = JSON.stringify(args);

// проверяем есть ли результат в кэше
if (!cache.has(key)) {
// если нет, то сохраняем в кэш
// значение вызова функции
cache.set(key, func(...args));
}

// и возвращаем значение из кэша
return cache.get(key);
};


Полный код функции:


function memoize(func) {
const cache = new Map();

return function(...args) {
const key = JSON.stringify(args);

if (!cache.has(key)) {
cache.set(key, func(...args));
}

return cache.get(key);
};
}


Я осознанно не использовал TypeScript, так как он сильно визуально усложняет эту функцию и объяснений получается много. Натыкайте 🔥 на пост и я разберу как правильно типизировать такую функцию

@prog_way_blogчат#theory #code #javascript #data
🔥76👍5🐳21😁1