【问题标题】:React TypeScript high order component typingsReact TypeScript 高阶组件类型
【发布时间】:2019-07-25 03:51:38
【问题描述】:

我正在尝试将现有的反应组件 (react-select) 包装在高阶组件 (HoC) 中,以提供一些条件渲染逻辑。我面临的困难是让 TypeScript 生成一个结合了 HoC 包装器和 Wrapped 组件属性的组件。

例如:

import React from "react";
import Select from "react-select";

interface HOCProps { 
  foo: string
}

// Return a type with the HOCProps properties removed.
type WitoutPrefilled<T extends HOCProps> = Pick<T, Exclude<keyof T, 'foo'>>; 

function withHoC<P extends HOCProps>(WrappedComponent: React.ComponentType<P>) {
    return class SomeHOC extends React.Component<WithoutPrefilled<P>> {
        public render(): JSX.Element {
            return <WrappedComponent {...this.props as P} /*foo={"test"}*/ />;
        }
    };
}

// Generate a wrapped component with a property union of the wrapped Select and outer HoC (HOCProps & Select) ?
const Wrapped = withHoC(Select);

实现此目的的正确方法是什么?

反应 16.8.3 打字稿 3.3.3

【问题讨论】:

  • 问题出在哪里?您的解决方案看起来不错。
  • 除非它导致这个编译器错误:(TS) Argument of type 'typeof Select' is not assignable to parameter of type 'ComponentType&lt;HOCProps&gt;'. Type 'typeof Select' is not assignable to type 'ComponentClass&lt;HOCProps, any&gt;'. Types of property 'defaultProps' are incompatible. Type 'Props&lt;any&gt;' has no properties in common with type 'Partial&lt;HOCProps&gt;'.

标签: reactjs typescript high-order-component


【解决方案1】:

首先,确保您的高阶组件提供您的组件感兴趣的道具。以className 为例。

interface WithClassName {
  className: string;
}

我们的withClassName HOC 将接受一个准备好接受className 作为道具的组件,并返回一个不再接受className 的组件。

export function withClassName<T extends React.ComponentType<Partial<WithClassName>>>(Component: T): React.FunctionComponent<Omit<React.ComponentProps<T>, keyof WithClassName>> {
  return props => React.createElement(Component, { className: "foo", ...props });
}

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

用法:

const NewSelect = withClassName(Select);

【讨论】:

  • 我明白了,但是这个例子不起作用,因为className 存在于Select 上。如果我将className 更改为bar 并更新接口和withClassName 函数,我会得到原来的错误。
  • 当然可以——如果一个组件不接受名为 bar 的 prop,为什么要接受 bar 来提供它?
  • 也许这是我的误解。我的理解是withClassName 正在生成一个包装Component 的组件,传递一个只包含与Component 相关的属性的属性对象,剥离WithClassName 接口中定义的附加属性。 HoC 函数withClassName 返回一个封装的组件,该组件暴露
  • 接口WithClassName上的属性,同时转发包装组件上的属性。 classNameSelect 上已经存在的属性,所以不能作为一个很好的例子,所以将接口更新为interface WithClassName { bar: string; } 并将withClassName HoC 返回到[...] Component, { bar: "anything", ...props }); 当然我应该期待const NewSelect = withClassName(Select)返回一个接受属性bar的组件并将任何其他属性转发给Select包装的组件?
  • 感谢您的帮助,我根据您的反馈进行了一些挖掘并找到了解决方案(见上文)
【解决方案2】:

好的 - 所以我已经设法让它工作了。当前的解决方案满足了我的两个关键要求:

  1. 为正在包装的子组件推断 defaultProps(如果它们存在),这意味着由 HoC 包装器生成的组件不需要显式传递它们。

  2. 公开 HoC 包装器属性和底层子组件属性的联合,以便 Intellisense 显示包装组件上的所有可用属性。

在我试图简化实际发生的事情时,withHoC 函数所期望的类型是硬编码的(在我的情况下为 react-select),因此 withHoC 包装器将只接受 react-select @987654329 @要包装的组件。其他任何东西都可能引发类型错误。

This link 描述了一些代码,这些代码可能能够自动推断出要被withHoC 包装的组件的类型,从而使withHoC 可以与react-select's Select 以外的组件类型一起使用。

// node dependencies used (because dependencies mutate so much this may not work in other versions):
// "react": "^16.8.2",
// "react-dom": "^16.8.2",
// "typescript": "^3.3.3",
// "react-select": "2.4.1",
// "@types/react": "^16.8.3",
// "@types/react-dom": "^16.8.2",
// "@types/react-select": "^2.0.13",

// Visual Studio 2017 TypeScript SDK build 3.3.3

import ReactDOM from "react-dom";   // (Optional - just for testing)
import React from "react";
import Select from "react-select";

// Properties shape for React Select (See react-select @type definitions)
import { Props } from "react-select/lib/Select";

// The properties we are want to add so that our resultant wrapped component contains all of its own properties plus the extra properties specified here
interface IHOCProps {
    bar: string;
}

function withHoC(WrappedComponent: React.ComponentType<Props>) {
    return class SomeHOC extends React.Component<IHOCProps & Props> {

        // If 'bar' isn't specified, configure a default (this section is optional)
        static defaultProps = {
            bar: "default bar"
        };

        public render(): JSX.Element {

            return <><div>{this.props.bar}</div><WrappedComponent {...this.props as any} /></>;
        }
    };
}

const WrappedSelect = withHoC(Select);

export { WrappedSelect, Select };

// Test it out (Optional). If using Visual Studio 2017 or some other IDE with intellisense, 
// <WrappedSelect /> should show all the 'react-select' properties and the HoC property (bar). 
// Additionally, all the defaultProps for 'react-select' are automatically inferred so no TypeScript errors about missing props when using <WrappedSelect />. 

const TestMe = () => 
<>
    <WrappedSelect bar="bumble monkey"> 
    <WrappedSelect />
    <Select />
</>;
// Append the result to an HTML document body element 
ReactDOM.render(<TestMe />,document.getElementsByTagName("body")[0]); 

有点不惜代价的胜利,因为它不能跨类型重复使用,但它确实有效。

最后一个金块;如果您使用 Visual Studio 2017 和 Node for TypeScript,请确保 TypeScript SDK 版本与您的 npm 节点包同步,否则 IDE 可能会报告在进行命令行编译时不会出现的错误(这导致我没有尽头红鲱鱼问题)。

Microsoft 发布了this Url,它不经常更新并且可能已过时,但公司中没有人注意到。

最新的 SDK 总是可以在 GitHub here 上找到,它通常总是与 npm 和 nuget 包一起发布。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-26
    • 2021-11-23
    • 2012-10-25
    • 1970-01-01
    • 2019-10-25
    • 2020-08-31
    相关资源
    最近更新 更多