无法在Typescript的子组件内使用HOC属性

时间:2019-06-10 20:16:50

标签: reactjs typescript

问题

我用一个典型的HOC包装了一个组件,在其他非TS项目中以某种格式使用了该HOC。我的问题是我无法在包装好的组件内部使用HOC的prop currentBreakpoint,因为它希望它属于组件的类型:

Property 'currentBreakpoint' does not exist on type 'Readonly<OwnProps> & Readonly<{ children?: ReactNode; }>'.  TS2339

因此,我随后导入了HOC的props接口,并将其合并到组件自己的prop接口中,但是我得到了:

Property 'currentBreakpoint' is missing in type '{}' but required in type 'Readonly<Props>'.  TS2741

我希望在我看来调用currentBreakpoint时会定义属性<ChildComponent />,即使它是由HOC提供的。

-

这是我的文件:

  • react-scripts@3.0.1
  • typescript@3.4.5

ChildComponent.js

import React, { Component } from 'react';
import withBreakpoints, {
  Breakpoints,
  Props as WithBreakpointsProps
} from 'lib/withBreakpoints';

interface OwnProps {}
type Props = WithBreakpointsProps & OwnProps;

class ChildComponent extends Component<Props> {
  constructor(props: Props) {
    super(props);

    this.state = {
      daysPerPage: this.getDaysPerPageFromViewportSize(),
      cursor: new Date()
    };
  }

  getDaysPerPageFromViewportSize = () => {
    // the problem area
    const { currentBreakpoint } = this.props;

    let daysPerPage;
    switch (currentBreakpoint) {
      case Breakpoints.SMALL.label:
      case Breakpoints.EXTRA_SMALL.label:
        daysPerPage = 1;
        break;
      case Breakpoints.MEDIUM.label:
      case Breakpoints.LARGE.label:
        daysPerPage = 4;
        break;
      case Breakpoints.EXTRA_LARGE.label:
        daysPerPage = 6;
        break;
      default: 
        daysPerPage = 1;
        break;
    }

    return daysPerPage
  };

  render() {
    return (
      <div className="AvailabilityCalendar" />
    );
  }
}

export default withBreakpoints<Props>(AvailabilityCalendar);

withBreakpoints.ts

import React, { Component, ComponentType } from 'react';

export type CurrentBreakpoint = string | null;

export interface Props {
  currentBreakpoint: string
}

export interface Breakpoint {
  label: string;
  lowerBound: number;
  upperBound: number;
}

export interface State {
  currentBreakpoint: CurrentBreakpoint;
}

export const Breakpoints: {
  [id: string]: Breakpoint
} = {
  EXTRA_SMALL: {
    label: 'EXTRA_SMALL',
    lowerBound: 0,
    upperBound: 640
  },
  SMALL: {
    label: 'SMALL',
    lowerBound: 641,
    upperBound: 1024
  },
  MEDIUM: {
    label: 'MEDIUM',
    lowerBound: 1025,
    upperBound: 1280
  },
  LARGE: {
    label: 'LARGE',
    lowerBound: 1281,
    upperBound: 1920
  },
  EXTRA_LARGE: {
    label: 'EXTRA_LARGE',
    lowerBound: 1921,
    upperBound: 1000000
  }
};

const withBreakpoints = <WrappedComponentProps extends object>(
  WrappedComponent: ComponentType<WrappedComponentProps>
) => {
  class WithBreakpoints extends Component<WrappedComponentProps, State> {
    constructor(props: WrappedComponentProps) {
      super(props);

      this.state = {
        currentBreakpoint: this.getCurrentBreakpoint()
      };
    }

    componentDidMount() {
      window.addEventListener('resize', this.checkBreakpoints);
    }

    componentWillUnmount() {
      window.removeEventListener('resize', this.checkBreakpoints);
    }

    checkBreakpoints = () => {
      let currentBreakpoint: CurrentBreakpoint = this.getCurrentBreakpoint();

      if (currentBreakpoint !== this.state.currentBreakpoint) {
        this.setState({ currentBreakpoint });
      }
    };

    getCurrentBreakpoint = (): CurrentBreakpoint => {
      const currentViewportWidth: number = Math.round(window.innerWidth);

      return Object.keys(Breakpoints).find(
        key =>
          Breakpoints[key].lowerBound < currentViewportWidth &&
          Breakpoints[key].upperBound > currentViewportWidth
      ) || null;
    };

    render() {
      return (
        <WrappedComponent
          {...this.props as WrappedComponentProps}
          currentBreakpoint={this.state.currentBreakpoint}
        />
      );
    }
  }

  return WithBreakpoints;
};

export default withBreakpoints;

-

响应“在currentBreakpoint上使ChildComponent为可选”。

我已将其视为其他问题的公认答案,但我认为我们都可以同意这是对属性上可选标志的不当使用,并且违反了使用Typescript的目的。

2 个答案:

答案 0 :(得分:1)

您应该阅读有关ts泛型的信息。

例如,假设一个组件具有外部道具,如type OwnProps = { a: string },而HOC注入了type InjectedProps = { b: boolean }。这意味着该组件的最终道具将是type Props = OwnProps & InjectedProps

但是,如果您这样做:

const Component = (props: Props) => {}
export default hoc(Component);

并且尝试仅通过a道具使用该组件,当该道具实际上在内部被接收时,它将抱怨没有收到b道具。

您可以做的事情是这样的:

const Component = (props: Props) => {}
export default hoc<OwnProps>(Component);

以便特设组件知道其需要的外部道具以及内部接收的道具。

有了这一点,就需要对HOC定义及其所接收的泛型进行一些调整,以使其具有适当的最终Props

答案 1 :(得分:1)

要扩展@cfraser的答案,注入内部道具的HoC的常见模式是键入您的HoC

<plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
            <skipTests>true</skipTests>
        </configuration>
    </plugin>

// these will be injected by the HoC interface SomeInternalProps { someProp: any } function hoc<P>(Wrapped: React.Component<P & SomeInternalProps>): React.Component<Omit<P, keyof SomeInternalProps>> 期望一个包含道具hocP的组件,并返回一个仅期望SomeInternalProps的组件。

您甚至可以省略泛型参数,TS会弄清楚。例如

P

有关function SomeComponent(props: { prop: any } & SomeInternalProps) {...} export default hoc(SomeComponent) // Component<{ prop: any }> 的信息,请参见here