将组件作为变量传递时,如何修复TypeScript React props类型?

时间:2019-04-30 14:21:11

标签: reactjs typescript typescript-typings

我将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类型以更简洁的方式解决它

1 个答案:

答案 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的打字稿常见问题解答条目

我希望这可以阐明这个问题。再说一次,这不是解决方案,但希望对理解问题有用。