使用 TypeScript 和 React 键入 redux 表单 v7

IT技术 javascript reactjs typescript redux redux-form
2021-05-08 05:03:16

我有一个简单的 react-redux 驱动的表单。我希望有一个 form.container.tsx 和一个 form.component.tsx,其中 form.container.tsx 保存所有与 redux 状态的连接减去 Field 的连接。我试图将我的容器包装在 react-redux 的 connect 中,然后将 reduxForm 包装在其中,使其看起来像TypeScript、redux-form 和 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;

而 <Input> 组件是一个常规的 React 组件。

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

4个回答

这是一个完全类型化的示例,它允许使用initialValues和传递额外的 props (as IOwnProps)来初始化表单

样本表格.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);

样本表格.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"/>

我发现我可以通过提供带有空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);

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

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

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

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

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

组件.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

容器.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)

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

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
}