company_banner

12 советов по внедрению TypeScript в React-приложениях

    TypeScript — стандарт современной фронтенд-разработки. Согласно исследованиям State of JavaScript, TS вызывает явный интерес у программистов. По данным опроса за 2019 год, почти 60% респондентов пробовали TS и продолжают использовать, 22% не пробовали и желают изучить.

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



    Я придерживаюсь контекста React-приложения, однако некоторые советы будут актуальны и в других экосистемах.

    Совет 1: нетерпимость к ошибкам


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

    1. Сделайте проверку для dev-режима, чтобы сразу видеть ошибки.
    2. Добавьте валидацию в CI на каждый пул-реквест. Это даст уверенность, что в основной ветке всегда правильные типы.

    Существует два основных способа компиляции ts-файлов: оригинальный TSC и @babel/preset-typescript. Метод babel интересен: из .ts файлов вырезается информация о типах, за счет чего сборка получается быстрой. Вот статья о внедрении.

    Проверка типов в случае с babel добавляется дополнительно, с помощью fork-ts-checker-webpack-plugin. В режиме разработки проверки стоит делать асинхронно, чтобы не блокировать сборку и не мешать писать код. При пул-реквестах проверка типов, наоборот, должна останавливать билд, если найдена ошибка.

    Совет 2: контракты с бэкендом


    Общение с бэкендом через API само по себе подразумевает контракт обмена данными. Полезно выделить эти сущности в отдельный слой, создавая отдельные интерфейсы. Мы называем их DTO — Data Transfer Object.

    Чтобы внедрить такой слой, нужны некоторые договоренности. Вот пример правил, которые можно внедрить в команде:

    • Имя интерфейса имеет суффикс Dto: FooBarDto.
    • Располагается в специальной папке.
    • Может измениться только с изменением API.
    • Данные такого интерфейса используются лишь для чтения, редактирование запрещено.

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

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

    Совет 3: алиасы на any


    Вы и ваша команда пишете превосходно типизированный код — без проблем, ошибок и точно в срок. Если это так, переходите к следующему пункту ;)

    В реальности же возникают соблазны опустить типизацию, особенно при внедрении TS в существующий проект. Хочется явно или неявно использовать any. Есть адекватные причины: сложный код, срочная задача, нецелесообразность — усилия не окупают результат.

    Вместо того чтобы игнорировать проблему, попробуйте ее «легализовать»:

    export type TODO_ANY = any;
    

    Плюсы:

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

    const getArrayLength = (array: any[]) => array.length

    Ограничения:

    • Слишком частое использование этого приема вряд ли принесет пользу.
    • Нужна сознательность и ответственность, чтобы в будущем убрать такие затычки.

    Также вам может пригодиться:

    export type TODO_UNKNOWN = unknown;
    

    В случае с any компилятор будет позволять практически любое действие. В случае с unknown — наоборот, практически любое использование приведет к ошибке. Поставили TODO_UNKNOWN, пока не ясен тип объекта → в будущем использовали объект → получили ошибку → исправили тип → вы восхитительны.

    Совет 4: типы для библиотек


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

    А что если у вас внутренняя библиотека, в которой типов точно нет? Попробуйте сгенерировать их автоматически. Хорошая новость для использующих jsDoc: в TypeScript 3.7 tsc-компилятор научился генерировать типы .d.ts как из .ts, так и из .js файлов. 

    Другая хорошая новость: существуют конвертеры js -> ts. Например, TypeStat помог мне справиться с типичными React-компонентами.

    Плохая новость: вряд ли автоматически получится хорошая система типов. Рассчитывайте на «скелет», на который впоследствии надо будет нарастить «мясцо».

    Совет 5: типы поверх типов


    … и типами погоняют!

    Бывает, что типы внешних зависимостей проекта не подходят — слишком общие (а вы хотели бы конкретней), содержат ошибку, не подходит версия — или же типы просто отсутствуют.

    Обычно такие проблемы возникают во внутренних библиотеках (которые еще и часто обновляются), во внешних — довольно редко. Если проблема появилась, попробуем вручную определить/переопределить типы.

    Опишем тип для модуля example. Подчеркну, что переопределяется только тип, имплементация не меняется:

    declare module "example" {
        const foo: { bar: string };
        export default foo;
    }
    

    Такое переопределение лучше вынести в отдельную директорию и файл, например typings/example.d.ts. Подключим все содержимое из typings в поле compilerOptions.typeRoots в tsconfig:

    {
        "compilerOptions": {
            "typeRoots": ["node_modules/@types", "./typings"]
        }
    }
    

    Вуаля — типы для example берутся из переопределения!

    Совет 6: стайлгайд


    Type или interface? const или enum? Как описать типы в функциональном компоненте? А в мемоизированном?

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

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

    • Возьмите за основу популярные стайлгайды. Мне нравятся эти: react-typescript-cheatsheet, react-redux-typescript-guide, modern-typescript-with-examples-cheat-sheet.
    • Проработайте линтеры и форматеры — пусть черную работу делают машины.
    • Добавьте специфику своего проекта: типичные шаблоны, лучшие практики, худшие практики.
    • Зафиксируйте эти решения и ссылайтесь на них при код-ревью.
    • Обеспечьте команде возможность обсуждать и менять эти решения.

    Хорошо построенный стайлгайд открывает чудеса CPDD (Copy Paste Driven Development): разработчики пишут в едином стиле, тратят меньше времени благодаря готовым бойлерплейтам. Не устраиваются эпичные баталии табы vs пробелы (пардон, типы vs интерфейсы), упрощается вхождение новичков в проект.

    P.S.: Еще один источник вдохновения — библиотеки ts-утилит. Найдете не только полезные хелперы, но и код от профи в исходниках. Например, utility-types, SimplyTyped.

    Совет 7: статистика


    Внедряете что-то новое? Ведите учет статистики.

    Зачем:

    • Отслеживать динамику работы.
    • Рефлексировать на промежуточных результатах.
    • Добавлять позитивчик команде — «Смотрите, какие мы молодцы, еще на шаг ближе к цели».



    ⌘ ⌘ ⌘

    На этом закончу блок с общими советами по использованию TS. 

    Во второй части пройдемся по интересным приемам TS, которые стоит применять в своих проектах.

    ⌘ ⌘ ⌘

    Не совет — минимум по TS


    Небольшой минимум знаний для самопроверки. Проверьте, понимаете ли вы, что тут происходит:

    const example = {
        a: "foo",
        b: "bar"
    };
    
    // операторы typeof и keyof
    type Ex1 = typeof example; // { a: string; b: string }
    type Ex2 = keyof typeof example; // 'a' | 'b'
    
    // популярные встроенные утилиты для работы с типами
    type Ex3 = Pick<typeof example, "a">; // { a: string };
    type Ex4 = Omit<typeof example, "a">; // { b: string };
    type Ex5 = Partial<typeof example>; // { a?: string; b?: string} 
    type Ex6 = Record<'a' | 'b', number>; // { a: number; b: number };
    
    // утилиты для работы с функциями
    const func = (a: string, b: number) => true;
    type Ex7 = ReturnType<typeof func>; // boolean
    type Ex8 = Parameters<typeof func>; // [string, number]
    

    Совет 8: литеральный тип vs enum


    TypeScript вводит удобную синтаксическую структуру — enum. Enum используется для конечных перечислений. Освоив enum, поначалу хочется писать его очень часто. Однако у enum есть справедливая критика, связанная с генерируемым кодом после компиляции.

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

    Жизненный пример — простые пропсы компонентов:

    // вариант с enum
    enum ColorEnum {
        yellow = "yellow",
        transparent = "transparent"
    }
    
    const ExampleEnum = (props: { color: ColorEnum }) => <div>{props.color}</div>;
    
    // вариант с литеральным типом
    type ColorLiteral = "yellow" | "transparent";
    
    const ExampleLiteral = (props: { color: ColorLiteral }) => <div>{props.color}</div>;
    
    // применение обоих вариантов
    const Example = () => (<>
            <ExampleEnum color={ColorEnum.yellow} />
            <ExampleLiteral color="yellow" />
     </>);
    

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

    Предпочтите строковые литералы для простых проверок типов. Используйте enum для описания более сложной логики. При этом лучше отдать предпочтение const enum (однако эта структура не поддерживается babel, придется добавлять плагин).

    Совет 9: const assertions


    Еще один повод реже использовать enum — const assertions.

    as const говорит компилятору, что данный объект меняться не будет. Значит, можно более детально вывести типы.

    const loadAction = {
        start: "LOAD_START",
        done: "LOADED",
        error: "LOAD_ERROR"
    } as const;
    
    type LoadActionType = typeof loadAction;
    type LoadActionKey = keyof LoadActionType; // 'start' | 'done' | 'error'
    type LoadActionValue = LoadActionType[LoadActionKey]; // 'LOAD_START' | 'LOADED' | 'LOAD_ERROR'
    // без "as const" LoadActionValue выводится как string
    

    Также можно применять as const и к массивам:

    const DIRECTION_ASC = 1;
    const DIRECTION_DESC = -1;
    const sortDirectionTypes = [DIRECTION_ASC, DIRECTION_DESC] as const;
    type SortDirection = typeof sortDirectionTypes[number]; // -> 1 | -1
    

    Это удобно использовать для перечислений. Например, проверить, что в словаре описаны все варианты:

    const sortDirectionDescription: Record<SortDirection, string> = {
        [DIRECTION_ASC]: "От меньшего",
        [DIRECTION_DESC]: "От большего"
    };
    const sortDirectionDescription2: Record<SortDirection, string> = {
        [DIRECTION_ASC]: "От меньшего"
    };
    // Error: TS2741: Property '[-1]' is missing // in type '{ [DIRECTION_ASC]: string; }'
    

    P.S.: Более подробно про const assertions можно прочитать в этой статье.

    Совет 10: брендированные типы


    Brand — маленькая утилита для создания уникальных («брендированных») типов. Обычно такие типы создаются на основе примитивных — строк, чисел.

    Например, при работе с данными постоянно используются id. Иногда крайне важно отличать один тип id от другого (например, id пользователя от id его заказа в магазине), хотя и то, и другое — строки. С помощью Brand как раз можно получить два различных типа:

    type Brand<T, U> = T & { __brand: U };
    
    type UserId = Brand<string, "userId">; // "5435436" – пример id пользователя
    type OrderId = Brand<string, "orderId">; // "42da-fet3-cdsl" – пример id заказа
    
    declare const userID = "1111" as UserId; // какой-то пользователь
    declare function getOrderById(id: OrderId): any; // какая-то функция
    
    getOrderById(userID);
    // Error: TS2345
    // Type '"userId"' is not assignable to type '"orderId"'.
    

    Чуть больше теории по этой теме — о номинальных и структурных типах — можно найти на Хабре, а еще тут и тут.

    Совет 11: discriminated union types


    Сложный термин discriminated union (разграниченные/размеченные объединения) легко понять на практике: создается объединение нескольких типов с общей частью (дискриминантом). При работе с таким объединением ts-компилятор может отфильтровывать варианты и конкретизировать тип.

    Классический пример использования discriminated union прекрасно ложится на шаблон редьюсера. Благодаря общему полю type тип для payload выводится автоматически:

    type Action =
        | { type: 'SET_ONE'; payload: string }
        | { type: 'SET_TWO'; payload: number };
    
    export function reducer(state: AppState, action: Action): AppState {
        switch (action.type) {
            case 'SET_ONE':
                return { ...state, one: action.payload}; // `payload` is string
            case 'SET_TWO':
                return { ...state, two: action.payload}; // `payload` is number
            default:
                return state;
        }
    }
    

    Совет 12: never


    Never — хорошая страховка от необработанных кейсов. Возьмем из предыдущего пункта, только без 'SET_TWO', и с добавкой typeguard: never;

    type Action =
        | { type: 'SET_ONE'; payload: string }
        | { type: 'SET_TWO'; payload: number };
    
    export function reducer(state: AppState, action: Action): AppState {
        switch (action.type) {
    
            case 'SET_ONE':
                return { ...state,
                            one: action.payload}; // `payload` is string
    
            default:
                const typeguard: never = action;
                return state;
                // Error: TS2322: Type '{ type: "SET_TWO"; payload: number; }'
                // is not assignable to type 'never'.
        }
    }
    

    Компилятор выводит тип для typeguard и приходит к противоречию. С одной стороны — тип которого нет, never. С другой — выводимый тип 'SET_TWO'. Типы не сходятся, получаем ошибку.

    Еще один интересный (но редкий) кейс использования never в React — запретить вложенность или передачу пропсов:

    type IconProps = {
        size: 's' | 'm';
        children?: never;
        foo?: never;
    };
    
    const Icon: React.FC<IconProps> = () => <div>Whatewer</div>;
        
    const Example = () => (<>
        <Icon size="s"> <div /> </Icon> {/* Ошибка – передается children */}
        <Icon size="s" foo="123"/> {/* Ошибка - указан foo */} 
    </>);
    

    Также never вовсю используется для условных типов (conditional-types). Вот классная статья на эту тему, если все примеры до этого были для вас слишком легкие.

    Заключение


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

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

    А вы уже используете что-либо из описанных приемов? Можете поделиться своими? Добро пожаловать в комментарии!
    Tinkoff
    it’s Tinkoff — просто о сложном

    Comments 43

      +1
      Статья огонь, спасибо. Отправил коллегам версию читать через гугл-транслэйт.
      0

      А как получить в рантайме список опций литерального перечисления, для тайпгварда, например?


      Вообще тайпгварды кто пишет честные для того же обмена с сервером?

        0

        Чтобы проверить в рантайме, что с сервера действительно пришли соответствующие контракту данные, можно использовать например io-ts.

          0

          Выглядит как-то очень странно написать динамический тайпгвард и вывести из него статический тип. Я обратную задачу имел в виду — по обычному статическому типу сгенерировать динамический тайпгвард.

            0

            Увы, штатными средствами обратная задача нерешаема. Если только распарсить исходник, определить типы и на основании них сделать какую-то ручную кодогенерацию. И все это куда более трудозатратно, чем пойти от динамического typeguard'а и статически определить его тип.

              0

              В компиляторе-то она решена, вот только в рантайм это не вынесено.

                0
                Typescript Non-goals:

                5. Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.
                  0

                  В результате получаем одну из трёх ситуаций:


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

              Есть еще https://github.com/gristlabs/ts-interface-builder, если я правильно понял задачу как генерацию валидационных функций из ts-типов

                0

                Очень близко. Спасибо

            0

            Еще typescript-is очень хорош, за ним будущее (но требуется пропатченный tsc, хотя его подключить не так уж и сложно).

            +3
            const getArrayLength = (array: any[]) => array.length

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


            const getArrayLength = <T>(array: T[]) => array.length

              +2
              const getArrayLength = (array: unknown[]): number => array.length

              полагаю — это идеальный вариант. Ни any, ни генерики тут не нужны.

                0

                Почему вы считаете тип unknown более предпочтительным, чем генерик? И зачем явно указывать возвращаемый тип для тела функции из одного выражения?

                  +1
                  Почему вы считаете тип unknown более предпочтительным, чем генерик?

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


                  И зачем явно указывать возвращаемый тип для тела функции из одного выражения?

                  В данном случае это просто вкусовщина. не более.

                    +1

                    А зачем нужен дополнительный тип-параметр, который фактически не используется нигде в функции? Мне тоже кажется, что лучше честно указать top-тип и не привлекать дополнительных сущностей.
                    По поводу второго вопроса – это может быть просто полезная привычка писать возвращаемый тип для самопроверки, или же привычка начинать написание функции с описания её типа

                      0

                      Или, как говорил мне CTO на одном проекте: "да, компилятор или IDE тип вывести могут, но я не компилятор, а читаю твой код я".

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

                    any хорош и удобен в распаковках типов. Посмотрим на встроенные утилиты, извлекающие аргументы или возвращаемое значение из функций:

                    type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
                    type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
                    


                    Еще классные примеры — в статье, которую я уже приводил выше.

                    Можно ли тут использовать unknown?
                    Да (с поправкой на …args)
                    Нужно ли тут использовать unknown?
                    ¯\_(ツ)_/¯
                  0

                  Не совет, но любопытное наблюдение: система типов TS полна по Тьюрингу. Это значит, что можно писать программы, которые будут выполняться на уровне типов во время компиляции. По ссылке есть примеры калькулятора, считающего выражения на типах, и даже небольшого языка c условиями :)


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

                    +1
                    Теоретически-то полна, а практически даже с самыми мало-мальски сложными рекурсивными типами tsc крэшится, и issue висит open больше года. Вообще, в TS все очень хрупко с выводом типов (если случаи не тривиальные), много работы еще предстоит (и много проделано). Много тупиков (например, arr[0] при arr: string[] имеет тип string и уже никогда не будет типа string|undefined; другой пример — это тип Record<string, Xyz>, который широко применяется в большом числе модулей, но при этом имеет мало смысла, ибо a[x] будет иметь тип Xyz для любой строки x, что, очевидно, просто не может быть на практике; все из-за того, что «опциональность» — признак ключа, а не значения). Примеры можно продолжать (хотя бы ограниченность типизации статической части класса чего стоит). Есть сомнения, что процесс вообще сходящийся, глядя в динамике на то, что происходит в issues.

                    Интересно еще, что tsc (вернее, его главная либа) — это, по сути, один большой js-файл (больше сотни тысяч строк), очень независимый, в котором все-все. Конечно, он автоматически бандлится из маленьких, но сам факт занятен.
                      +1
                      например, arr[0] при arr: string[] имеет тип string и уже никогда не будет типа string|undefined

                      И слава богу! Это прекрасный пример сознательного дизайн-решения, когда user expreience поставили впереди формальной логики. Если так сделать, пользоваться массивами станет невыносимо.

                    0
                    TS ощущается как типизация на честном слове.
                    Есть риск увязнуть, начиная от ревью с ЧФ, заканчивая best practices, которые казались реально неплохими.
                    Для крупных и сложных проектов 10 раз подумал бы прежде, чем внедрять именно TS.

                    Сама статья хороша.
                      +1
                      TS ощущается как типизация на честном слове.

                      Если вы про soundness — то да, типы в TS являются unsound, и тому есть вполне логичное объяснение. Язык проектируется таким образом, чтобы давать существующей экосистеме условные 80% пользы за 20% затрат. Да, это не 100, но это разумный компромисс. Если для вас этого недостаточно — есть много других компилируемых в JS языков c гарантией soundness (Purescript, Elm, ...) — этой проблемы у них нет, но есть другие :)


                      Для крупных и сложных проектов 10 раз подумал бы прежде, чем внедрять именно TS.

                      А что бы вы предложили для крупных и сложных проектов?

                        0
                        А что бы вы предложили для крупных и сложных проектов?

                        Как раз то, что вы предложили выше. Имел опыт с ReasonML и остался доволен результатом, но признаюсь, перестроиться было непросто.
                          0

                          Есть подозрение, что найти достаточное количество разработчиков на ReasonML будет непросто. И как у него дела с поддержкой всяких популярных библиотек-фреймворков? Например Vue, Bootstrap, прости-хоспади-jQuery?

                            0
                            Соглашусь, что ReasonML не на слуху и в русскоязычном пространстве, например, весьма мало упоминаний. Но в последнее время заметил, что комьюнити растет, выходят туториалы и гайды. Есть канал в Discord, там даже проскакивают вакансии.

                            Поддержка любых сторонних библиотек решается через binding и там на первый план выходит BuckleScript, тут нужна практика.

                            К счастью, биндить слишком часто мне не приходится, хотя это довольно занимательный процесс. Есть и готовые биндинги, но их не так много пока.
                            За Vue не скажу, но React вполне комфортно использовать reasonml.github.io/reason-react
                              0

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

                                0
                                Так почти с любой новой технологией. «Новой» не только в принципе, но и для себя в частности. Мне в этом плане повезло войти в проект, где это уже использовалось, что сильно помогло разобраться во многих вещах.
                      0

                      Спасибо за статью. Расскажите подробнее про typescript, dto и swagger. Откуда берутся типы, как формируется документация, что, где и чем валидируется и вот все это?

                        0
                        Swagger построен на спецификации OpenApi, которая в свою очередь предоставляет множество вариантов по генерации данных. Вот, например, один из них. Можно поискать и другие, тут у меня нет рекомендации.

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

                        Если контракты на бекенде меняются нечасто, то же самое можно делать и вручную. Главная идея в том, что отдельный слой типов и интерфейсов отвечает за контракты с бэкендом и меняется только при изменении этих контрактов.
                        0
                        Спасибо большое за статью!
                          +2
                          Совет 8: литеральный тип vs enum

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

                          Так же, типы часто приходится экспортировать/импортировать, и если таких типов много или если где-то используется много компонентов, и каждый из них содержит в себе типизированный параметр, то количество импортов может сильно вырасти. Для себя я вывел идею того, что типы, которые будут использоваться вне компонента, но вместе с ним, можно записать в статичные поля и получится что-то подобное:
                          enum FlexDirection {...};
                          enum AlignItems {...};
                          
                          interface StaticFields {
                            FlexDirection: FlexDirection;
                            AlignItems: AlignItems;
                          }
                          
                          const FlexContainer: React.FC<Props> & StaticFields = () => {...};
                          
                          FlexContainer.FlexDirection = FlexDirection;
                          FlexContainer.AlignItems = AlignItems;
                          
                          <FlexContainer flexDirection={FlexContainer.FlexDirection...}>...</FlexContainer>
                          

                            0

                            Спасибо за статью! А как сгенерировали картинку со статистикой?

                              0
                              Учет статистики вели грубо, просто по количеству файлов (несправедливый подход, но для визуализации достаточно). Сама диаграмма по данным легко строится в программах типа excel (мы же использовали встроенный макрос из Atlassian Wiki).
                              +2
                              Использование TypeScript в проекте изменяет подход к написанию кода. Становится легче изучать код, ...

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


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


                              Говорят что с ростом кодовой базы тайпскрипт начинает помогать разработчику… Но я за год разработки похоже так и не достиг этого количества кодовой базы, и мои коллеги того же мнения).

                                +1

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


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

                                  +2
                                  С реактом проптайпсы позволяют получить необходимую информацию, без избытка в коде

                                  Очень слабая альтернатива. Особенно для вложенных типов (если вы конечно не делаете глубоких shape-ов). К тому же весьма многословная и очень слабая в инструментарии. Такое удовольствие доставляет потом вычищать все эти prop-types заменяя их на полноценные типы.


                                  TypeScript определённо замедляет процесс разработки нового кода. Особенно если у вас много generic-кода. Там бывает такие ребусы встают, что можно рабочий день тупо на 1 тип потратить, с непривычки.


                                  TypeScript экономит тонну времени при рефакторингге. С ужасом вспоминаю как я жил без него. Для приложений больше 30 тыщ строк просто незаменимый механизм (разве что Flow).


                                  TypeScript имеет очень высокий уровень входа по сравнению с JavaScript. Потому что ему надо всё доказывать. И не срезать углы. А несрезание углов очень нетривиальная задача. В итоге, мне кажется, все кто в итоге оказался им сильно недоволен, по сути просто так и не овладели этими техниками — затипить то что нетипизируется. Там бывает всё очень непросто. И я очень даже понимаю тех, кто не осилил — иногда это как пробивать головой стену. Ошибки чаще всего нечитаемые. Инструментария как их разрулить вроде нет. Уровней вложенности матрёшек может быть 10+. Всякие "and 21 overloads". Но если осилить этот путь, то потом уже к JavaScript назад дороги нет )

                                    0

                                    Ещё добавлю — если рефакторинг кода + починка\поиск багов не являются очень большим %-ом от всего времени разработки вашего приложения (т.е. вы пишите write-only код), то, пожалуй, TypeScript вам не нужен.

                                      0
                                      Вы подчеркнули важную проблему — иногда очень много времени тратится на типизацию.

                                      Вот пара идей, как с этим можно справляться:

                                      В команде/компании есть адвокат ts — хорошо разбирается в теме и приходит на помощь. Также есть отдельный канал по ts, в который можно написать с вопросом.

                                      Оцениваем, насколько стоит вкладываться в типизацию конкретного блока.
                                      Например, есть блок легаси кода на js, который написали год назад и с тех пор не меняли. Фича готова, протестирована, заехала в продакшен. Если она и будет меняться, то вероятно с каким-то глобальным рефакторингом. Врядли стоит тратить много сил на разборки с типами, покрыть основные входы-выходы может быть вполне достаточно.
                                        +1
                                        В команде/компании есть адвокат ts — хорошо разбирается в теме и приходит на помощь

                                        Для большинства компаний это очень утопичное допущение. Найти просто хорошего senior фронтенд разработчика — очень сложная задача. А найти ещё и эксперта в области TypeScript — прямо большая удача.


                                        Например, есть блок легаси кода на js, который написали год назад и с тех пор не меняли

                                        Хорошо работает подход — типизировать по верхам при помощи ts-комментариев.

                                          0
                                          Для большинства компаний это очень утопичное допущение. Найти просто хорошего senior фронтенд разработчика — очень сложная задача. А найти ещё и эксперта в области TypeScript — прямо большая удача.


                                          Я не предлагал искать отдельного эксперта по TS. Под «адвокатом ts» имею ввиду неформальную роль — человека, которому тема интересна и который готов повышать свой уровень, делиться знаниями с окружающими.

                                          Хорошо работает подход — типизировать по верхам при помощи ts-комментариев.

                                          Точно!
                                            0

                                            Таких адвокатов надо контролировать: они могут чуть ли не неделями создавать правильные семнадцатиэтажные типы вместого того, что бы сделать as или any. А когда создадут тратить часы на разбор того, почему рабочий js код не компилируется.

                                  Only users with full accounts can post comments. Log in, please.