打字稿是控制参数的正确类型?

Typescript which is correct type for control parameter?

我为我的表单创建了一个打字稿反应组件 FormInput。该组件使用 MUI 框架和 react-hook-form。问题是我无法在我的组件中为参数“control”设置正确的类型。我临时用 type any 合并了它。

在道具控件中输入的类型是类型Control<TrekEditDto, object>

文档中的 react-hook-form 是参数 control?: Control<FieldValues, object> | undefined

我认为解决方案是使用泛型。

这是我的反应输入组件:

import { Controller } from 'react-hook-form';
import { TextField, Typography } from '@material-ui/core';
import React from 'react';

type FormInputProps = {
  control: any;
  name: string;
  label: string;
  multiline?: boolean;
  defaultValue?: string;
  fullWidth?: boolean;
  disabled?: boolean;
  error?: { message: string };
};

const FormInput = ({ control, name, label, multiline, defaultValue, fullWidth, disabled, error }: FormInputProps) => {
  return (
    <>
      <Controller
        control={control}
        name={name}
        defaultValue={defaultValue}
        render={(...inputProps) => {
          return (
            <>
              <TextField
                {...inputProps}
                fullWidth={fullWidth}
                label={label}
                multiline={multiline}
                disabled={disabled}
                error={error != null}
              />
              <Typography variant="inherit" color="textSecondary">
                {error?.message}
              </Typography>
            </>
          );
        }}
      />
    </>
  );
};

export default FormInput;

这部分正在调用我的组件:

<FormInput
   control={control}
   name="translation.cz.name"
   label={t('trek.create.formField.name.cs')}
/>

它是包含调用我的组件的完整文件。

import React, {useEffect, useState} from "react";
import TrekService from "../services/TrekService";
import {
    Box,
    Button,
    Container, DialogActions, DialogContent, DialogContentText,
    FormLabel,
    Grid,
    Paper,
    Snackbar,
    Typography
} from "@material-ui/core";
import {FormRadio} from "../components/FormRadio";
import {http} from "../../../common/axios-api";
import {useForm} from "react-hook-form";
import {useTranslation} from "react-i18next";
import {DropzoneArea} from "material-ui-dropzone";
import AlertDialog from "../../../components/AlertDialog/AlertDialog";
import {Alert} from "@material-ui/lab";
import {useHistory} from "react-router-dom";
import {Permissions} from "../../../auth/hooks/Permissions";
import ROLES from "../../../auth/Roles";
import {TYPE_OF_STATUS} from "../commons/TrekConstants";
import {FormAutocomplete} from "../components/FormAutocomplete";
import {FormDifficulty} from "../components/FormDifficulty";
import {FormExperiences} from "../components/FormExperiences";
import {TrekEditDto} from "../../../models/Trek";
import {Codebook} from "../../../models/Codebook";
import FormInput from "../components/FormInput";
import FormInputMultiline from "../components/FormInputMultiline";

export default function EditTrek() {

    const {t} = useTranslation(['translation', 'common']);
    const history = useHistory();
    const [trek, setTrek] = useState<TrekEditDto>();
    const [codebook, setCodebook] = useState<Codebook>();
    const {handleSubmit, control, reset, setValue, watch, formState: {errors}} = useForm<TrekEditDto>({
        defaultValues: trek
    });
    const [trekImages, setTrekImages] = useState<File[]>([]);
    const [coverImage, setCoverImage] = useState<File[]>([]);
    const [trekGpx, setTrekGpx] = useState<File[]>([]);
    const [openSuccessDialog, setOpenSuccessDialog] = useState(false);
    const [openErrorMessage, setOpenErrorMessage] = useState(false);
    const watchTrekType = watch('trip_type_id');

    // max file size is 2MB
    const MAX_FILE_SIZE_FOR_UPLOAD = 2 * 1000 * 1000;

    // max number of file for upload image
    const MAX_NUMBER_FILES_FOR_UPLOAD = 6;

    // max number gpx files for upload
    const MAX_NUMBER_GPX_FILES_FOR_UPLOAD = 1;

    useEffect(() => {
        async function fetchData() {
            try {
                await loadCoodBook();
                const trekId = (new URLSearchParams(window.location.search)).get('trekId');
                if (trekId == null) {
                    throw new Error(`Couldnt load data becouse trek id = ${trekId}`);
                }
                await TrekService.findById(trekId)
                    .then(response => {
                            setTrek(response.data);
                            reset(response.data);
                        }
                    )
            } catch (e) {
                console.error(e)
            }
        }

        fetchData();
    }, []);

    const loadCoodBook = () => {
        http.get(`web/codebook`)
            .then(response => {
                setCodebook(response.data);
            })
            .catch(reason => {
                console.error('Nastala chyba pri nacitani codebook: ', reason)
            })
    }

    const publish = (data: TrekEditDto) => {
        sendForm(data, TYPE_OF_STATUS.PUBLISHED);
    };

    const save = (data: TrekEditDto) => {
        sendForm(data, TYPE_OF_STATUS.TO_CHECK);
    };

    const sendForm = async (data: TrekEditDto, status: number) => {
        //convert data into correct format
        const sendData = JSON.parse(JSON.stringify(data));
        sendData.state_id = data.state_id?.id;
        sendData['status'] = status;
        if (data.danger != null) {
            sendData.danger = data.danger.map(danger => danger.id);
        }
        if (data.equipment != null) {
            sendData.equipment = data.equipment.map(equip => equip.id);
        }
        if (data.pois != null) {
            sendData.poi = data.pois.map(poi => poi.id);
        }

        // workaround for backend - removed start_point and destination_point
        delete sendData.start_point;
        delete sendData.destination_point;

        if (watchTrekType !== '2') {
            delete sendData.check_point;
        }

        try {
            const trekId = sendData?.trekId;
            await TrekService.updateTrek(trekId, sendData);

            // delete previous images
            await trek?.images.map(img => {
                const nameOfImage = img.substring(img.lastIndexOf('/') + 1, img.length);
                TrekService.deleteTrekImage(trekId, nameOfImage);
            });

            // delete previous cover image
            if (trek?.coverImage != undefined) {
                const nameOfImage = trek?.coverImage.substring(trek?.coverImage.lastIndexOf('/') + 1, trek?.coverImage.length);
                await TrekService.deleteCoverTrekImage(trekId, nameOfImage);
            }

            trekImages.length > 0 && trekId && trekImages.map(image => uploadTrekImagesFiles(trekId, image));
            coverImage.length > 0 && trekId && await uploadTrekCoverImagesFiles(trekId, coverImage[0]);
            trekGpx.length > 0 && trekId && await uploadTrekGpxFiles(trekId);
            handleOpenSuccessDialog();
        } catch (error) {
            console.error('update trek error = ', error);
            setOpenErrorMessage(true);
        }
    }

    const uploadTrekGpxFiles = (trekId: number) => {
        const formData = new FormData();
        formData.append('file', trekGpx[0])
        return TrekService.uploadTrekGpx(trekId, formData);
    }

    const uploadTrekImagesFiles = (trekId: number, image: File) => {
        const formData = new FormData();
        formData.append('file', image);
        return TrekService.uploadTrekImage(trekId, formData);
    }

    const uploadTrekCoverImagesFiles = (trekId: number, image: File) => {
        const formData = new FormData();
        formData.append('file', image);
        return TrekService.uploadTrekCoverImage(trekId, formData);
    }

    const handleCloseErrorMessage = () => {
        setOpenErrorMessage(false);
    }

    const handleCloseSuccessDialog = () => {
        setOpenSuccessDialog(false);
    }

    const handleOpenSuccessDialog = () => {
        setOpenSuccessDialog(true);
    }

    return (
        codebook && trek && Object.entries(trek).length > 0 && (
            <Container>
                <Paper>
                    <Box component="form" px={8} py={8}>
                        <Typography variant="h4"> {t('trek.edit.formTitle')}</Typography>
                        <Grid container spacing={3}>
                            <Grid item xs={12} sm={12}>
                                <FormInput
                                    control={control}
                                    name="translation.cz.name"
                                    label={t('trek.create.formField.name.cs')}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormInput
                                    control={control}
                                    name="translation.en.name"
                                    label={t('trek.create.formField.name.en')}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormInput
                                    control={control}
                                    name="translation.cz.title"
                                    label={t('trek.create.formField.title.cs')}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormInput
                                    control={control}
                                    name="translation.en.title"
                                    label={t('trek.create.formField.title.en')}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormInputMultiline
                                    control={control}
                                    name="translation.cz.description"
                                    label={t('trek.create.formField.description.cs')}
                                    maxRows={6}
                                    minRows={6}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormInputMultiline
                                    control={control}
                                    name="translation.en.description"
                                    label={t('trek.create.formField.description.en')}
                                    maxRows={6}
                                    minRows={6}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormInputMultiline
                                    control={control}
                                    name="note"
                                    label={t('trek.create.formField.note')}
                                    maxRows={6}
                                    minRows={1}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormInput
                                    control={control}
                                    name="duration"
                                    label={t('trek.create.formField.duration')}
                                    required={true}
                                    error={errors.duration}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormInput
                                    control={control}
                                    name="distance"
                                    label={t('trek.create.formField.distance')}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormInput
                                    control={control}
                                    name="elevation_difference"
                                    label={t('trek.create.formField.elevationDifference')}
                                />
                            </Grid>
                            <FormDifficulty control={control} setValue={setValue} menuItems={codebook?.difficulty}/>
                            <FormExperiences control={control} setValue={setValue}/>
                            <Grid item xs={12} sm={12}>
                                <FormInput
                                    control={control}
                                    name="highest_point"
                                    label={t('trek.create.formField.highestPoint')}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormInput
                                    control={control}
                                    name="link"
                                    label={t('trek.create.formField.link')}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormRadio name='trip_type_id'
                                           control={control}
                                           label={t('trek.create.formField.typeOfTrek')}
                                           menuItems={codebook?.tripType}
                                           setValue={setValue}
                                />
                            </Grid>
                            {watchTrekType === '2' && (
                                <>
                                    <Grid item xs={12} sm={12}>
                                        <FormInput
                                            control={control}
                                            name="check_point.lat"
                                            label={t('trek.create.formField.latitude')}
                                        />
                                    </Grid>
                                    <Grid item xs={12} sm={12}>
                                        <FormInput
                                            control={control}
                                            name="check_point.long"
                                            label={t('trek.create.formField.longitude')}
                                        />
                                    </Grid>
                                </>
                            )}
                            <Grid item xs={12} sm={12}>
                                <FormAutocomplete label={t('trek.create.formField.state')}
                                                  control={control}
                                                  name="state_id"
                                                  options={codebook?.state}
                                                  multiple={false}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormAutocomplete label={t('trek.create.formField.danger')}
                                                  control={control}
                                                  name="danger"
                                                  options={codebook?.danger}
                                                  multiple={true}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormAutocomplete label={t('trek.create.formField.equipment')}
                                                  control={control}
                                                  name="equipment"
                                                  options={codebook?.equipment}
                                                  multiple={true}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormAutocomplete label={t('trek.create.formField.poi')}
                                                  control={control}
                                                  name="pois"
                                                  options={codebook?.poi}
                                                  multiple={true}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormRadio name='camping_id'
                                           control={control}
                                           label={t('trek.create.formField.camping')}
                                           menuItems={codebook?.camping}
                                           setValue={setValue}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormRadio name='dog_friendly_id'
                                           control={control}
                                           label={t('trek.create.formField.dogFriendly')}
                                           menuItems={codebook?.dogFriendly}
                                           setValue={setValue}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormLabel
                                    component="legend">{t('trek.create.formField.coverImage', {
                                    imageCount: 1,
                                    imageMaxSize: MAX_FILE_SIZE_FOR_UPLOAD / 1000000
                                })}</FormLabel>
                                <DropzoneArea
                                    name={'coverImage'}
                                    filesLimit={1}
                                    acceptedFiles={['image/*']}
                                    onChange={(files) => {
                                        setCoverImage(files)
                                    }}
                                    maxFileSize={MAX_FILE_SIZE_FOR_UPLOAD}
                                    dropzoneText={t('material-ui-dropzone.dropzonetext')}
                                    initialFiles={[trek?.coverImage]}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormLabel
                                    component="legend">{t('trek.create.formField.images', {
                                    imageCount: MAX_NUMBER_FILES_FOR_UPLOAD,
                                    imageMaxSize: MAX_FILE_SIZE_FOR_UPLOAD / 1000000
                                })}</FormLabel>
                                <DropzoneArea
                                    name={'trekImages'}
                                    filesLimit={MAX_NUMBER_FILES_FOR_UPLOAD}
                                    acceptedFiles={['image/*']}
                                    onChange={(files) => {
                                        setTrekImages(files)
                                    }}
                                    maxFileSize={MAX_FILE_SIZE_FOR_UPLOAD}
                                    dropzoneText={t('material-ui-dropzone.dropzonetext')}
                                    initialFiles={trek?.images}
                                />
                            </Grid>
                            <Grid item xs={12} sm={12}>
                                <FormLabel component="legend">{t('trek.create.formField.gpx')}</FormLabel>
                                <DropzoneArea
                                    name={'trekGpx'}
                                    filesLimit={MAX_NUMBER_GPX_FILES_FOR_UPLOAD}
                                    // acceptedFiles={['application/gpx+xml']}
                                    onChange={(files) => {
                                        setTrekGpx(files)
                                    }}
                                    dropzoneText={t('material-ui-dropzone.dropzonetext')}
                                />
                            </Grid>
                            <Grid
                                container
                                direction="row"
                                justifyContent="center"
                            >
                                <Button variant={"contained"}
                                        onClick={handleSubmit(save)}>{t('trek.create.form.button.submit.save')}</Button>

                                <Permissions roles={[ROLES.admin, ROLES.approver]}>
                                    <Button variant={"contained"}
                                            onClick={handleSubmit(publish)}>{t('trek.create.form.button.submit.publish')}</Button>
                                </Permissions>
                            </Grid>
                        </Grid>

                        <Snackbar open={openErrorMessage} autoHideDuration={6000} onClose={handleCloseErrorMessage}>
                            <Alert onClose={handleCloseErrorMessage} severity="error">
                                {t('trek.edit.message.error')}
                            </Alert>
                        </Snackbar>

                        <AlertDialog open={openSuccessDialog}>
                            <DialogContent>
                                <DialogContentText id="alert-dialog-description">
                                    {t('trek.edit.message.success')}
                                </DialogContentText>
                            </DialogContent>
                            <DialogActions>
                                <Button onClick={handleCloseSuccessDialog} color="primary">
                                    {t('trek.edit.message.button.edit')}
                                </Button>
                                <Button onClick={() => history.push('/adventurerClient/treks')} color="primary">
                                    {t('trek.edit.message.button.goToTrekList')}
                                </Button>
                            </DialogActions>
                        </AlertDialog>
                    </Box>
                </Paper>
            </Container>
        )
    )
}

我认为文档非常清楚,您必须将表单值用作类型。

type FormValues = {
  'translation.cz.name': string
}

type FormInputProps = {
  control: Control<FormValues>
  // ...
}

尽管我希望不必告诉您,如果这个“translation.cz.name”字符串是可替换的,您的表单处理逻辑就会遇到麻烦。 name 属性不应暴露给最终用户,因此绝对没有必要对其进行本地化。

无论如何,如果您需要更多帮助,请提供 minimal reproducible example, preferably in codesandbox.io。您的问题中有很多代码,但实际上我们 运行 一个也做不到。