#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
обратный вызов без ввода аргументов, но здесь вы не используете никаких аргументов. Необычно, что ahandleClick
используется несколько раз подобным образом, но все зависит от того, что ваш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
внутрь aButton
, вы не получаете никаких предупреждений или ошибок TS, но когда вы нажимаете на него, вашinput
становится[object Object]
(потомуchildren
что это элемент Reactobject
, который преобразуется в astring
). Что касается чисел, то когда вы автоматически устанавливаете детей через JSX, это всегда astring
"7"
. Чтобы получить его как anumber
, вам придется передать его как переменную. Либо с фигурными скобками<Button handleClick={handleClick}>{7}</Button>
, либо установив его в качестве опоры<Button handleClick={handleClick} children={7}/>
.4. Поэтому я думаю
string
, что илиstring | number
лучше, чемReactNode
потому, что он обеспечивает максимальную безопасность типов и предотвращает его использование таким образом, чтобы вызвать проблемы, как в ситуации с вложенными кнопками, описанной ранее. Вы хотите ограничитьchildren
опору кнопки, чтобы это было только то, с чем вы можете справиться.