пытаясь преобразовать приложение calc в typescript, какого типа должна быть эта функция?

#reactjs #typescript

Вопрос:

  • Я пытаюсь преобразовать это приложение-калькулятор в машинописный текст.
  • три функции (handleClick, handleEqual, handleClear) почему typescript НЕ кричит на меня, чтобы я указал что-то о типе их функций? Только один принимает аргумент, который является числом, я указал тип аргумента для val:число. Но действительно ли это все, что мне здесь нужно?
  • Есть ли сайт, который вы рекомендуете, который может помочь разобраться в типах? Кажется довольно запутанным.
       import React, { useState } from 'react';
      import * as math from 'mathjs';
      import '../styling/App.css';
      import { Button } from './button';
      import { Input } from './input'
      import { ClearButton } from './clear-button';
    
      const App = () => {
        const [input, setInput] = useState('');
    
        const handleClick = (val: number) => {
          setInput(input   val)
        }
    
        const handleEqual = () => {
          setInput(math.evaluate(input))
        }
    
        const handleClear = () => {
          setInput('');
        }
    
        return (
          <div className="App">
            <div className="calc-wrapper">
              <Input input={input}/>
              <div className="row">
                <Button handleClick={handleClick}>7</Button>
                <Button handleClick={handleClick}>8</Button>
                <Button handleClick={handleClick}>9</Button>
                <Button handleClick={handleClick}>/</Button>
              </div>
              <div className="row">
                <Button handleClick={handleClick}>4</Button>
                <Button handleClick={handleClick}>5</Button>
                <Button handleClick={handleClick}>6</Button>
                <Button handleClick={handleClick}>*</Button>
              </div>
              <div className="row">
                <Button handleClick={handleClick}>1</Button>
                <Button handleClick={handleClick}>2</Button>
                <Button handleClick={handleClick}>3</Button>
                <Button handleClick={handleClick}> </Button>
              </div>
              <div className="row">
                <Button handleClick={handleClick}>.</Button>
                <Button handleClick={handleClick}>0</Button>
                <Button handleClick={handleEqual}>=</Button>
                <Button handleClick={handleClick}> </Button>
              </div>
              <ClearButton handleClear={handleClear} />
            </div>
          </div>
        );
      }
    
      export default App;
     

Теперь я отредактировал, чтобы добавить свой компонент кнопки

     import React, {FC} from 'react';
    import '../styling/button.css';

    // create types for props
    type ButtonProps = {
      // HandleClick needs to a be a function type with a parameter that takes a ReactNode (since that's the type of props.children in this case, and you want to pass it into that function
      handleClick: (children: React.ReactNode) => void; // basically, clicking the reusable component I made, the arg is a ReactNode 
      // don't need to specify children prop, <ButtonProps> has it by default
    }

                    // set type for value
    const isOperator = (val: React.ReactNode) => {
      // isNaN() only takes a number parm, and only checks whether a number is set to specal value of 'NaN'
      return (typeof val === "number" amp;amp; !isNaN(val)) || val === "." || val === "=";
    }

    // generic type, FC
    export const Button: FC <ButtonProps> = ({children, handleClick}) => {
      return (
        <div className={`button-wrapper ${isOperator(children) ? null : "operator"}`}
            onClick={() => handleClick(children)}
        >
          {children}
        </div>
      )
    }
 

Комментарии:

1. Typescript будет жаловаться, если вы попытаетесь использовать e обратный вызов без ввода аргументов, но здесь вы не используете никаких аргументов. Необычно, что a handleClick используется несколько раз подобным образом, но все зависит от того, что ваш Button компонент делает с ним. Если Button устанавливается значение onClick , которое вызывает props.handleClick(props.children) , то это будет работать. На самом деле, у вас есть нечисловые кнопки, которые вы используете handleClick , поэтому вам нужно, чтобы это произошло (val: string) . Эта ошибка будет отображаться в вашем Button файле, а не здесь.

Ответ №1:

Да, это действительно все, что вам нужно в этом компоненте. Typescpript способен сделать множество выводов на основе использования. Например, вам не нужно объявлять, что ваше input состояние является a string , потому что оно предполагает это в зависимости от типа начального значения "" .

В этом коде есть проблемы с типами машинописи, но они будут отображаться в вашем Button компоненте. Button получает handleClick функцию, которая принимает a number в качестве своего аргумента. Это нормально? Как вы справляетесь с нажатием на * кнопку? Как вы вызываете функцию, которая может принимать только a number ?

Если наша кнопка действительно принимает реквизиты точно так, как мы их предоставляем, то это будет выглядеть примерно так:

 interface ButtonProps {
  handleClick: (val: number) => void;
  children: ReactNode; // this is the default so it's not actually needed here
}

const Button: React.FC<ButtonProps> = ({ handleClick, children }) => (
  <button onClick={() => handleClick(children)}>{children}</button>
);
 

Мы получим сообщение об ошибке при вызове handleClick(children) :

Аргумент типа ‘ReactNode’ не может быть присвоен параметру типа ‘number’.

Тип «неопределенный» не может быть присвоен типу «номер»

Хорошо, значит, мы не можем допустить ничего children подобного . Если мы добавим {children: number;} в ButtonProps , то это исправит ошибку, Button но создаст новую ошибку в App :

Компоненты «Кнопки» не принимают текст в качестве дочерних элементов.

Поэтому нам нужно прочитать children опору как string . Наша Button будет выглядеть так:

 interface ButtonProps {
  handleClick: (val: string) => void;
  children: string;
}

const Button: React.FC<ButtonProps> = ({ handleClick, children }) => (
  <button onClick={() => handleClick(children)}>{children}</button>
);
 

Теперь мы получаем другую ошибку в приложении:

Тип ‘(val: число) => пусто’ не может быть присвоен типу ‘(val: строка) =>> пусто’

Что приводит нас к полному кругу. Мы просто меняем тип handleClick функции, и все готово!

 const handleClick = (val: string) => {
  setInput(input   val);
};
 

Ссылка на песочницу Кода

Комментарии:

1. спасибо, это подробно и полезно. пара вопросов, которые помогут мне понять…. во-первых, я добавил/отредактировал, чтобы вы могли увидеть файл button.tsx. во-вторых, в настоящее время моя функция handleClick принимает дочерние элементы в качестве аргумента, потому что пользователь нажимает на (число или строку), который является повторно используемым компонентом. Вы думаете, я все равно должен изменить его на просто «строковый» тип?

2. В вашем текущем коде у вас должна быть ошибка в App каждом handleClick свойстве: Type '(val: number) => void' is not assignable to type '(children: ReactNode) => void поэтому вам нужно будет изменить свой обработчик на const handleClick = (val: React.ReactNode) => . Это действительно работает. setInput(input val) не дает вам никаких ошибок, потому что он всегда может привести val к строке, независимо от того, что это такое. Дети в вашем приложении только string для того, чтобы здесь все работало нормально, но у вас может быть какое-то странное поведение, если дети другие.

3.Если вы помещаете a Button внутрь a Button , вы не получаете никаких предупреждений или ошибок TS, но когда вы нажимаете на него, ваш input становится [object Object] (потому children что это элемент React object , который преобразуется в a string ). Что касается чисел, то когда вы автоматически устанавливаете детей через JSX, это всегда a string "7" . Чтобы получить его как a number , вам придется передать его как переменную. Либо с фигурными скобками <Button handleClick={handleClick}>{7}</Button> , либо установив его в качестве опоры <Button handleClick={handleClick} children={7}/> .

4. Поэтому я думаю string , что или string | number лучше, чем ReactNode потому, что он обеспечивает максимальную безопасность типов и предотвращает его использование таким образом, чтобы вызвать проблемы, как в ситуации с вложенными кнопками, описанной ранее. Вы хотите ограничить children опору кнопки, чтобы это было только то, с чем вы можете справиться.