Я пытаюсь создать 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 для компонентов, созданных псевдонимом. А также псевдоним на самом деле не набирается должным образом.
Итак, мои вопросы:
Component и props?setProps (чтобы использовать ICounterProps в качестве декоратора на Counter)?ICounter или IOtherCounter, чтобы включить все псевдонимы?class Counter (чтобы использовать только одно расширение или реализацию)?С уважением.





Я не знаю, являются ли эти «предварительно пропущенные подкомпоненты» способом реагирования на это, но машинописный текст может помочь с пунктами 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 не на последней версии. Я постараюсь предоставить упрощенную ссылку на игровую площадку
Еще один вопрос: почему машинописный текст позволяет использовать объект с дополнительными свойствами, если вы используете Partial <IProps>? Например, интерфейс IProps {count: number} будет соответствовать объекту {count: 1, something: 'hi'} - до тех пор, пока дополнительное свойство не является первым в объекте. Есть ли способ предотвратить это?
@Schadenn Ты уверен? При прямом назначении объектных литералов проверяются лишние свойства. Если вы назначаете переменную, которая имеет больше разрешенных свойств, typescriptlang.org/play/…
Если я использую псевдоним ('StartAt5', {count: 5, hey: 'how are you'}), в параметре setProps не будет ошибки TS. Однако, если я использую псевдоним ('StartAt5', {эй: 'как дела'}, я получаю "эй, не существует в типе Partial <ComponentProps <ICounter>>". Пока есть одно действительное свойство, весь объект действителен. Хотелось бы отправить вам пример, но я не могу найти способ обновить версию машинописного текста в codeandbox.
@Schadenn Да, вы правы насчет этого, это небольшая складка в том, как extends взаимодействует с дополнительной проверкой свойств, мы можем обойти это, убедившись, что если присутствуют какие-либо лишние свойства, они имеют тип never, не совсем идеально, но достаточно хорош для 99,9% всех возможных ситуаций. Отредактированы определения методов alias, чтобы TProps extends Partial<ComponentProps<TComponent>> & Record<Exclude<keyof TProps, keyof ComponentProps<TComponent>>, never> был немного загроможден, но должен выполнять свою работу :)
Интересный вопрос, не знаю, почему вы получили отрицательный голос.