Typescript React псевдоним HOC (для использования <Component.alias />) проблемы с типизацией

Я пытаюсь создать HOC (который я также хочу использовать в качестве декоратора), чтобы сделать следующее:

Предположим, у меня есть компонент под названием «счетчик».

interface ICounterProps {
  count: number;
}

interface ICounter<T> extends React.Component<T> {
  // I'd have to add all aliases here manually I guess
  startAt5?: React.SFC<ICounterProps>
}

class Counter extends React.Component<ICounterProps> implements ICounter<ICounterProps> {
  render() {
    return <div>{this.props.count}</div>
  }
}

Теперь мой HOC должен делать что-то вроде:

const alias = (name: string, setProps: {}) => (Component: any) => {
  Component[name] = (props: {}) => <Component {...setProps} {...props} />
}

Поэтому я могу использовать его как декоратор для компонентов класса или как функцию для функциональных компонентов:

@alias('startAt5', { count: 5 })
class counter {...}

interface IOtherCounterProps {
  otherCount: number;
}

interface IOtherCounter extends React.SFC<IOtherCounterProps> {
  startAt10?: React.SFC<IOtherCounterProps>
}

const someOtherCounter: IOtherCounter = ({}) => {...}
alias('startAt10', { otherCount: 10 })(SomeOtherCounter);

А позже используйте мой компонент с разными предустановками, например:

<Counter.startAt5 />
<OtherCounter.startAt10 />

Код должен работать так (или работает так), сделал эту песочницу, чтобы вы могли ее опробовать: https://codesandbox.io/s/ww1vrqlxw7

Хотя внутри index.tsx вы видите предупреждения TS для компонентов, созданных псевдонимом. А также псевдоним на самом деле не набирается должным образом.

Итак, мои вопросы:

  1. Какие типы можно использовать для правильного ввода Component и props?
  2. Можете ли вы даже ввести setProps (чтобы использовать ICounterProps в качестве декоратора на Counter)?
  3. Можете ли вы динамически сгенерировать ICounter или IOtherCounter, чтобы включить все псевдонимы?
  4. Можете ли вы как-то сократить тип class Counter (чтобы использовать только одно расширение или реализацию)?
  5. Может быть, есть еще лучшее решение для создания предварительно пропиранных подкомпонентов?

С уважением.

Интересный вопрос, не знаю, почему вы получили отрицательный голос.

Titian Cernicova-Dragomir 01.06.2018 17:11
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
1
529
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я не знаю, являются ли эти «предварительно пропущенные подкомпоненты» способом реагирования на это, но машинописный текст может помочь с пунктами 1–4.

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

// Helper type to extarct the props form a component type
type ComponentProps<T extends React.ComponentClass<any> | React.SFC<any>> = 
    T extends React.ComponentClass<infer P> ? P :
    T extends React.SFC<infer P> ? P : never;


type RestOfProperties<TAllProps, TPartial extends Partial<TAllProps>> =
    Pick<TAllProps, Exclude<keyof TAllProps, keyof TPartial>> & Partial<TPartial>;

// We need to declare 2 versions of the function that does the mutating of the type one for SCF the other for ComponentClass
// There may be a solution with a single class but I can't see it right now
    declare class Helper<TComponent extends React.ComponentClass<any>, TAllProps extends ComponentProps<TComponent>> {
    Component: TComponent;
    alias<TName extends string, TProps extends Partial<ComponentProps<TComponent>> & Record<Exclude<keyof TProps, keyof ComponentProps<TComponent>>, never> >(name: TName, setProps: TProps) : 
        Helper<TComponent & { [P in TName]: (props: RestOfProperties<TAllProps, TProps>) => JSX.Element }, TAllProps>
}
declare class HelperSCF<TComponent extends React.SFC<any>, TAllProps extends ComponentProps<TComponent>> {
    Component: TComponent;
    alias<TName extends string, TProps extends Partial<ComponentProps<TComponent>> & Record<Exclude<keyof TProps, keyof ComponentProps<TComponent>>, never>>(name: TName, setProps: TProps) : 
        HelperSCF<TComponent & { [P in TName]: (props: RestOfProperties<TAllProps, TProps>) => JSX.Element }, TAllProps>
}
// Define the aliasFactory function, with overlaods for SCF and ComponentClass
function aliasFactory<TComponent extends React.SFC<any>>(Component: TComponent) : HelperSCF<TComponent, ComponentProps<TComponent>>
function aliasFactory<TComponent extends React.ComponentClass<any>>(Component: TComponent) : Helper<TComponent, ComponentProps<TComponent>> 
function aliasFactory<TComponent extends React.ComponentClass<any> | React.SFC<any>>(Component: TComponent) {
    class Helper<T extends React.SFC<any>> {
        public constructor(public Component: T) { }
        alias<TName extends string, TProps extends Partial<ComponentProps<TComponent>>>(name: TName, setProps: TProps) : any {
            (this.Component as any)[name] = (props: {}) => <this.Component {...setProps} {...props} />
            return this;
        }
    }

    return new Helper(Component as any);
};


// Usage sample 
// Have a mix of required and optional properties 
interface IOtherCounterProps {
    otherCount: number;
    speed?: number;
    required: number;
}

// Define the implementation 
const OtherCounterImpl : React.SFC<IOtherCounterProps> = ({ otherCount }) => <div>{otherCount}</div>;

// This is the symbol we can export that will contain all our aliasses
const OtherCounter = aliasFactory(OtherCounterImpl)
    .alias("startAt10", { otherCount: 10 })
    .Component;


interface ICounterProps {
    count: number;
}

class CounterProto extends React.Component<ICounterProps> {
    render() {
        return <div>{this.props.count}</div>;
    }
}

// Similar for classes, we can add as may aliasses as we wish 
// This is the symbol we can export that will contain all our aliasses
const Counter = aliasFactory(CounterProto)
    .alias("startAt5", { count: 5})
    .alias("startAt10", { count: 10})
    // .alias("startAt10", { count: 10, notThere: ""}) // this would be an error (added after feedback)
    .Component;




let d = <div >
    <Counter count = {3} />
    {/* Required properties that have been specified become optional  */}
    <Counter.startAt5  />
    <Counter.startAt10  />
    {/* Optional properties remnain optional, the required ones remain required */}
    <OtherCounter.startAt10 required = {10}  />
</div>

Частичная детская площадка ссылка на сайт

@Schadenn странно, что эти ошибки предполагают, что, по крайней мере, ваша IDE не на последней версии. Я постараюсь предоставить упрощенную ссылку на игровую площадку

Titian Cernicova-Dragomir 04.06.2018 15:06

Еще один вопрос: почему машинописный текст позволяет использовать объект с дополнительными свойствами, если вы используете Partial <IProps>? Например, интерфейс IProps {count: number} будет соответствовать объекту {count: 1, something: 'hi'} - до тех пор, пока дополнительное свойство не является первым в объекте. Есть ли способ предотвратить это?

Schadenn 05.06.2018 10:02

@Schadenn Ты уверен? При прямом назначении объектных литералов проверяются лишние свойства. Если вы назначаете переменную, которая имеет больше разрешенных свойств, typescriptlang.org/play/…

Titian Cernicova-Dragomir 05.06.2018 10:11

Если я использую псевдоним ('StartAt5', {count: 5, hey: 'how are you'}), в параметре setProps не будет ошибки TS. Однако, если я использую псевдоним ('StartAt5', {эй: 'как дела'}, я получаю "эй, не существует в типе Partial <ComponentProps <ICounter>>". Пока есть одно действительное свойство, весь объект действителен. Хотелось бы отправить вам пример, но я не могу найти способ обновить версию машинописного текста в codeandbox.

Schadenn 05.06.2018 11:58

@Schadenn Да, вы правы насчет этого, это небольшая складка в том, как extends взаимодействует с дополнительной проверкой свойств, мы можем обойти это, убедившись, что если присутствуют какие-либо лишние свойства, они имеют тип never, не совсем идеально, но достаточно хорош для 99,9% всех возможных ситуаций. Отредактированы определения методов alias, чтобы TProps extends Partial<ComponentProps<TComponent>> & Record<Exclude<keyof TProps, keyof ComponentProps<TComponent>>, never> был немного загроможден, но должен выполнять свою работу :)

Titian Cernicova-Dragomir 05.06.2018 13:29

Другие вопросы по теме