我将React组件作为变量传递,并尝试使用正确的类型保护自己免受运行时错误的影响。问题是,当我需要从变量实例化组件时,道具类型会感觉“倒置”。以下代码片段将更好地说明问题。
还有我发现的解决方案-将组件与功能组件包装在一起,将道具传递通过
interface IBaseStore {
prop: number;
}
interface IExtendedStore extends IBaseStore {
extraProp: number;
}
type ComponentProps<StoreT> = {
store: StoreT;
};
const BaseComponent = (props: ComponentProps<IBaseStore>) => <div>{props.store.prop}</div>;
const ExtendedComponent = (props: ComponentProps<IExtendedStore>) => <div>{props.store.extraProp}</div>;
type ConfigProps<StoreT> = {
additionalLayer: ComponentType<ComponentProps<StoreT>>;
};
const tableConfigCorrect1: ConfigProps<IBaseStore> = {
additionalLayer: BaseComponent,
};
const tableConfigCorrect2: ConfigProps<IExtendedStore> = {
additionalLayer: BaseComponent,
};
const tableConfigWrongType1: ConfigProps<IBaseStore> = {
additionalLayer: ExtendedComponent, // No TS error
};
const runtimeError = <tableConfigWrongType1.additionalLayer store={{ prop: 5 }}/>;
// Solution:
const tableConfigWrongType2: ConfigProps<IBaseStore> = {
additionalLayer: props => <ExtendedComponent {...props}/>, // TS error, not compiled
};
感觉到它常见的OOP问题是“倒置”子类型,我正在寻找某种模式或TS类型以更简洁的方式解决它
答案 0 :(得分:0)
我对此进行了分解,是的,打字稿错误检查器似乎存在漏洞。这不是解决方案,但我可以将问题简化为更清晰的示例。这是您应该在其中出现错误的代码:
const tableConfigWrongType1: ConfigProps<IBaseStore> = {
additionalLayer: ExtendedComponent, // No TS error
};
让我们分解一下:
// Unwrapping ConfigProps<StoreT> and just looking at additionalLayer:
// Also, I replaced ExtendedComponent with its implementation.
let additionalLayer: React.ComponentType<ComponentProps<IBaseStore>>;
additionalLayer = (props: ComponentProps<IExtendedStore>) => <div />; // No error
进一步简化:
// Unwrapping ComponentProps
let additionalLayer: React.ComponentType<{ store: IBaseStore; }>;
additionalLayer = (props: { store: IExtendedStore; }) => <div />;
然后分解IBaseStore和IExtendedStore:
let additionalLayer: React.ComponentType<{ store: {prop: number}; }>;
additionalLayer = (props: { store: {prop: number; extraProp: number}; }) => <div />;
然后用其功能实现(简化)替换ComponentType:
let additionalLayer: (props: { store: {prop: number} }) => React.ReactElement | null;
additionalLayer = (props: { store: {prop: number; extraProp: number} }) => <div />;
仍然没有错误,我感到困惑。现在,我可以将其简化为一个更简单的示例:
// This function takes a callback that requires an object with `a`.
function callWithA (cb: (obj: {a: number}) => void) {
cb({a: 5});
}
// But we can pass a callback that expects more than `a`.
declare let callbackThatNeedsAAndB: (obj: {a: number, b: number}) => void;
callWithA(callbackThatNeedsAAndB); // No error but should be an error.
但是,如果将属性分隔为参数,则会正确显示错误:
function callWithA_separate (cb: (a: number) => void) {
cb(5);
}
declare let callbackThatNeedsAAndB_separate: (a: number, b: number) => void;
callWithA_separate(callbackThatNeedsAAndB_separate);
// Error: Argument of type '(a: number, b: number) => void' is not assignable to parameter of type '(a: number) => void'. ts(2345)
这可能是发生了什么:请参阅名为why are function parameters bivariant的打字稿常见问题解答条目
我希望这可以阐明这个问题。再说一次,这不是解决方案,但希望对理解问题有用。