Как мы обсуждали в комментариях, сценарий фактически связан с реагирующими компонентами и тем, как новая функция универсального компонента взаимодействует с общими параметрами по умолчанию.
Проблема
Если у нас есть компонент реагирования, у которого есть универсальный параметр, а значение по умолчанию для универсального параметра имеет поле функции, то параметры функции, которую мы передаем, не будут выведены. Простой пример будет:
class Foo<P = { fn : (e: string)=> void }> extends React.Component<P>{}
let foo = () => (
<div>
<Foo fn={e=> e}/> {/* e is any */}
</div>
)
Возможное решение
Позвольте мне предвосхитить это с оговоркой, я не знаю, это работает во всех ситуациях или зависит ли это от поведения компилятора, которое изменится. Что я обнаружил, так это то, что это очень хрупкое решение, изменив любую деталь, даже с чем-то, что можно было бы разумно считать эквивалентным, и оно не будет работать. Что я могу сказать, так это то, что, как я представил, это сработало для того, на чем я его тестировал.
Решение состоит в том, чтобы воспользоваться тем фактом, что типы свойств берутся из возвращаемого значения конструктора, и если мы немного упростим вещи для универсального параметра по умолчанию, мы получим нужный нам вывод. Единственной проблемой является обнаружение того, что мы имеем дело с универсальным параметром по умолчанию. Мы можем сделать это, добавив дополнительное поле к параметру по умолчанию, а затем протестировать его, используя условные типы. Еще одно предостережение: это не работает для простых случаев, когда P
может быть выведен на основании используемых свойств, поэтому у нас будет пример, когда компонент извлекает свойства из другого компонента (который фактически ближе к вашему варианту использования):
// Do not change { __default?:true } to DefaultGenericParameter it only works with the former for some reason
type IfDefaultParameter<C, Default, NonDefault> = C extends { __default?:true } ? Default : NonDefault
type DefaultGenericParameter = { __default? : true; }
export type InferredProps<C extends React.ReactType> =
C extends React.ComponentType<infer P> ? P :
C extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[C] :
{};
// The props for Foo, will extract props from the generic parameter passed in
type FooProps<T extends React.ReactType = React.ComponentClass<{ fn: (s: string )=> void }>> = InferredProps<T> & {
other?: string
}
// Actual component class
class _Foo<P> extends React.Component<P>{}
// The public facing constructor
const Foo : {
new <P extends React.ReactType & {__default?: true} = React.ComponentClass<{ fn: (s: string )=> void }> & DefaultGenericParameter>(p: P) :
IfDefaultParameter<P, _Foo<FooProps>, _Foo<FooProps<P>>>
} = _Foo as any;
let foo = () => (
<div>
<Foo fn={e=> e}/> {/* e is string */}
<Foo<React.ComponentClass<{ fn: (s: number )=> void }>> fn={e=> e}/> {/* e is number */}
</div>
)
Применяется в материале UI
Один пример, который я попробовал в репозитории Material-Ui, - это Avatar
компонент, и он, кажется, работает:
export type AvatarProps<C extends AnyComponent = AnyComponent> = StandardProps<
PassthruProps<C, 'div'>,
AvatarClassKey
> & {
alt?: string;
childrenClassName?: string;
component?: C;
imgProps?: React.HtmlHTMLAttributes<HTMLImageElement>;
sizes?: string;
src?: string;
srcSet?: string;
};
type IfDefaultParameter<C, Default, NonDefault> = C extends { __default?:true } ? Default : NonDefault
type DefaultGenericParameter = { __default? : true; }
declare const Avatar : {
new <C extends AnyComponent & DefaultGenericParameter = 'div' & DefaultGenericParameter>(props: C)
: IfDefaultParameter<C, React.Component<AvatarProps<'div'>>, React.Component<AvatarProps<C>>>
}
Использование
const AvatarTest = () => (
<div>
{/* e is React.MouseEvent<HTMLDivElement>*/}
<Avatar onClick={e => log(e)} alt="Image Alt" src="example.jpg" />
{/* e is {/* e is React.MouseEvent<HTMLButtonElement>*/}*/}
<Avatar<'button'> onClick={e => log(e)} component="button" alt="Image Alt" src="example.jpg" />
<Avatar<React.SFC<{ x: number }>>
component={props => <div>{props.x}</div>} // props is props: {x: number;} & {children?: React.ReactNode;}
x={3} // ok
// IS NOT allowed:
// onClick={e => log(e)}
alt="Image Alt"
src="example.jpg"
/>
</div>
Удачи в этом, дайте мне знать, помогло ли это. Это была забавная маленькая проблема, которая не давала мне уснуть в течение 10 дней:)