React Typescript自定义挂钩:类型“ {}”上不存在属性“ prop_name”

时间:2019-09-08 15:41:20

标签: reactjs typescript react-hooks

我正在尝试学习React和React Hooks。我创建了生活在另一个文件CustomHook.ts中的自定义钩子。我在ContactForm.tsx中使用它。我遇到的问题是value={inputs.property}标记中的每个<input />内部。 Typescript无法解析每个inputs._propertyName的类型。

我已经定义了一个接口IContact,该接口定义了我想使用的类型。我目前不使用此界面,因为我不知道将其放置在哪里。

任何帮助将不胜感激!

错误:

ERROR in /jefthimi/src/components/Contact/ContactForm.tsx(35,31)
      TS2339: Property 'subject' does not exist on type '{}'.
ERROR in /jefthimi/src/components/Contact/ContactForm.tsx(46,31)
      TS2339: Property 'email' does not exist on type '{}'.
ERROR in /jefthimi/src/components/Contact/ContactForm.tsx(57,31)
      TS2339: Property 'name' does not exist on type '{}'.
ERROR in /jefthimi/src/components/Contact/ContactForm.tsx(68,31)
      TS2339: Property 'comments' does not exist on type '{}'.

ContactForm.tsx

import React from 'react';
import './ContactForm.scss';
import useContactForm from './CustomHook';

interface IContact {
  subject: string;
  email: string;
  name: string;
  comments: string;
}

const message = (inputs: any) => {
  alert(`Message Sent!
  Subject: ${inputs.subject}
  Sender: ${inputs.email}
  Name: ${inputs.name}
  Comments: ${inputs.comments}`);
};

const { inputs, handleInputChange, handleSubmit } = useContactForm(message);

export default class ContactForm extends React.Component {
  render() {
    return (
      <div className="contactForm_container">
        <div className="contactForm_inner">
          <form onSubmit={handleSubmit}>
            <div className="input-group">
              <label htmlFor="subject">Subject</label>
              <input
                id="subject"
                name="subject"
                type="text"
                onChange={handleInputChange}
                value={inputs.subject}
                required
              />
            </div>
            <div className="input-group">
              <label htmlFor="email">Your Email</label>
              <input
                id="email"
                name="email"
                type="text"
                onChange={handleInputChange}
                value={inputs.email}
                required
              />
            </div>
            <div className="input-group">
              <label htmlFor="name">Your Name</label>
              <input
                id="name"
                name="name"
                type="text"
                onChange={handleInputChange}
                value={inputs.name}
                required
              />
            </div>
            <div className="input-group">
              <label htmlFor="comments">Comments</label>
              <textarea
                name="comments"
                id="comments"
                rows={10}
                onChange={handleInputChange}
                value={inputs.comments}
                required
              />
            </div>
            <div className="controls">
              <button type="submit">Send Message</button>
            </div>
          </form>
        </div>
      </div>
    );
  }
}

CustomHook.ts

import React, { useState } from 'react';

/*
This is a Custom React Hook that handles our form submission
*/

const useContactForm = (callback) => {
  const [inputs, setInputs] = useState({});

  const handleSubmit = (event) => {
    if (event) {
      event.preventDefault();
    }
    callback();
  };
  const handleInputChange = (event) => {
    event.persist();
    setInputs((inputs) => ({
      ...inputs,
      [event.target.name]: event.target.value
    }));
  };
  return {
    handleSubmit,
    handleInputChange,
    inputs
  };
};

export default useContactForm;

2 个答案:

答案 0 :(得分:3)

问题在于CustomHook.ts中inputs的初始状态为{}。然后,您尝试渲染inputs.subjectinputs.emailinputs.nameinputs.comments。这些属性在空对象{}上不存在,这是错误消息告诉您的内容。

让我们从一些基础知识开始。您有IContact,但是您不知道该怎么做。您应该使用它在需要该签名的任何地方键入数据。首先,使用message回调。

const message = (inputs: IContact) => {
    alert(`Message Sent!
    Subject: ${inputs.subject}
    Sender: ${inputs.email}
    Name: ${inputs.name}
    Comments: ${inputs.comments}`);
};

useContactForm钩怎么样?好吧,可以,但是我不建议您这样做。当我查看该钩子时,看不到其中引用IContact的任何内容。在这种情况下,钩子会更泛型

如果只有一种添加类型的方式更泛型 ...

嗯,有。我们想要做的就是能够传递一种类型用作其他对象的类型。

const useContactForm = <T>(callback: (state: T) => void) => {
    //...code
}

在这里,我在箭头函数参数的前面添加了<T>。我也将回调键入为(value: T) => void,以指示回调应接受T类型的参数作为参数,并且不返回任何内容。

现在,我们需要输入useState函数。

const [inputs, setInputs] = useState<T>({});

嗯。 {}T不匹配。我们需要类型为T的初始状态。由于T被传入,因此我们的初始状态也必须保持不变。

const useContactForm = <T>(callback: (state: T) => void, initialState: T) => {
    const [inputs, setInputs] = useState<T>(initialState);
    // ...code
}

并将其传递进来。

    const {inputs, handleInputChange, handleSubmit} = useContactForm(message, {
        subject: '',
        email: '',
        name: '',
        comments: '',
    });

好的。这基本上就是需要处理的方式。 但是,代码以及TypeScript和Hooks的使用还有其他一些问题。

  1. 您需要输入更多内容。事件处理程序中的事件参数应具有类型。例如ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
  2. 您要将函数传递到setInputs中,而不是传递新状态。
  3. 您在callback中调用handleSubmit,但不带参数,但是message回调显然正在寻找IContact
  4. 您正在尝试在类组件而不是功能组件中使用钩子。您需要将类组件更改为一个函数,然后将对您自定义钩子的调用放在函数内部。

这是一些工作代码。

ContactForm.tsx

import React from 'react';
import './ContactForm.scss';
import useContactForm from './CustomHook';

interface IContact {
    subject: string;
    email: string;
    name: string;
    comments: string;
}

const message = (inputs: IContact) => {
    alert(`Message Sent!
  Subject: ${inputs.subject}
  Sender: ${inputs.email}
  Name: ${inputs.name}
  Comments: ${inputs.comments}`);
};

export default () => {
    const {inputs, handleInputChange, handleSubmit} = useContactForm(message, {
        subject: '',
        email: '',
        name: '',
        comments: '',
    });

    return (
        <div className="contactForm_container">
            <div className="contactForm_inner">
                <form onSubmit={handleSubmit}>
                    <div className="input-group">
                        <label htmlFor="subject">Subject</label>
                        <input
                            id="subject"
                            name="subject"
                            type="text"
                            onChange={handleInputChange}
                            value={inputs.subject}
                            required
                        />
                    </div>
                    <div className="input-group">
                        <label htmlFor="email">Your Email</label>
                        <input
                            id="email"
                            name="email"
                            type="text"
                            onChange={handleInputChange}
                            value={inputs.email}
                            required
                        />
                    </div>
                    <div className="input-group">
                        <label htmlFor="name">Your Name</label>
                        <input
                            id="name"
                            name="name"
                            type="text"
                            onChange={handleInputChange}
                            value={inputs.name}
                            required
                        />
                    </div>
                    <div className="input-group">
                        <label htmlFor="comments">Comments</label>
                        <textarea
                            name="comments"
                            id="comments"
                            rows={10}
                            onChange={handleInputChange}
                            value={inputs.comments}
                            required
                        />
                    </div>
                    <div className="controls">
                        <button type="submit">Send Message</button>
                    </div>
                </form>
            </div>
        </div>
    );
};

CustomHook.ts

import React, {useState, FormEvent, ChangeEvent} from 'react';

/*
This is a Custom React Hook that handles our form submission
*/

const useContactForm = <T>(callback: (state: T) => void, initialState: T) => {
    const [inputs, setInputs] = useState<T>(initialState);

    const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
        if (event) {
            event.preventDefault();
        }
        callback(inputs);
    };
    const handleInputChange = (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
        event.persist();
        setInputs({
            ...inputs,
            [event.target.name]: event.target.value,
        });
    };
    return {
        handleSubmit,
        handleInputChange,
        inputs,
    };
};

export default useContactForm;

答案 1 :(得分:1)

建议的解决方案是定义一个包含我们的Message属性的接口,然后创建一个具有初始化字段的默认Message对象。

interface IMessage {
  subject: string;
  email: string;
  name: string;
  comments: string;
}

const message: IMessage = {
  subject: '',
  email: '',
  name: '',
  comments: ''
};

然后使用此初始化的对象设置状态。

const [inputs, setInputs] = useState(message);