反应TypeScript高阶组件类型

时间:2019-03-03 19:48:24

标签: reactjs typescript high-order-component

我试图将现有的React组件(react-select)包装在High Order Component(HoC)中,以提供一些条件渲染逻辑。我面临的困难是让TypeScript生成一个将HoC包装程序和包装的组件属性结合在一起的组件。

例如:

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 TypeScript 3.3.3

2 个答案:

答案 0 :(得分: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);

答案 1 :(得分:0)

好的-所以我设法解决了这个问题。当前的解决方案对我来说满足两个关键要求:

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

  2. 公开HoC包装器属性和基础子组件属性的并集,以便Intellisense显示包装组件上的所有可用属性。

我尝试简化withHoC函数所期望的类型上的实际操作,并对其进行了硬编码(在我的情况下为react-select),因此withHoC包装程序仅接受react-select Select要包装的组件。其他可能会引发类型错误。

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和TypeScript节点,请确保TypeScript SDK版本与您的npm节点包同步,否则IDE可以报告在执行命令行编译时不会出现的错误(这使我无休止)红鲱鱼问题)。

Microsoft发布了this Url,该书很少更新,可能已经过时,但是公司中没有人注意到。

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