将 React 组件转换为 TypeScript
在这篇文章中,我将带你了解如何修改一个简单的组件,从而开始使用 TypeScript。我正在测试的WordSearch 游戏
是用 CreateReactApp 构建的,所以我将按照他们的指南,学习如何在现有项目中启用 TypeScript。
首先,我们需要安装一些软件包,以便在项目中启用 TypeScript。
- TypeScript——启用实际 TS 编译器的软件包
- @types/node - 包含 Node.js 类型定义的包
- @types/react - 包含 React 类型定义的包
- @types/react-dom - 包含 React DOM 类型定义的包
- @types/jest - 包含 Jest 类型定义的包
CreateReactApp 的文档让我把这些作为运行时依赖项安装,但我认为它们应该放在开发依赖项下,所以我会把它们安装在这里 :)
我打算将 AddWord 组件转换为使用 TypeScript。该组件负责在单词搜索游戏的单词面板中添加新单词。
以下是原始代码,可帮助您理解:
import React, {Fragment, useEffect, useRef, useState} from 'react';
import Add from '@material-ui/icons/Add';
const AddWord = ({onWordAdd}) => {
const inputEl = useRef(null);
const [newWord, setNewWord] = useState('');
const [disable, setDisable] = useState(true);
useEffect(() => {
// A word is valid if it has more than a single char and has no spaces
const isInvalidWord = newWord.length < 2 || /\s/.test(newWord);
setDisable(isInvalidWord);
}, [newWord]);
function onAddClicked() {
onWordAdd && onWordAdd(inputEl.current.value);
setNewWord('');
}
function onChange(e) {
const value = e.target.value;
setNewWord(value);
}
return (
<Fragment>
<input
type="text"
name="new"
required
pattern="[Bb]anana|[Cc]herry"
ref={inputEl}
placeholder="Add word..."
value={newWord}
onChange={onChange}
/>
<button onClick={onAddClicked} disabled={disable}>
<Add></Add>
</button>
</Fragment>
);
};
export default AddWord;
首先,我将文件扩展名更改为 .tsx - src/components/AddWord.js > src/components/AddWord.tsx
启动应用时,我遇到了第一个类型错误:
TypeScript error in
word-search-react-game/src/components/AddWord.tsx(4,19):
Binding element 'onWordAdd' implicitly has an 'any' type. TS7031
2 | import Add from '@material-ui/icons/Add';
3 |
> 4 | const AddWord = ({onWordAdd}) => {
| ^
让我们来解决这个问题。
问题在于组件没有声明它允许接收的 props 类型。我看到了两种解决方法。一种是使用 React.FC,另一种是将这个函数组件视为一个函数,因此将其类型定义为一个没有 React 专用类型定义的函数。阅读了Kent C. Dodds 关于此问题的文章,以及这篇StackOverflow 上的详细回答中关于使用 React.FC 的注意事项后,我决定采用传统的函数类型定义方式。
好的,所以我们需要定义 props 的类型。我倾向于使用接口而不是类型,因为我有面向对象编程的背景,我知道使用接口要灵活得多。
这个组件接收一个 prop,它是一个回调函数,带有一个字符串参数,并且没有返回值(我喜欢用“I”前缀来标记我的接口)。
我们的 props 接口如下所示:
interface IAddWordProps {
onWordAdd: (value: string) => void;
}
用法如下:
const AddWord = ({onWordAdd}: IAddWordProps) => {
...
这个问题解决了,接下来处理下一个错误:
TypeScript error in
word-search-react-game/src/components/AddWord.tsx(20,32):
Object is possibly 'null'. TS2531
18 |
19 | function onAddClicked() {
> 20 | onWordAdd && onWordAdd(inputEl.current.value);
|
没错,`inputEl` 可能为空,那么我们该如何处理呢?
一般来说,我不喜欢抑制错误和警告。既然决定使用某个工具,就没必要在“禁用规则”配置上过于谨慎,所以我们来认真解决这个问题。
首先,我想给 `inputEl` 的引用设置一个类型,它可以是 `null`,也可以是一个具有泛型类型的 `React.RefObject` 接口。由于我们处理的是一个输入元素,所以它应该是 `HTMLInputElement`。现在 `inputEl` 的类型定义如下:
const inputEl: RefObject<HTMLInputElement> | null = useRef(null);
然而,这并没有解决我们的主要问题。让我们继续。
解决这个问题的一个方法是使用可选链,这意味着我们需要预先了解并准备好代码,以便优雅地处理空指针。现在的处理程序如下所示:
function onAddClicked() {
onWordAdd && onWordAdd(inputEl?.current?.value);
但是这样做就破坏了我们之前定义的 props 接口,因为它期望接收一个字符串,而现在它也可以接收 undefined,所以让我们修复接口以支持这一点:
interface IAddWordProps {
onWordAdd: (value: string | undefined) => void;
}
搞定了。接下来处理下一个错误。
TypeScript error in
word-search-react-game/src/components/AddWord.tsx(24,23):
Parameter 'e' implicitly has an 'any' type. TS7006
22 | }
23 |
> 24 | function onChange(e) {
| ^
25 | const value = e.target.value;
解决方法很简单——我给 e 添加了 ChangeEvent 类型。现在它看起来像这样:
function onChange(e: ChangeEvent<HTMLInputElement>) {
const value = e.target.value;
setNewWord(value);
}
这不是“React 类型”,而且就目前而言,我看不出在不需要的时候使用 React 类型有什么理由(如果您知道这样的理由,请在评论中分享)。
搞定!应用程序已恢复运行 :)
下面您可以找到修改后的代码(添加了一些额外的非关键类型),您可以将其与本文开头的原始代码进行比较。
更新——
在收到下方评论区(以及Reddit上)的一些宝贵反馈后,我已据此对代码进行了一些修改。谢谢大家。
import React, {ChangeEventHandler, MouseEventHandler, RefObject, useRef, useState} from 'react';
import Add from '@material-ui/icons/Add';
interface IAddWordProps {
onWordAdd?: (value: string | undefined) => void;
}
const AddWord = ({onWordAdd}: IAddWordProps) => {
const inputEl: RefObject<HTMLInputElement> | null = useRef(null);
const [newWord, setNewWord] = useState('');
const [disable, setDisable] = useState(true);
const onAddClicked: MouseEventHandler<HTMLButtonElement> = () => {
onWordAdd?.(newWord);
setNewWord('');
};
const onChange: ChangeEventHandler<HTMLInputElement> = ({currentTarget: {value}}) => {
setNewWord(value);
// A word is valid if it has more than a single char and has no spaces
const isInvalidWord: boolean = value.length < 2 || /\s/.test(value);
setDisable(isInvalidWord);
};
return (
<>
<input
type="text"
name="new"
required
pattern="[Bb]anana|[Cc]herry"
ref={inputEl}
placeholder="Add word..."
value={newWord}
onChange={onChange}
/>
<button onClick={onAddClicked} disabled={disable}>
<Add />
</button>
</>
);
};
export default AddWord;
谢谢 :)
嘿!如果你喜欢刚才读到的内容,也别忘了在推特上关注我哦 :)关注 @mattibarzeev 🍻
文章来源:https://dev.to/mbarzeev/converting-a-react-component-to-typescript-15cl
