Использование [key: string]: any, чтобы избежать необходимости в дополнительной функции для обобщений? - PullRequest
0 голосов
/ 17 мая 2018

Я использую перекомпоновку в React, в частности ее функцию compose, которая имеет сигнатуру

function compose<TInner, TOutter>(
    ...functions: Function[]
): ComponentEnhancer<TInner, TOutter>;

interface ComponentEnhancer<TInner, TOutter> {
    (component: Component<TInner>): ComponentClass<TOutter>;
}

, где я передаю некоторые компоненты React более высокого порядка.Я определил свои типы TInner и TOuter как интерфейсы, которые описывают то, что мне нужно для работы моих HoC:

interface IOuterProps {
    somethingYouGiveMe: string;
}

interface IInnerProps {
    somethingIGiveBack: number;
}

const myEnhancer = compose<IInnerProps, IOuterProps>(myHoC, myOtherHoc);

(как глупый пример для демонстрации).Но проблема в том, что мои компоненты имеют больше реквизита, чем тот, который генерирует HoC, должен взять эти дополнительные реквизиты и передать их, например:

interface IMyComponentProps {
    somethingYouGiveMe: string;
    somethingElse: string;
}

Из-за этого я не могу сделатьconst MyEnhancedComponent = myEnhancer(MyComponent), так как компилятор будет жаловаться, что MyEnhancedComponent не имеет somethingElse в качестве реквизита.

Я нашел два обходных пути, и я не доволен.Любопытно, что сделали бы более опытные разработчики TypeScript.

Обходной путь # 1: Введение функции

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

function myEnhancer<TInnerProps extends IInnerProps, TOuterProps extends IOuterProps>(Component: React.ComponentType<TInnerProps>) {
    return compose<TInnerProps, TOuterProps>(mYHoC, myOtherHoc)(Component);
}

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

Работа вокруг # 2: используйте [ключ: строка]: любой

Вместо этого я могу изменить свои интерфейсы на

interface IOutterProps {
    somethingYouGiveMe: string;
    [key: string]: any;
}

interface IInnerProps extends IOuterProps {
    somethingIGiveBack: number;
}

Я в принципеиспользуя [key:string]: any как бедного человека extends IOutterProps.Как, например, мне нужно, чтобы вы дали мне эти реквизиты, и если вы дадите мне дополнительные услуги, то я на самом деле не забочусь о них.

Это позволяет мне использовать myEnhancer везде, где у меня есть компоненты, которые соответствуюттребований, избегает добавленной функции и чувствует себя лучше, чем обходной путь № 1.Но также неправильно добавлять [key: string]: any.

1 Ответ

0 голосов
/ 17 мая 2018

Вы можете сделать подпись функции внутри ComponentEnhancer generic:

declare function compose<TInner, TOutter>(
    ...functions: Function[]
): ComponentEnhancer<TInner, TOutter>;

interface ComponentEnhancer<TInner, TOutter> {
    // This is now a generic function 
    <TActualInner extends TInner, TActualOuterProps extends TOutter>(component: React.ComponentClass<TActualInner>): React.ComponentClass<TActualOuterProps>;
}

interface IOuterProps {
    somethingYouGiveMe: string;
}

interface IInnerProps {
    somethingIGiveBack: number;
}

const myEnhancer = compose<IInnerProps, IOuterProps>();
interface IMyComponentProps {
    somethingYouGiveMe: string;
    somethingElse: string;
}
interface IMyInnerComponentProps {
    somethingElse: string; 
    somethingIGiveBack: number;
}

class MyComponent extends React.Component<IMyInnerComponentProps>{ }
// We specify the actual inner and outer props
const MyEnhancedComponent = myEnhancer<IMyInnerComponentProps, IMyComponentProps>(MyComponent)

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

type Omit<T, TOmit> = { [P in Exclude<keyof T, keyof TOmit>] : T[P] }
type ComponentEnhancerProps<TActualInner, TInner, TOuter> = Omit<TActualInner, TInner> & TOuter;
interface ComponentEnhancer<TInner, TOutter> {
    <TActualInner extends TInner>(component: React.ComponentClass<TActualInner>): React.ComponentClass<ComponentEnhancerProps<TActualInner, TInner, TOutter>>;
}


class MyComponent extends React.Component<IMyInnerComponentProps>{ }
const MyEnhancedComponent = myEnhancer(MyComponent)

let d = <MyEnhancedComponent somethingYouGiveMe="0" somethingElse="" />

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

type ExcludeUndefined<T, TKeys extends keyof T> = { [P in TKeys]: undefined extends T[P]  ? never : P}[TKeys];
type Omit<T, TOmit> = 
    { [P in ExcludeUndefined<T, Exclude<keyof T, keyof TOmit>>] : T[P] } &
    { [P in Exclude<Exclude<keyof T, keyof TOmit>, ExcludeUndefined<T, Exclude<keyof T, keyof TOmit>>>] ? : T[P] }
type ComponentEnhancerProps<TActualInner, TInner, TOuter> = Omit<TActualInner, TInner> & TOuter;
interface ComponentEnhancer<TInner, TOutter> {
    <TActualInner extends TInner>(component: React.ComponentClass<TActualInner>): React.ComponentClass<ComponentEnhancerProps<TActualInner, TInner, TOutter>>;
}

interface IMyInnerComponentProps {
    somethingElse: string; 
    somethingIGiveBack: number;
    optionalProp?: number;
}


class MyComponent extends React.Component<IMyInnerComponentProps>{ }
const MyEnhancedComponent = myEnhancer(MyComponent)

let d = <MyEnhancedComponent somethingYouGiveMe="0" somethingElse="" />
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...