<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Тайная техника Хокаге: Полиморфизм и Дженерики в TypeScript]]></title><description><![CDATA[<p dir="auto">Всем здрасьте!<br />
Сегодня хочу разобрать две важные темы, которые тесно пересекаются между собой, а именно - Полиморфизм и Дженерики (обобщенные типы).</p>
<h2>Поехалииииииии</h2>
<p dir="auto">Дженерики и полиморфизм являются связанными концепциями в программировании, особенно в контексте объектно-ориентированного программирования (ООП) и языков, поддерживающих статическую типизацию, таких как TypeScript. Однако они не являются одним и тем же понятием; каждый из них имеет свои особенности и применения.</p>
<h3>Дженерики</h3>
<p dir="auto">Дженерики — это механизм, позволяющий писать код, который может работать с различными типами данных без потери типовой безопасности. Они используются для создания универсальных компонентов, таких как коллекции, функции и классы, которые могут работать с любыми типами данных, сохраняя при этом строгую проверку типов.</p>
<pre><code class="language-ts">function identity&lt;T&gt;(arg: T): T {
    return arg;
}
</code></pre>
<p dir="auto">Здесь T является переменной типа параметра, которая позволяет функции identity принимать аргументы любого типа и возвращать значение того же типа.</p>
<h3>Полиморфизм</h3>
<p dir="auto">Полиморфизм — это способность системы использовать объекты разных типов, но с похожими интерфейсами, так, чтобы эти объекты могли взаимодействовать друг с другом без необходимости изменения их внутренней реализации. Полиморфизм позволяет программисту писать более гибкий и модульный код, поскольку различные классы могут реализовывать один и тот же интерфейс по-разному.</p>
<pre><code class="language-ts">class Animal {
    speak() {
        console.log('Это животинка');
    }
}

class Pig extends Animal {
    speak() {
        console.log('Хрюшка делает: хрю хрю');
    }
}
</code></pre>
<p dir="auto">Здесь <strong>Animal</strong> является базовым классом с методом <strong>speak</strong>, а <strong>Pig</strong> является подклассом, который переопределяет этот метод, предоставляя свою уникальную реализацию.</p>
<h3>Ну а теперь о главном</h3>
<p dir="auto">Что такое конкретные типы в TypeScript - это:</p>
<pre><code class="language-ts">// Пример если что
boolean;
string;
Date[];
{a: number} | {b: string};
(number: number[]) =&gt; number;
</code></pre>
<p dir="auto">Конкретные типы полезны, но полезны когда Вы точно знаете какой ожидается тип и хотите сверить его с переданным типом. Но иногда это нецелесообразно ограничивать поведение функций конкретным типом.</p>
<p dir="auto">Давай представим с Вами функцию <code>filter</code> для итерации по массиву и его очистки.<br />
В JS она может выглядеть примерно так:</p>
<pre><code class="language-js">function filter(array, f) {
  let result = []
  for (let i = 0; i &lt; array.length; i++) {
    let item = array[i]
    if(f(item)) {
      result.push(item)
    }
  }
}

filter([1, 2, 3, 4], _ =&gt; _ &lt;= 3) // вычисляется как [1, 2]
</code></pre>
<p dir="auto">А теперь давайте приступим с извлечения сигнатуры типа filter и добавления временных заместителей <code>unknown</code> для типов:</p>
<pre><code class="language-ts">type Filter = {
  (array: unknown, f: unknown) =&gt; unknown[]
}
</code></pre>
<p dir="auto">Далее попробуем заполнить типы, к примеру возьмем <code>number</code>:</p>
<pre><code class="language-ts">type Filter = {
  (array: number[], f: (item: number) =&gt; boolean): number[]
}
</code></pre>
<p dir="auto">Такая сигнатура будет работать для массива чисел, но не как не строк и уже тем более не объектов и других массивов. Давайте теперь попробуем использовать перегрузку для ее расширения:</p>
<pre><code class="language-ts">type Filter = {
  (array: number[], f: (item: number) =&gt; boolean): number[]
  (array: string[], f: (item: string) =&gt; boolean): string[]
}
</code></pre>
<p dir="auto">Пока вроде все отлично, но прописывать перегрузку для каждого типа - такая себе идея.<br />
А может еще жахнем массив объектов ?</p>
<pre><code class="language-ts">type Filter = {
  (array: number[], f: (item: number) =&gt; boolean): number[]
  (array: string[], f: (item: string) =&gt; boolean): string[]
  (array: object[], f: (item: object) =&gt; boolean): object[]
}
</code></pre>
<p dir="auto">В принципе выглядит неплохо, но если реализовать функцию <code>filter</code> с сигнатурой <code>filter: Filter</code> и ее использовать, то получим следующее:</p>
<pre><code class="language-ts">let names = [
  {firstName: 'Lox'},
  {firstName: 'Debik'},
  {firstName: 'Dodik'},
]

let result = filter(
  names,
  _ =&gt; _.firstName.startsWith('L')
) // Ошибка TS2339: свойство 'firstName' не существует в типе 'object'.

result[0].firstName // Ошибка TS2339: свойство 'firstName'
                    //не существует в типе 'object'.
</code></pre>
<p dir="auto">В этом месте должно стать понятно, почему TypeScript выдает ошибку.<br />
Мы сообщили ему, что можем передать массив чисел, строк или объектов в <code>filter</code>,  и передали массив объектов. Но как Вы уже должны знать, <code>object</code> ничего не сообщает TypeScript о конкретной форме самого объекта.</p>
<p dir="auto">И что же делать спросите вы?</p>
<p dir="auto">Если ранее вы писали на языке, который поддерживает обобщенные типы, то наверняка хрюкните “ХОЧУ ОБОБЩЕННЫЕ ТИПЫ”!<br />
Замечательная новость в том, что вы правы, а вот плохая - вы только что разбудили @Jspi</p>
<p dir="auto">Для несведущих начну с определения обобщенных типов, а затем приведу пример с нашей функцией.</p>
<h4>ПАРАМЕТР ОБОБЩЕННОГО ТИПА</h4>
<p dir="auto"><em>Замещающий тип, используемый для применения ограничений на уровне типов в нескольких местах. Также известен как параметр полиморфного типа.</em></p>
<p dir="auto">Едем дальше. Вот как будет выглядеть тип <code>filter</code>, если мы перепишем его с параметром обобщенного типа <code>T</code>:</p>
<pre><code class="language-ts">type Filter = {
  &lt;T&gt;(array: T[], f: (item: T) =&gt; boolean): T[]
}
</code></pre>
<p dir="auto">Таким образом мы сообщили: “Функция <code>filter</code> использует параметр обобщенного типа <code>T</code>. Мы заранее не знаем, каким будет тот или ной тип в дальнейшем, поэтому, TypeScript, если ты сможешь сделать его вывод при каждом вызове <code>filter</code>, то было бы замечательно”.<br />
TypeScript выводит тип <code>T</code> на основе типа, который мы передаем для <code>array</code>. Как только TypeScript делает вывод, чем является <code>T</code> для вызова <code>filter</code>, он подставляет этот тип для каждого видимого <code>T</code>.<br />
<code>T</code> выступает в роли замещающего типа, который заполняется модулем проверки на основе контекста. Он параметризует тип <code>Filter</code>, поэтому мы и зовем его параметром обобщенного типа.</p>
<p dir="auto">“<em>Фраза параметр обобщенного типа часто заменяется на обобщенный тип или обобщение.</em>”</p>
<p dir="auto">Вообще для объявления обобщенных типов используются угловые скобки <code>(&lt;&gt;)</code> А еще их называют ДЖЕНЕРИКАМИ (<a href="https://www.typescriptlang.org/docs/handbook/2/generics.html" target="_blank" rel="noopener noreferrer">Generics</a>), воспринимайте их как ключевое слово <code>type</code>.<br />
Место размещения скобок определяет диапазон охватываемых типов (да, кстати есть всего несколько мест где вы можете их использовать). TypeScript в свою очередь убеждается, что внутри этого диапазона все экземпляры параметров обобщенных типов привязаны к одному реальному типу. В текущем примере при вызове <code>filter</code> TypeScript привяжет конкретные типы к обобщенному типу <code>T</code> в зависимости от обозначенного скобками диапазона. Какой именно тип привязывать к <code>T</code>, он решит исходя из того, с каким типом мы вызовем <code>filter</code>. Между угловых скобок мы можем объявить столько обобщений, сколько пожелаем, разделив их точкой с запятой.</p>
<p dir="auto"><em>T - это просто имя типа, такое же как А, LOX, baloven, 2k24. Но в мире TS принято использовать имена состоящие из одной заглавной буквы, так что всего скорее в чужом коде вы встретите такие имена типов как T, U, V, W и т.п.</em></p>
<p dir="auto"><em>Если конечно вы используете множество типов или какие-то сложные образы то конечно лучше стоит рассмотреть вариант вроде Value или WidgetType.</em></p>
<p dir="auto"><em>А еще некоторые программисты предпочитают начинать с А вместо Т. Это зависит от сообщества пользователей разных языков программирования которые делают выбор согласно устоявшейся традиции. Например функциональщики предпочитают A, B, C. Разработчики ООП склонны использовать Т или type. TypeScript поддерживает любой стиль, но предпочтительнее используется последний</em></p>
<p dir="auto">ПРОДОЛЖАЕМ!<br />
Подобно тому как параметр функции повторно привязывается при каждому вызове функции, также и каждый вызов <code>filter</code> получает свою привязку для T:</p>
<pre><code class="language-ts">type Filter = {
  &lt;T&gt;(array: T[], f: (item: T) =&gt; boolean): T[]
}

let filter: Filter = (array, f) =&gt; // ...

// (a) T привязан к number
filter([1,2,3], _=&gt;_&gt;2)

// (b) T привязан к строке
filter(['a', 'b'], _=&gt;_!=='b')

// (c) T привязан к {firstName: string}
let names = [
  {firstName: 'lox'},
  {firstName: 'baloven'},
  {firstName: 'kapysha'}
]
filter(names, _=&gt;_.firstName.startsWith('b'))
</code></pre>
<p dir="auto">TypeScript делает вывод привязок обобщенных типов на основе типов переданных аргументов. Глянем, как привязывает T для (a):</p>
<ul>
<li>Исходя из сигнатуры <code>filter</code> TypeScript знает, что array - это массив элементов некоего типа Т.</li>
<li>TypeScript замечает, что мы передали в массив <code>[1, 2, 3]</code>, а значит Т должен быть <code>number</code></li>
<li>Везде, где TypeScript видит Т, он заменяет его на <code>number</code>. Следовательно параметр <code>f: (item: T) =&gt; boolean</code> становится <code>f: (item: number) =&gt; boolean</code>, а возвращаемый тип T[] становится number[].</li>
<li>TypeScript проверяет типы на совместимость и убеждается, что функция, которую мы передали как f, совместима со своей только что выведенной сигнатуры.</li>
</ul>
<p dir="auto">Обобщенные типы - это эффективный способ выразить более обширное действие функции, чем это позволяет конкретный тип. Воспринимать же их стоит в виде ограничений. Как аннотирование параметра функции в виде <code>n: number</code> ограничивает значение параметра <code>n</code> типом <code>number</code>, так и использование обобщенного типа Т ограничивает тип любого привязываемого к Т условием типа быть одинаковым в каждом Т.</p>
<p dir="auto"><em>Обобщенные типы также могут применяться в псевдонимах типов, классах и интерфейсах.</em></p>
<p dir="auto">Ну а на этом я предлагаю завершить данную статью и ознакомление с так называемыми Дженериками.</p>
<p dir="auto">P.S. В последующих статьях я хочу более подробно рассмотреть как привязывать конкретные типы к обобщенным, а также где можно их объявлять.</p>
]]></description><link>https://forum.exlends.ru/topic/15/tajnaya-tehnika-hokage-polimorfizm-i-dzheneriki-v-typescript</link><generator>RSS for Node</generator><lastBuildDate>Sun, 31 May 2026 00:53:48 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/15.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 18 Jun 2024 17:19:42 GMT</pubDate><ttl>60</ttl></channel></rss>