Ответы пользователя по тегу TypeScript
  • Node+ts module-alias не работает с import, но работает с require?

    bingo347
    @bingo347 Куратор тега Node.js
    Crazy on performance...
    Typescript только резолвит пути из алиасов, но не переводит их в настоящие, оставляя это за сборщиками.

    Вариант 1: собирать проект с помощью webpack или rollup (или что Вам еще нравится)
    https://www.npmjs.com/package/@rollup/plugin-alias
    https://webpack.js.org/configuration/resolve/#reso...

    Вариант 2: захачить require/import в node (в зависимости от того, что на выходе у TS), чтобы понимало Ваши алиасы
    https://nodejs.org/dist/latest-v14.x/docs/api/esm....
    Для commonjs хука нет, нужно патчить вот это: https://nodejs.org/dist/latest-v14.x/docs/api/modu...
    Ответ написан
    Комментировать
  • Как правильно обрабатывать многомерные массивы в typeScript?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    Проблема в ...form.formParams.errorList[formIndex]
    Давайте по порядку:
    // допустим у нас есть следующие переменные с типами:
    let obj: Obj;
    let value: T;
    
    // мы их собираем в такой объект:
    const newObj = { ...obj, k: value };
    тип для newObj компилятор будет выводить из контекста и получит Obj & { k: T }, ровно из того, что мы объединяем эти сущности.

    Усложним пример:
    let objs: Obj[];
    let value: T;
    let key: string;
    
    const newObj = { ...objs[1], [key]: value };
    Тип на выходе будет ужеObj & { [key: string]: string }, хотя objs на это раз у нас массив, компилятор видит, что мы берем индекс.

    Что будет, если objs вместо простого Obj[] у нас будет Obj[] | Obj[][]?
    Сначала мы берем значение по индексу из objs - эта операция вернет тип Obj | Obj[].
    После мы разворачиваем его через spread в объект и дописываем значение по индексу, получаем тип:(Obj | Obj[]) & { [key: string]: string }, тут еще Важно понимать, что типы в TS - это чистая математика, и данный тип полностью равносилен такому:
    (Obj & { [key: string]: string }) | (Obj[] & { [key: string]: string })

    А что, если Obj - это тоже { [key: string]: string }? Мы сможем свернуть тип, упростив уравнение на типах:
    (Obj & Obj) | (Obj[] & Obj)
    (Obj) | (Obj[] & Obj)
    (Obj & Obj[]) | (Obj & Obj)
    (Obj & Obj[]) | (Obj)
    Obj & Obj[]
    И это именно тот тип, который получил у Вас компилятор
    Ответ написан
    Комментировать
  • Как описать обобщённый ответ с restful api сервера?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    Используйте дженерик параметры
    export type ApiResponse<T> =
      | { errors: Array<{ [key: string]: string }> }
      | { data: Array<T> }
      | { text: string };

    abstract class Api<T> {
      protected readonly baseUrl: string;
      protected readonly endPoint: string;
    
      protected get url(): string {
        return new URL(this.endPoint, this.baseUrl).toString();
      }
    
      protected constructor(baseUrl = '/', endPoint: string) {
        this.baseUrl = baseUrl;
        this.endPoint = endPoint;
      }
    
      protected async http({
        url = '',
        method = 'GET',
        data = undefined,
      }: RequestData): Promise<ApiResponse<T>> {
        try {
          const response: Response = await fetch(
            (new URL(url, this.url).toString()),
            { method, body: data }
          );
          return await response.json();
        } catch (e) {
          console.error(e.message);
          return { text: e.message };
        } finally {
          // finnally
        }
      }
    }

    class ApiPost extends Api<Post> {
      constructor(baseUrl: string, endPoint: string) {
        super(baseUrl, endPoint);
      }
    
      async list(count?: number): Promise<false | Array<Post>> {
        const response: ApiResponse = await this.http({
          url: `${count ? `?_limit=${count}` : ''}`,
        });
    
        if (Array.isArray(response))
          return response;
    
        return false;
      }
    }
    Ответ написан
    4 комментария
  • Как передать дженерик функции аргумент key типа keyof T и чтобы при этом же было T[key]: number?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    type ChooseKeys<O, T> = {
        [K in keyof O]: O[K] extends T ? K : never;
    }[keyof O];
    
    // 'number' | 'alsoNumber'
    type Test1 = ChooseKeys<{
      number: number;
      alsoNumber: number;
      string: string;
      anotherString: string;
      bool: boolean;
    }, number>;
    
    // 'number' | 'alsoNumber' | 'bool'
    type Test2 = ChooseKeys<{
      number: number;
      alsoNumber: number;
      string: string;
      anotherString: string;
      bool: boolean;
    }, number | boolean>;
    Ответ написан
    Комментировать
  • Как преобразовать имена свойств объекта из kebab-case в camelCase?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    В дополнение к ответу 0xD34F нормальная типизация такой функции (нужен typescript 4.1 или выше):
    type AsCamelCase<S extends string> = S extends `${infer A}_${infer B}` ? `${A}${Capitalize<AsCamelCase<B>>}` : S;
    type CamelCaseKeysRecord<T> = T extends Record<PropertyKey, unknown> ? {
        [K in string & keyof T as AsCamelCase<K>]: CamelCaseKeysRecord<T[K]>;
    } & {
        [K in Exclude<keyof T, string>]: CamelCaseKeysRecord<T[K]>;
    } : T;
    
    function toCamelCaseKeys<T>(val: T): CamelCaseKeysRecord<T> {
        if (Array.isArray(val)) {
            return val.map(toCamelCaseKeys) as unknown as CamelCaseKeysRecord<T>;
        }
        if (typeof val === 'object') {
            return Object.fromEntries(Object
                .entries(val)
                .map(([k, v]) => [
                    k.replace(/_+(.)/g, (_, g) => g.toUpperCase()),
                    toCamelCaseKeys(v as Record<string, unknown>),
                ])) as CamelCaseKeysRecord<T>;
        }
        return val as unknown as CamelCaseKeysRecord<T>;
    }

    https://www.typescriptlang.org/play?target=7&ts=4....
    Ответ написан
    6 комментариев
  • Как правильно описать пропс, который может быть и объектом, и массивом с объектами одинакового типа?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    много где начинает ругаться например что у ControlsProps нет метода foreach
    все правильно ругается, по Вашим типам ControlsProps - это plain object, где ключом может быть любая строка, а все значения - ControlProps или массив ControlProps. У произвольного объекта нет метода forEach.

    Не подскажете как вообще правильные ситуации обрабатывать, может подход с ControlsProps | ControlsProps[] в корне неправильный
    https://www.typescriptlang.org/docs/handbook/2/nar...
    Ответ написан
    Комментировать
  • Как задекларировать директиву vue?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    Если нужно расширить существующий интерфейс Vue своими элементами, то положите в корень проекта файл .d.ts имя которого не совпадает с другими файлами.
    import 'vue'; // нужно чтоб слинковаться с базовыми модулем и интерфейсом
    declare module 'vue' { // расширяем модуль
      import vClickOutside from 'v-click-outside'; // подключаем типы плагина
      interface Vue { // расширяем интерфейс
        // прописываем нужные расширения
        vClickOutside(): typeof vClickOutside;
      }
    }
    Ответ написан
    Комментировать
  • Как в VS Code проверить проект на типизацию TypeScript?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    лишь ошибки типизации которые не мешают компиляции
    Если проект компилируется - то все типы сошлись. Если проект компилируется, а ошибка все таки есть - значит типы криво описаны.
    Как проверять все файлы проекта на правильность типизации?
    Скомпилировать проект.

    Но, если допустим, изменить какой-нибудь тип (интерфейс), а файл, который на него ссылается и использует будет закрыт, то ошибку никакую не выдаст.
    И это хорошо. Если TypeScript LSP будет перепроверять весь проект на каждый чих, а не только открытые файлы, то писать что-то сложнее todo листа будет невозможно...

    Знаю, существуют ESLint, TSLint.
    Они тоже для статического анализа, но для немного другого. В VSCode они так же чекают только открытые файлы, из тех же соображений производительности.
    TSLint устарел. ESLint ставьте отсюда: https://marketplace.visualstudio.com/items?itemNam...
    Ответ написан
    Комментировать
  • В чем превосходство Typescript?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Как бы это странно не звучало, но одно из главных преимуществ TypeScript - это ускорение разработки в несколько раз. Да, когда еще ничего нет - разработка незначительно замедлится, так как помимо логики нужно описать еще и типы. Но это только в начале. И это не только возможность писать большую часть кода с помощью автодополнения. Поддержка существующего кода занимает в десятки раз больше времени, чем его начальное написание. Стоит ли вложить несколько лишних часов на старте, чтоб сэкономить в последствии месяцы? Как по мне стоит.

    Еще одним огромным плюсом является возможность проектировать на типах. Познав дзен TypeScript'а я перестал пользоваться UML, так как типы дают мне ту же наглядность, но при этом еще и сокращают время, так как типы - это уже код. Ну и источник правды остается один. Бизнес требования выраженные в типах не дают отклонится от них при написании логики, исключая возможность неправильной трактовки задачи. Перенеся задачу в типы, я могу обсудить ее с коллегами и исправить неточности еще до написания кода, что опять же сильно экономит время.

    Ну и отвчечу на некоторые Ваши сомнения:
    TSLint в VSCode прям жутко тугой
    TSLint официально deprecated, вместо него стоит использовать плагин к eslint, заодно можете мой конфиг попробовать.

    Типы? Есть JSDOC
    JSDoc в плане типов не умеет и 10% того, что умеет TypeScript. Кроме того, никто в здравом уме не пишет JSDoc на приватную логику, а значит проверки типов там не будет. JSDoc не гарантирует корректность рефакторинга, а вот благодаря TypeScript я, опять таки, точно не забуду обновить JSDoc.

    многие библиотеки nodejs не имеют типов
    может лет 5 назад так и было, но сейчас встретить библиотеку без типов - скорее исключение. Если библиотека популярная, но не предоставляет типов, скорее всего их уже написал кто-то другой, достаточно просто установить одноименный модуль из npm скоупа types и все будет работать само.

    Поддержка браузерами скомпилированного кода? Да какбы почти весь JS имеет поддержку 95%+, тот же Babel уже забыл когда использовал.
    Вообще это не основная задача компилятора TypeScript, а опциональная возможность. И babel + preset-env с ней справляются гораздо лучше. И никто не мешает использовать их вместе. А еще думаю вопрос времени, когда кто-то напишет оптимизатор кода использующий информацию о типах из TS.

    Примерно в каждой второй есть инстансы, на которые смотришь - и чешешь репу - а как называется тип этой переменной в @types/?
    import {someObject} from 'some-library';
    
    type TypeFromValue = typeof someObject;
    const valueCopy: TypeFromValue = {
        ...someObject,
        type: 'overrides',
        with: 'type check',
    };
    и кстати, вот пример того что JSDoc типы не умеют.

    //@ts-check
    и почти везде останется бесполезный any.

    P.S. удачи такие типы на JSDoc описывать
    P.P.S. Еще прелести современного TypeScript невозможные у...
    Ответ написан
    8 комментариев
  • Как изменить тип на any?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    Комментировать
  • Как затипизировать React.memo, если у функционального компонента есть статические свойства?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    Насколько знаю (я не эксперт реакта, могу ошибаться), memo и не пробросит статик свойства. Он по сути создает новый HOC вокруг вашего компонента. И статические свойства нужно вешать уже на эту обертку:
    import { FC, memo } from "react";
    
    interface IAppProps {
      title: string;
    }
    
    interface IStatic<T> {
      someStaticString: string;
      someStaticObj: T;
    }
    
    const someObj = {
      SOME_KEY: "SOME_VALUE"
    };
    
    const App: FC<IAppProps> & IStatic<typeof someObj> = memo((props) => {
      const { title } = props;
    
      return <>{title}</>;
    });
    
    App.someStaticString = "someStaticString";
    App.someStaticObj = someObj;
    
    export default App;
    Ответ написан
  • Почему TypeScript выдаёт ошибку?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    enum UserActionTypes {
      FETCH_USERS = "FETCH_USERS",
      FETCH_USERS_SUCCESS = "FETCH_USERS",
      FETCH_USERS_ERROR = "FETCH_USERS",
    }

    У Вас во всех вариантах один и тот же литеральный тип, надо так:
    enum UserActionTypes {
      FETCH_USERS = 'FETCH_USERS',
      FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS',
      FETCH_USERS_ERROR = 'FETCH_USERS_ERROR',
    }
    Ответ написан
  • Почему расширеный интерфейс в TS не принимается компилятором?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    Расширение интерфейсов (это именно расширение, а не наследование) работает по принципу И, а в формировании Record Вы от него ожидаете принципа ИЛИ.
    Типы в TypeScript подчиняются достаточно строгой математике, за счет которой он способен статически типизировать всю мощь хаоса динамического JavaScript.
    Если не брать в расчет способность интерфейсов к слиянию, то расширение интерфейсов можно считать сахаром над операцией & над типами, то есть это:
    interface Field {
      value: any,
      validation?: FieldValidation
    }
    
    interface InputField extends Field {
      value: string,
      placeholder: string,
      textarea?: boolean,
      maxChars?: number,
      type?: 'text' | 'email' | 'password' | 'tel' | 'url',
      dark?: boolean,
      disabled?: boolean,
      onInput?: () => any,
      onKeyDown?: () => any,
      onFocus?: () => any,
      onBlur?: () => any
    }
    аналогично этому:
    interface Field {
      value: any,
      validation?: FieldValidation
    }
    
    type InputField = Field & {
      value: string,
      placeholder: string,
      textarea?: boolean,
      maxChars?: number,
      type?: 'text' | 'email' | 'password' | 'tel' | 'url',
      dark?: boolean,
      disabled?: boolean,
      onInput?: () => any,
      onKeyDown?: () => any,
      onFocus?: () => any,
      onBlur?: () => any
    }

    А ожидаемую Вами логику можно получить c помощью операции | следующим образом:
    interface BaseField {
      value: any,
      validation?: FieldValidation
    }
    
    interface InputField extends BaseField {
      value: string,
      placeholder: string,
      textarea?: boolean,
      maxChars?: number,
      type?: 'text' | 'email' | 'password' | 'tel' | 'url',
      dark?: boolean,
      disabled?: boolean,
      onInput?: () => any,
      onKeyDown?: () => any,
      onFocus?: () => any,
      onBlur?: () => any
    }
    
    interface SelectField extends BaseField {
      options: string[],
      value: string,
      placeholder: string,
      dark?: boolean,
      disabled?: boolean,
      onSelect?: () => any,
      onKeyDown?: () => any,
      onFocus?: () => any,
      onBlur?: () => any
    }
    
    type Field = InputField | SelectField;
    const fields = ref<Record<string, Field>>({
            name: {
              value: '1',
              placeholder: '123'
            },
    });


    P.S. Для одного доклада по TypeScript готовил такой пример:
    https://gist.github.com/bingo347/00652993abf31702c...
    Если поймете, как оно работает, то познаете большую часть дзена TypeScript
    Ответ написан
    2 комментария
  • Как получить названия функций из объекта, который является результатом выполнения функции?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Не совсем понятен вопрос, но догадываюсь, что нужна правильная типизация данного хука, IDE именно на нее смотрят для формирования списка автокомплита.
    Можно сделать примерно так: https://www.typescriptlang.org/play?#code/C4TwDgpg...
    Ответ написан
    Комментировать
  • Как сделать свой плагин для Vue2 если проект на TypeScript?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    Комментировать
  • Сокращенный импорт для *.tsx и *.jsx, нужно ли добавлять алиасы и в конфиг вебпака и в tsconfig?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    tsconfig.json
    {
        "compilerOptions": {
            "baseUrl": "./",
            "paths": {
                "@*": ["./src/modules/*"]
            }
        }
    }
    остальные параметры по своему вкусу

    с webpack боюсь придется для каждой папки генерировать свой алиас, но учитывая что конфиг webpack - это код, не большая проблема, а за основу для генерации он может брать tsconfig.json
    Ответ написан
    1 комментарий
  • Как проверить тип параметров ф-ции?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    Типов нет в рантайме, так что нужно проверять по тому что есть, например написать примерно такой тайпгвард:
    const isFormPropsType = (v: FormPropsType | {path: string}): v is FormPropsType => typeof (v as FormPropsType).formName === 'string';

    И соответственно использовать его для проверки:
    const authForms:AuthFormsType = {
      login:(props) =>{
        if(isFormPropsType(props)){
          return <Login 
          formName={props.fromName} 
          onSubmit={props.onSubmit} 
          changeFormStateHandler={props.changeFormStateHandler}
          />
        } 
        return <Login path={props.path} />
      },

    Хотя подозреваю, что можно гораздо проще сделать в данном случае:
    const authForms:AuthFormsType = {
      login:(props) => {
        return <Login {...props} />
      },
    Ответ написан
  • Как динамически передавать дженерик тип?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    Если commonName - переменная, то ее тип можно извлечь через typeof
    submitHandlersCreator<AuthFormsDataTypes[typeof commonName]>({
                      dispatch,
                      path: `${location.pathname}${location.search}`,
                      actionName:commonName
                    })
    Но у нее должен быть литеральный тип или юнион литеральных типов, притом из множества ключей AuthFormsDataTypes
    Ответ написан
    Комментировать
  • Как решить ошибку с this в функции TypeScript?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    Комментировать
  • Как заставить TS работать с группами типов?

    bingo347
    @bingo347 Куратор тега TypeScript
    Crazy on performance...
    const f = <T extends T1 | T2>(someObj: T, someEnum: T extends T1 ? typeof E1 : typeof E2) => {
      return (someObj as T1 & T2)[someEnum.a];
    };
    Ответ написан