【问题标题】:React Typescript Custom Hooks: Property 'prop_name' does not exist on type '{}'React Typescript 自定义挂钩:类型“{}”上不存在属性“prop_name”
【发布时间】:2019-09-08 15:41:20
【问题描述】:

我正在尝试学习 React 和 React Hooks。我创建了位于另一个文件中的自定义钩子:CustomHook.ts。我在我的ContactForm.tsx 中使用它。我遇到的问题是在<input /> 标签中的每个value={inputs.property} 内。 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;

【问题讨论】:

    标签: reactjs typescript react-hooks


    【解决方案1】:

    问题在于 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
    }
    

    这里,我在箭头函数参数的前面添加了&lt;T&gt;。我还将回调键入为(value: T) =&gt; 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&lt;HTMLInputElement | HTMLTextAreaElement&gt;
    2. 您将函数传递到 setInputs 而不是新状态。
    3. 您在handleSubmit 中调用callback 时不带任何参数,但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;
    

    【讨论】:

    • 这是迄今为止最好的解决方案。它使我们的 Hook 通用,以便我们可以将它用于不同的形式。我觉得我对 Typescript 中的泛型了解多了一点。感谢您的意见!
    【解决方案2】:

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

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

    然后我们用这个初始化的对象设置状态。

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

    【讨论】:

    • 如果您定义[key: string]: string;,如果您尝试访问界面上不存在的字段,它不会警告您
    • @AsafAviv 好点。这首先会破坏使用 Typescript 的意义。
    • 您只需使用原始的interfaceconst [inputs, setInputs] = useState&lt;IContact&gt;({ Subject: 'me', Sender: 'you', ...rest }) 就可以了
    • @AsafAviv 更新了解决方案。代码有效,我们的 .tsx 渲染方法中有我们的类型。感谢您的意见!
    猜你喜欢
    • 1970-01-01
    • 2020-05-15
    • 1970-01-01
    • 2023-01-09
    • 2019-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多