在typescript中,当连接Component属性时,我该如何使用Component?

时间:2018-05-14 03:05:16

标签: reactjs typescript redux

环境

  • typescript 2.8
  • 反应16
  • 终极版

Foo.tsx

GridControl gridControl2 = new GridControl();
gridControl2.DataSource = exportDataList; // my data list

SaveFileDialog fileDialog = new SaveFileDialog();
fileDialog.Title = "Data Export";
fileDialog.Filter = "Excel (2010) (.xlsx)|*.xlsx|*.xls|*.csv";
DialogResult dialogResult = fileDialog.ShowDialog();
if (dialogResult == DialogResult.OK)
    gridControl2.ExportToXls(fileDialog.FileName);  // NullReferenceException

Index.tsx

以下代码缺少userId props错误

interface IFooProps{
  userId:number
}

class Foo extends Component<IFooProps>{
    render(){
      return <p>foo...</p>
    }
}

const mapStateToProps = (state: IState) => {
  return {
    userId: state.user.id,
  }
}

const mapDispatchToProps = (dispatch: Dispatch) => {
  return {}
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Foo)

我想要的是什么:

道具来自// missing userId props error <Foo /> ,不需要再次传递。

3 个答案:

答案 0 :(得分:4)

您的问题是人们在使用React从JS切换到TS并使用HOC(如connect及其组件时遇到的常见问题。

基本上,使用您的界面IFooProps,您告诉TS每当使用该组件时userId都是必需的道具且TS实际上并不知道,userId将通过connect照顾/注入道具。

有几种方法可以解决问题,第一种方法是使userId成为可选属性。

interface IFooProps{
  userId?: number
}

这种方法确实有缩小规模,因为当你使用userId时,TS会要求你执行null / undefined检查。但是,它是最简单的一个。

另一种方法是定义另一个接口,在您的示例中,可以按常规命名:IFooInjectedProps。您的组件将如下:

interface IFooProps {
   // ....Whatever props that require by the component
}

// All injected props from the store
interface IFooInjectedProps extends IFooProps {
  userId: number
}

class Foo extends Component<IFooProps> {
    get injectedProps() {
       // Cast this.props as injectedProps to bypass typechecking 
       return this.props as IFooInjectedProps;
    }

    render() {
      // Get userId from the getter `injectedProps`
      const { userId } = this.injectedProps;

      return <p>foo...</p>
    }
}

答案 1 :(得分:0)

因为你说过&#34; IFooProps&#34;作为道具的类型,它期望您在使用它时发送userId。将类型设置为any。使用redux时,不需要设置道具的类型。

class Foo extends Component<any>{
   render(){
     return <p>foo...</p>
   }
}

答案 2 :(得分:0)

使用Typescript和Redux几年后,我设法找到了connect的终极解决方案。这有点棘手

首先,您需要使用typescript中的module augmentation功能。它允许您扩展现有的类型。我们会用它来扩展react-redux类型。 您需要使用以下代码在项目中的某个位置创建.ts文件:

import { Component, ComponentClass } from "react-redux";

declare module "react-redux" {
  export interface InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> {
    <P extends TInjectedProps>(component: Component<P>): ComponentClass<
      Omit<P, keyof TInjectedProps> & TNeedsProps
    > & {
      WrappedComponent: Component<P>;
    };

    allProps: TInjectedProps & TNeedsProps;
  }
}

这里我们向现有的InferableComponentEnhancerWithProps接口添加一个属性allProps: TInjectedProps & TNeedsProps;,其余的代码是从原始类型中重写现有的接口声明。

创建后,typescript编译器会自动选择新的定义,现在我们可以回到你的文件了

<强> Foo.tsx

// you only define props that you would like to pass from the parent component, not the props that are injected from redux state
interface IFooProps {
  testProp: any;
}

// IConnectProps are defined at the bottom
class Foo extends Component<IConnectProps>{
    render() {
      this.props.testProps;
      this.props.userId;
      return <p>foo...</p>
    }
}

const mapStateToProps = (state: IState, props: IFooProps) => {
  return {
    userId: state.user.id,
    ...props,
  }
}

const mapDispatchToProps = (dispatch: Dispatch) => {
  return {}
}

const connectCreator = connect(
  mapStateToProps,
  mapDispatchToProps
);

// here we use our new typings, thanks to them typescript automatically merge 
// props that we declared in mapStateToProps so the IConnectProps 
// looks like {
//  userId: string;
//  testProp: any;
// }
type IConnectProps = typeof connectCreator.allProps;

export default connectCreator(Foo);

<强> Index.tsx

// typescript only tells you to pass a testProp
<Foo testProp={5} />