我正在尝试创建一个HOC(我也希望能够用作装饰器)来执行以下操作:
假设我有一个名为“counter”的组件
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)吗?ICounter
或IOtherCounter
以包含所有别名吗?class Counter
类型(仅使用一个扩展或实现)?亲切的问候。
答案 0 :(得分:2)
我不知道这个'预先支撑的子组件'是否是实现这一点的反应方式,但打字稿可以帮助点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>
部分游乐场link