#reactjs #typescript #generics #react-component
#reactjs #typescript #дженерики #реагирующий компонент
Вопрос:
Представьте гибкий компонент, который принимает a React.ComponentType
и его props
и отображает его:
type Props<C> = {
component: React.ComponentType<C>;
componentProps: C;
otherProp: string;
};
const MyComponent = <C extends {}>(props: Props<C>) => {
return React.createElement(props.component, props.componentProps);
};
Могу ли я каким-то образом разрешить MyComponent
получать динамику props
напрямую, например, так (не работает):
type Props<C> = {
component: React.ComponentType<C>;
otherProp: string;
};
const MyComponent = <C extends {}>(props: Props<C> amp; C) => {
const { otherProp, component, ...componentProps } = props;
return React.createElement(component, componentProps);
};
Ошибка:
Error:(11, 41) TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type 'Pick<Props<C> amp; C, Exclude<keyof C, "component" | "otherProp">>' is not assignable to parameter of type 'Attributes amp; C'.
Type 'Pick<Props<C> amp; C, Exclude<keyof C, "component" | "otherProp">>' is not assignable to type 'C'.
'Pick<Props<C> amp; C, Exclude<keyof C, "component" | "otherProp">>' is assignable to the constraint of type 'C', but 'C' could be instantiated with a different subtype of constraint '{}'.
Ответ №1:
Здесь нам нужно понять некоторые типы утилит и то, как происходит деструктурирование в TS.
type Obj = {
[key: string]: any
}
interface I1 {
a: number
b: number
c: number
}
const i1: I1 = {
a: 1,
b: 1,
c: 1,
}
let {a, ...rest} = i1
interface Obj {
[key: string]: any
}
const i2: Obj amp; I1 = {
a: 1,
b: 1,
c: 1,
d: 1,
e: 1,
}
let {a: a1, b, c, ...rest2} = i2
function func<T extends Obj>(param: I1 amp; T) {
const {a, b, c, ...rest} = param
}
В приведенном выше коде предполагаемый тип for rest
будет {b: number, c: number}
, потому что object i1
содержит только три ключа, и один из них, иначе a
говоря, исчерпан. В случае rest2
, TS все еще может определять тип Obj
, поскольку ключи из интерфейса I1
исчерпаны. Под исчерпанием я подразумеваю, что они не фиксируются с помощью оператора rest.
Но в случае функции TS не может выполнять этот тип вывода. Я не знаю причины, по которой TS не может этого сделать. Это может быть связано с общими ограничениями.
Что происходит в случае функции, так это то, что тип для rest
внутри функции равен Pick<I1 amp; T, Exclude<keyof T, "a" | "b" | "c">>
. Exclude
исключает ключи a
b
и c
из универсального типа T
. Проверьте Исключение здесь. Затем Pick
создает новый тип из I1 amp; T
с ключами, возвращаемыми Exclude
. Поскольку T
может быть любого типа, TS не может определить ключи после исключения и, следовательно, выбранные ключи и, следовательно, вновь созданный тип, даже если T ограничен Obj
. Вот почему переменная типа rest
в функции остается Pick<I1 amp; T, Exclude<keyof T, "a" | "b" | "c">>
.
Пожалуйста, обратите внимание, что тип, возвращаемый, Pick
является подтипом Obj
Теперь, переходя к вопросу, та же ситуация происходит с componentProps
. Выводимый тип будет Pick<Props<C> amp; C, Exclude<keyof C, "otherProp" | "component">>
. TS не сможет сузить его. Глядя на подпись React.createElement
function createElement<P extends {}>(
type: ComponentType<P> | string,
props?: Attributes amp; P | null,
...children: ReactNode[]): ReactElement<P>
И вызывая его
React.createElement(component, componentProps)
Предполагаемый тип для P
подписи будет C
в вашем коде из первого аргумента, т.е. component
Потому, что он имеет тип React.ComponentType<C>
. Второй аргумент должен быть или undefined
или null
или C
(на данный момент игнорируется Attributes
). Но тип componentProps
is Pick<Props<C> amp; C, Exclude<keyof C, "otherProp" | "component">>
, который определенно можно присвоить {}
, но не C
потому, что он является подтипом {}
not of C
. C
также является подтипом {}
, но типом выбора и C
может быть или не быть совместимым (это то же самое, что — существует класс A; B и C получают A, объекты B и C могут быть присвоены A, но объект B не может быть приписан C). Вот почему ошибка
'Pick<Props<C> amp; C, Exclude<keyof C, "component" | "otherProp">>' is assignable to the constraint of type 'C', but 'C' could be instantiated with a different subtype of constraint '{}'.
Поскольку мы более интеллектуальны, чем компилятор TS, мы знаем, что они совместимы, а TS — нет. Так что заставьте TS поверить, что мы все делаем правильно, мы можем выполнить утверждение типа следующим образом
type Props<C> = {
component: React.ComponentType<C>;
otherProp: string;
};
const MyComponent = <C extends {}>(props: Props<C> amp; C) => {
const { otherProp, component, ...componentProps } = props;
return React.createElement(component, componentProps as unknown as C);
// ------------------------------------------------^^^^^^^^
};
Это определенно правильное утверждение типа, потому что мы знаем, что тип componentProps
будет C
Надеюсь, это ответит на ваш вопрос и решит вашу проблему.