首页 > 解决方案 > 将组件作为变量传递时如何修复 TypeScript React 道具类型?

问题描述

我将 React 组件作为变量传递,并试图用正确的类型保护自己免受运行时错误的影响。问题是,当我需要从变量实例化组件时,prop-types 感觉是“颠倒的”。以下代码段将更好地说明问题。

还有我找到的解决方案 - 用功能组件包装组件,它只是通过道具

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

标签: reactjstypescripttypescript-typings

解决方案


我稍微打破了这一点,是的,打字稿错误检查器中似乎存在漏洞。这不是解决方案,但我可以将问题简化为更清晰的示例。这是您应该有错误的代码:

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)

这可能是正在发生的事情:请参阅 Typescript FAQ 条目,称为为什么函数参数是双变量的

我希望这能对这个问题有所启发。同样,这不是一个解决方案,但希望它有助于理解这个问题。


推荐阅读