【问题标题】:Typing redux forms v7 with TypeScript and React使用 TypeScript 和 React 输入 redux forms v7
【发布时间】:2018-02-15 03:15:13
【问题描述】:

我有一个简单的 react-redux 驱动的表单。我希望有一个 form.container.tsx 和一个 form.component.tsx,其中 form.container.tsx 包含所有与 redux 状态的连接减去字段。我正在尝试将我的容器包装在 react-redux 的 connect 中,然后将 reduxForm 包装在其中看起来像TypeScript, redux-form and connect

(理想的)form.container.tsx:

interface DummyFormContainerProps {}

export const DummyFormContainer: React.SFC<DummyFormContainerProps> = props => {
  const submitForm = (formValues: object) => {
    alert(formValues);
  };
  return (
    <DummyForm
      onSubmit={submitForm}
    />
  );
};

const mapStateToProps = (state: State) => ({});
const mapDispatchToProps = (dispatch: object) => {
  return {};
};
const mergeProps = (stateProps: State, dispatchProps: object | null, ownProps: object | void) => 
  Object.assign({}, stateProps, dispatchProps, ownProps);

const formConfiguration = {
  form: 'dummy-form',
  forceUnregisterOnUnmount: true
};

export default connect(mapStateToProps, mapDispatchToProps)(
  reduxForm(formConfiguration)(DummyFormContainer)
);

上述方法不起作用,但如果我取出 reduxForm() 部分,我会留下一个没有 reduxForm 集成的工作容器:

(不使用 reduxForm)form.container.tsx:

export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(
  DummyFormContainer
);

我已经尝试了 reduxForms 和 connect 的不同变体,目前都不起作用:

(带类)form.container.tsx:

export class DummyFormContainer extends React.Component<DummyFormContainerProps, void> {
  submitForm = (formValues: object) => {
    alert(formValues);
  }

  render() {
    return (
      <DummyForm
        onSubmit={this.submitForm}
      />
    );
  }
}

const mapStateToProps = (state: State) => ({});
const mapDispatchToProps = (dispatch: object) => {
  return {};
};
const mergeProps = (stateProps: State, dispatchProps: object | null, ownProps: object | void) => 
  Object.assign({}, stateProps, dispatchProps, ownProps);

const formConfiguration = {
  form: 'business-registration',
};

export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(
  reduxForm(formConfiguration)(DummyFormContainer) // ERROR
);

错误:

./src/modules/dummy-form/dummy-form.container.tsx
(100,32): error TS2345: Argument of type 'typeof DummyFormContainer' is not assignable to parameter of type 'ComponentType<InjectedFormProps<{}, {}>>'.
  Type 'typeof DummyFormContainer' is not assignable to type 'StatelessComponent<InjectedFormProps<{}, {}>>'.
    Type 'typeof DummyFormContainer' provides no match for the signature '(props: InjectedFormProps<{}, {}> & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.

(带有无状态功能组件)form.container.tsx:

export const DummyFormContainer: React.SFC<DummyFormContainerProps> = props => {
  const submitForm = (formValues: object) => {
    alert(formValues);
  };
  return (
    <DummyForm
      onSubmit={submitForm}
    />
  );
};

export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(
  reduxForm(formConfiguration)(DummyFormContainer) // ERROR
);

错误:

./src/modules/dummy-form/dummy-form.container.tsx
(100,3): error TS2345: Argument of type 'DecoratedComponentClass<{}, Partial<ConfigProps<{}, {}>>>' is not assignable to parameter of type 'ComponentType<(State & null & void) | (State & null & object) | (State & object & void) | (State ...'.
  Type 'DecoratedComponentClass<{}, Partial<ConfigProps<{}, {}>>>' is not assignable to type 'StatelessComponent<(State & null & void) | (State & null & object) | (State & object & void) | (S...'.
    Type 'DecoratedComponentClass<{}, Partial<ConfigProps<{}, {}>>>' provides no match for the signature '(props: (State & null & void & { children?: ReactNode; }) | (State & null & object & { children?: ReactNode; }) | (State & object & void & { children?: ReactNode; }) | (State & object & { children?: ReactNode; }), context?: any): ReactElement<any> | null'.

form.component.tsx 如下所示:

import * as React from 'react';
import Input from '../../components/input';

interface DummyFormProps {
  onSubmit: (formValues: object) => void
}

export const DummyForm: React.SFC<DummyFormProps> = () => {
  return (
    <div>
      <h1>DummyForm (no state)</h1>
      <form>
        <Input inputType="primary" />
      </form>
    </div>
  );
};

export default DummyForm;

组件是一个常规的 React 组件。

有谁知道如何正确连接reduxForm和react-redux的connect()?

【问题讨论】:

    标签: javascript reactjs typescript redux redux-form


    【解决方案1】:

    这是一个完全类型化的示例,它允许使用initialValues 初始化表单并传递其他道具(如IOwnProps):

    sampleForm.tsx:

    export interface IFormData {
      userId: string;
    }
    
    export interface IOwnProps {
      foo: string;
    }
    
    export interface IDispatchProps {
      onSubmit: (data: IFormData, dispatch: Dispatch<any>, props: IOwnProps) => void;
    }
    
    type AllSampleFormProps = IOwnProps & IDispatchProps & InjectedFormProps<IFormData, IOwnProps>;
    
    const SampleForm: React.SFC<AllSampleFormProps> = (props) => (
      <form onSubmit={props.handleSubmit(props.onSubmit)}>
        <div>foo={props.foo}</div>
        <Field name="userId" component="input" />
        <button type="submit">Submit</button>
      </form>
    );
    
    export const DecoratedSampleForm = reduxForm<IFormData, IOwnProps>({})(SampleForm);
    

    sampleForm.ts:

    这里的技巧是为mapStateToProps 指定正确的返回类型,否则编译器会像其他作者指出的那样报错。

    function mapStateToProps(state: AppState, props: IOwnProps): ConfigProps<IFormData, IOwnProps> {
      return {
        form: "sampleForm", // Form will be handled by Redux Form using this key
        initialValues: {
          userId: state.somethere.userId // Can also be calculated using props
        }
      }
    }
    
    function mapDispatchToProps(dispatch: Dispatch<any>): IDispatchProps {
      return {
        onSubmit: (formData: IFormData, dispatch: Dispatch<any>, props: IOwnProps) => {
          console.log(formData);
          console.log(props);
        }
      }
    }
    
    export default connect<ConfigProps<IFormData, IOwnProps>>(
      mapStateToProps,
      mapDispatchToProps
    )(DecoratedSampleForm);
    

    现在这个表单可以这样挂载:

    <FormContainer foo="bar"/>
    

    【讨论】:

    • 你如何处理你想添加更多道具然后表单/初始值的情况。假设我希望调用者添加下拉列表的初始值。你会怎么做?
    • 前。假设您的表单下拉列表中填充了 [a,b,c,d]。这不会进入 initialValues,因为它不是表单的选定值与表单的实际选定值的一部分。如果它不是初始值的一部分,那么在 mapStateToProps 中是如何配置的
    【解决方案2】:

    我发现我可以通过向 connect 语句提供空的 TStatePropsTDispatchProps 对象来消除错误。

    interface SampleFormData {
      username: string;
    }
    
    interface SampleFormProps {
      saveData: (data: SampleFormData) => void;
    }
    
    type AllSampleFormProps = SampleFormProps & InjectedFormProps<SampleFormData>;
    
    const SampleForm: React.SFC<AllSampleFormProps> = (props) => (
      <form onSubmit={props.handleSubmit(props.saveData)}>
        <Field name="username" component="input" />
      </form>
    );
    
    const DecoratedSampleForm = reduxForm<SampleFormData>({ form: "sampleForm" })(SampleForm);
    
    export default connect<{},{}>(
      () => ({}),
      (dispatch) => ({
        saveData: (data: SampleFormData) => dispatch({ type: "SAVE_DATA", data })
      })
    )(DecoratedSampleForm);
    

    这样做的一个缺点是它迫使我们盲目地提供连接道具,但我觉得这是一个比编写覆盖 @types 声明更优雅的解决方案。

    为了解决这个缺点,我能够通过提供正确接口与空对象的连接来验证类型;但是,此方法只能临时用于检查绑定,因为它不能解决 DecoratedComponentClass 错误。

    export default connect<{}, SampleFormProps, InjectedFormProps<SampleFormData>>(
      () => ({}),
      (dispatch) => ({
        saveData: (data: SampleFormData) => dispatch({ type: "SAVE_DATA", data })
      })
    )(DecoratedSampleForm);
    

    【讨论】:

      【解决方案3】:

      根据https://redux-form.com/7.0.4/examples/initializefromstate/ 中的示例,我在尝试从 redux 状态初始化表单时也遇到了这个问题

      我最终通过在更高级别连接组件来解决它,例如:

      component.tsx:

      interface DummyFormComponentProps {} extends InjectedFormProps
      
      const DummyFormComponent: React.SFC<DummyFormComponentProps> = props => {
        return (
          <form onSubmit={props.handleSubmit}>
            // Fields go here
          </form>
        )
      }
      
      export const DummyForm = reduxForm({
        form: "dummy-form"
      })(DummyFormComponent)
      
      // Trying to connect here also gave errors with DecoratedComponentClass
      

      container.tsx:

      interface DummyFormContainerProps {} extends Pick<InjectedFormProps,
        "initialValues"
      >
      
      const submitForm = (formValues: object) => {
        alert(formValues);
      };
      
      const DummyFormContainer: React.SFC<DummyFormContainerProps> = props => {  
        return (
          <DummyForm 
            initialValues={props.initialValues}
            onSubmit={submitForm}
          />
        )
      }
      
      const mapStateToProps = (state: State) => ({
        initialValues: {}
      });
      const mapDispatchToProps = (dispatch: object) => {
        return {};
      };
      export default connect(mapStateToProps, mapDispatchToProps)(DummyFormContainer)
      

      【讨论】:

      • 你有这个的更新版本吗?现在有更好的方法吗?
      【解决方案4】:

      我们最终做的是闭上眼睛并使用类型声明文件覆盖默认类型:

      redux-forms.d.ts:

      declare module 'redux-form' {
        type anyProps = { [key: string]: {} }
        function Field(): React.Component<anyProps>;
        function reduxForm({}): <T>(c: T) => T
        function reducer(): object
        interface SubmissionError {
          new(error?: {}) : Error;
        }
        function getFormValues(formName: string): (formName: {}) => {}
        function stopSubmit(formName: string, errorObject?: {}): any
        function isSubmitting(formName: string): any
        function setSubmitFailed(formName: string): any
        function setSubmitSucceeded(formName: string): any
        function touch(formName: string): any
        function clearSubmitErrors(formName: string): any
        function getFormMeta(formName: string, ...fields: string[]): (state: {}) => {}
        function getFormSyncErrors(formName: string): (state: {}) => {}
        function getFormSubmitErrors(formName: string): (state: {}) => {}
        function getFormNames(): any
      }
      

      【讨论】:

      • 将此类型文件放在项目根级别或 node_modules 文件夹中的位置
      • 我们只是把它放在 src/typings/redux-forms.d.ts 中。我们将 create-react-app 与 Microsoft 的 Typescript 脚本一起使用。没有修改 package.json 来做任何事情,所以我相信 TypeScript 只是在 src/ 文件夹中挑选东西并读取 'declare' 关键字。
      • 当我按照你说的做的时候。我遇到了以下错误。 D:\repositories\tsproj\reactjs-ts-form-demo\node_modules\@types\redux-form\lib\selectors.d.ts (1,10): error TS2305: Module ''redux-form'' has no导出的成员“FormErrors”。我的依赖是:jsfiddle.net/mglrahul/9qrn6gbn/1
      • 听起来TypeScript声明正在工作,您需要在声明中定义'FormErrors'的类型
      【解决方案5】:

      我有同样的问题,发现它是由“@types/react-redux”引起的,删除这个类型定义文件,一切都按照你期望的方式工作,没有任何其他副作用/类型错误该类型定义文件。

      【讨论】:

        猜你喜欢
        • 2021-11-22
        • 2018-10-23
        • 1970-01-01
        • 2019-05-03
        • 1970-01-01
        • 2017-09-28
        • 1970-01-01
        • 2020-08-04
        • 2021-01-09
        相关资源
        最近更新 更多