我正在尝试为Higher Order Component
中的Dialog
组件创建一个React Native
。不幸的是,我有一些根本不了解的编译错误。我在Higher Order Components
的{{1}}上关注了this tutorial,但是没有显示使TypeScript
正常工作的示例。
我有一个名为ref
的组件,我通过一个名为DialogLoading
的{{1}}导出了它。 Higher Order Component
组件定义了两个接口,以确定它注入了什么道具以及它接受了什么其他道具。在下面的代码中,类型参数withActions
,withActions
和C
分别代表A
,P
和ComponentType
。
接口为:
ActionType
和
PropType
我还声明了一个类型别名,该别名表示interface InjectedProps<A>
{ onActionClicked: (action: A) => void;}
的最终道具类型。此类型应具有包装组件的所有属性,interface ExternalProps<C, A>
{ onActionClickListener?: (component: C | null, action: A) => void;}
接口的所有属性,而不是HOC
接口的所有属性。声明如下:
ExternalProps<C, A>
然后InjectedProps<A>
声明如下:
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
type Subtract<T, K> = Omit<T, keyof K>;
type HocProps<C, A, P extends InjectedProps<A>> = Subtract<P, InjectedProps<A>> & ExternalProps<C, A>;
,可由以下人员使用:
Higher Order Component
在export default <C, A, P extends InjectedProps<A>> (WrappedComponent: React.ComponentType<P>) =>
{
const hoc = class WithActions extends React.Component<HocProps<C, A, P>>
{
...Contents of class removed for breivity.
private onActionClicked = (action: A) =>
{
this.onActionClickedListeners.forEach(listener =>
{ listener(this.wrapped, action);});
}
private wrapped: C | null;
render()
{
return (
<WrappedComponent ref={i => this.wrapped = i} onActionClicked={this.onActionClicked} {...this.props} />
);
}
}
return hoc;
}
的render函数内部的ref回调上,<DialogLoading onActionClickListener={this.onActionClickListener} title="Loading Data" section="Connecting" />;
给我以下错误消息:
HOC
我怀疑这是因为传入的TypeScript
的类型为[ts] Property 'ref' does not exist on type 'IntrinsicAttributes & InjectedProps<A> & { children?: ReactNode; }'.
[ts] Type 'Component<P, ComponentState, never> | null' is not assignable to type 'C | null'.
Type 'Component<P, ComponentState, never>' is not assignable to type 'C'.
,它是WrappedComponent
和React.ComponentType<P>
的并集类型。引发错误,因为React中的无状态组件不接受ref回调。然后可能的解决方案是将其类型更改为 just React.ComponentClass<P>
。
这种种类解决了该问题,但奇怪的是,现在包装组件的React.SFC<P>
道具上引发了一个新错误!错误是:
React.ComponentClass<P>
第二个错误使我完全困惑。 甚至陌生人的原因是,当我将onActionClicked
的类型别名调整为以下值时(即,我不再从[ts] Type '(action: A) => void' is not assignable to type '(IntrinsicAttributes & IntrinsicClassAttributes<Component<P, ComponentState, never>> & Readonly<{ children?: ReactNode; }> & Readonly<P>)["onActionClicked"]'.
WithActions.tsx(7, 5): The expected type comes from property 'onActionClicked' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<P, ComponentState, never>> & Readonly<{ children?: ReactNode; }> & Readonly<P>'
中减去HocProps
):< / p>
InjectedProps<A>
P
上的错误已消除!这对我来说很奇怪,因为type HocProps<C, A, P extends InjectedProps<A>> = P & ExternalProps<C, A>;
的类型定义与包装组件的prop类型无关!但是,这种“解决方案”对我来说是不可取的,因为现在onActionClicked
的用户也可以注射HocProps
。
那么我在哪里错了?
我是否正确假设ref回调不起作用,因为InjectedProps<A>
类型是HOC
而不是Wrapped Component
?
为什么将React.ComponentType<P>
类型更改为React.ComponentClass<P>
会导致Wrapped Component
的{{1}}道具上出现编译错误?
为什么更改React.ComponentClass<P>
的类型别名会消除onActionClicked
道具上的错误?他们不是完全无关吗?
我煮熟的Wrapped Component
功能正确吗?错误是从哪里来的?
任何帮助将不胜感激,所以在此先感谢!
答案 0 :(得分:2)
我是否正确假设ref回调不起作用,因为
Wrapped Component
类型是React.ComponentType<P>
而不是React.ComponentClass<P>
?
从广义上讲,是的。当您从WrappedComponent
创建一个具有联合类型(React.ComponentType<P> = React.ComponentClass<P> | React.StatelessComponent<P>
)的JSX元素时,TypeScript会找到与该联合的每个替代对应的props类型,然后使用子类型来获取props类型的联合减少。来自checker.ts(太大,无法链接到GitHub上的行):
function resolveCustomJsxElementAttributesType(openingLikeElement: JsxOpeningLikeElement,
shouldIncludeAllStatelessAttributesType: boolean,
elementType: Type,
elementClassType?: Type): Type {
if (elementType.flags & TypeFlags.Union) {
const types = (elementType as UnionType).types;
return getUnionType(types.map(type => {
return resolveCustomJsxElementAttributesType(openingLikeElement, shouldIncludeAllStatelessAttributesType, type, elementClassType);
}), UnionReduction.Subtype);
}
我不确定为什么这是规则;交叉点对于确保所有必需的道具都存在是更有意义的,而与组件的并集的替代方式无关。在我们的示例中,React.ComponentClass<P>
的道具类型包括ref
,而React.StatelessComponent<P>
的道具类型不包括。通常,如果属性存在于联合的至少一个组成部分中,则该属性被视为“已知”。但是,在该示例中,子类型归约排除了React.ComponentClass<P>
的props类型,因为它恰好是React.StatelessComponent<P>
的props类型的子类型(具有更多的属性),所以我们剩下React.StatelessComponent<P>
,而没有ref
属性。再次,这一切看起来很奇怪,但是它产生了一个错误,指出了代码中的实际错误,因此我不倾向于针对TypeScript提交错误。
为什么将
Wrapped Component
类型更改为React.ComponentClass<P>
会导致onActionClicked
的{{1}}道具上出现编译错误?
此错误的根本原因是TypeScript无法推断类型Wrapped Component
的组合onActionClicked={this.onActionClicked} {...this.props}
提供了所需的道具类型Readonly<HocProps<C, A, P>> & { onActionClicked: (action: A) => void; }
。您的意图是,如果从P
中减去onActionClicked
,然后再添加回去,则应该留下P
,但是TypeScript没有内置规则可以对此进行验证。 (存在一个潜在的问题,P
可以声明类型为P
的子类型的onActionClicked
属性,但是您的使用模式足够普遍,我希望如果TypeScript要添加这样的属性,一条规则,该规则将以某种方式解决这个问题。)
TypeScript 3.0.3在(action: A) => void
上报告错误令人困惑(尽管这可能是由于我提到的问题所致)。我进行了测试,并在3.0.3和3.2.0-dev.20180926之间的某个时刻进行了更改,以报告onActionClicked
上的错误,这似乎更合理,因此在此无需进行进一步的跟踪。>
WrappedComponent
类型为WrappedComponent
时不会发生错误 的原因是因为对于无状态函数组件(与组件类不同),TypeScript仅检查您传递了足够的道具来满足道具类型React.ComponentType<P>
(即P
,而不是InjectedProps<A>
)的约束。我相信这是一个错误,并且有reported it。
为什么更改
P
的类型别名会消除HocProps
道具上的错误?他们不是完全无关吗?
因为onActionClicked
本身就满足要求的{...this.props}
。
我煮熟的
P
功能正确吗?错误是从哪里来的?
您的Subtract
是正确的,但是如上所述,TypeScript对基本Subtract
和Pick
的推理几乎没有支持。
要解决您的原始问题,我建议使用类型别名和交集而不是像this answer中所述的减法。就您而言,这看起来像:
Exclude
(这里我还更改了import * as React from "react";
interface InjectedProps<A>
{ onActionClicked: (action: A) => void;}
interface ExternalProps<C, A>
{ onActionClickListener?: (component: C | null, action: A) => void;}
// See https://stackoverflow.com/a/52528669 for full explanation.
const hocInnerPropsMarker = Symbol();
type HocInnerProps<P, A> = P & {[hocInnerPropsMarker]?: undefined} & InjectedProps<A>;
type HocProps<C, A, P> = P & ExternalProps<C, A>;
const hoc = <C extends React.Component<HocInnerProps<P, A>>, A, P>
(WrappedComponent: {new(props: HocInnerProps<P, A>, context?: any): C}) =>
{
const hoc = class WithActions extends React.Component<HocProps<C, A, P>>
{
onActionClickedListeners; // dummy declaration
private onActionClicked = (action: A) =>
{
this.onActionClickedListeners.forEach(listener =>
{ listener(this.wrapped, action);});
}
private wrapped: C | null;
render()
{
// Workaround for https://github.com/Microsoft/TypeScript/issues/27484
let passthroughProps: Readonly<P> = this.props;
let innerProps: Readonly<HocInnerProps<P, A>> = Object.assign(
{} as {[hocInnerPropsMarker]?: undefined},
passthroughProps, {onActionClicked: this.onActionClicked});
return (
<WrappedComponent ref={i => this.wrapped = i} {...innerProps} />
);
}
}
return hoc;
}
interface DiagLoadingOwnProps {
title: string;
section: string;
}
// Comment out the `{[hocInnerPropsMarker]?: undefined} &` in `HocInnerProps`
// and uncomment the following two lines to see the inference mysteriously fail.
//type Oops1<T> = DiagLoadingOwnProps & InjectedProps<string>;
//type Oops2 = Oops1<number>;
class DiagLoadingOrig extends React.Component<
// I believe that the `A` type parameter is foiling the inference rule that
// throws out matching constituents from unions or intersections, so we are
// left to rely on the rule that matches up unions or intersections that are
// tagged as references to the same type alias.
HocInnerProps<DiagLoadingOwnProps, string>,
{}> {}
const DialogLoading = hoc(DiagLoadingOrig);
class OtherComponent extends React.Component<{}, {}> {
onActionClickListener;
render() {
return <DialogLoading onActionClickListener={this.onActionClickListener} title="Loading Data" section="Connecting" />;
}
}
的类型,使其实例类型为WrappedComponent
,以便对C
的赋值进行类型检查。)