私は、単純なリアクションレッドを使用したフォームを持っています。 form.container.tsxとform.component.tsxがあり、form.container.tsxがRedux状態へのすべての接続からFieldを引いたものを保持することを望みます。私は自分のコンテナをreact-reduxの接続でラップし、その中に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;
<入力>コンポーネントは、通常のReactコンポーネントです。
ReduxFormとreact-reduxのconnect()を適切に接続する方法を知っている人はいますか?
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)
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"/>
空のTStateProps
およびTDispatchProps
オブジェクトを含む接続ステートメントを提供することにより、エラーを無視できることがわかりました。
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);
最終的には、目を閉じて、型宣言ファイルでデフォルトの型をオーバーライドしました。
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
}