如何在React中使用TypeScript
在这篇文章中,你将学习如何使用TypeScript和React通过本文的阅读,你将对如何使用TypeScript编写React代码有一个扎实的理解想要观看本教程的视频版本吗?你可以在下方查看视频:目录 *
在这篇文章中,您将学习如何在React中使用TypeScript。
到最后,您将对如何使用TypeScript编写React代码有了扎实的理解。
想观看本教程的视频版本吗?您可以在下面查看视频:
目录
- 先决条件
- 入门
- React和TypeScript基础知识
- 定义Prop类型的三种方法
- 如何创建一个随机用户列表应用程序
- 如何将用户列表存储在状态中
- 如何在 UI 上显示用户
- 如何创建一个独立的用户组件
- 如何为类型声明创建一个单独的文件
- 如何显示加载指示器
- 如何在按钮点击时加载用户
- 如何处理更改事件
- 感谢阅读
先决条件
要跟随本教程,您需要以下准备:
- 基本了解如何使用React
- 基本了解TypeScript代码的编写
入门
要开始使用TypeScript,您首先需要在您的机器上安装TypeScript。您可以通过在终端或命令提示符中执行npm install -g typescript
来完成此操作。
现在,我们将使用TypeScript创建一个Vite项目。
npm create vite
执行后,您将被问一些问题。
对于项目名称,请输入react-typescript-demo
。
对于框架,请选择React
,对于变体,请选择TypeScript
。
创建项目后,将其在VS Code中打开,并从终端执行以下命令:
cd react-typescript-demonpm install
现在,让我们进行一些代码清理。
删除src/App.css
文件,并用以下内容替换src/App.tsx
文件的内容:
const App = () => { return <div>App</div>;};export default App;
保存文件后,您可能会看到文件中的红色下划线,如下所示:
如果遇到该错误,只需按下Cmd + Shift + P(Mac)
或Ctrl + Shift + P(Windows/Linux)
打开VS Code命令面板,然后在搜索框中输入TypeScript
文本,并选择选项TypeScript: Select TypeScript Version...
:
一旦选择,你将看到如下图所示的在VS Code版本和工作区版本之间进行选择的选项:
从这些选项中,你需要选择Use Workspace Version
选项。一旦选择了该选项,App.tsx
文件中的错误就会消失。
现在,打开src/index.css
文件,并将其内容替换为以下代码:
:root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%;}
现在,通过执行npm run dev
命令来启动应用程序。
现在,点击显示的URL并访问应用程序。你将在浏览器中看到以下初始界面,其中显示了文本App
。
React和TypeScript基础知识
当使用React和TypeScript时,你需要了解的第一件事是文件扩展名。
每个React + TypeScript文件都需要有一个.tsx
扩展名。
如果文件不包含任何JSX特定的代码,那么你可以使用.ts
扩展名代替.tsx
扩展名。
要在React中使用TypeScript创建组件,你可以使用react
包中的FC
类型,并在组件名称之后使用它。
因此,打开src/App.tsx
文件,并用以下内容替换:
import { FC } from 'react';const App: FC = () => { return <div>App</div>;};export default App;
现在,让我们给这个App
组件传递一些props。
在src/main.tsx
中,将一个title
prop传递给App
组件,如下所示:
import React from 'react';import ReactDOM from 'react-dom/client';import App from './App.tsx';import './index.css';ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App title='TypeScript Demo' /> </React.StrictMode>);
然而,添加了title
prop之后,我们现在有一个TypeScript错误,如下所示:
三种定义Prop类型的方式
我们可以用三种不同的方式来修复上述的TypeScript错误。
- 使用接口声明类型
错误出现的原因是我们将title
prop添加为App
组件的必需prop-所以我们需要在App
组件内部提到这一点。
打开src/App.tsx
文件,并使用以下代码替换其内容:
import { FC } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = () => { return <div>App</div>;};export default App;
如上所示,我们添加了额外的接口AppProps
来指定组件接受的props。我们还在角括号中的FC
后面使用了AppProps
接口。
开始界面名称以大写字母开头,比如我们的情况是 AppProps
,这是一种很好的做法。
现在,通过这个变化,TypeScript错误已经消失,如下所示:
这是我们指定特定组件接受哪些属性的方式。
- 使用
type
进行类型声明
我们也可以使用type
关键字来声明属性类型。
所以打开 App.tsx
文件,将下面的代码进行更改:
import { FC } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = () => { return <div>App</div>;};export default App;
更改为以下代码:
import { FC } from 'react';type AppProps = { title: string;};const App: FC<AppProps> = () => { return <div>App</div>;};export default App;
在这里,不再使用interface
声明,而是使用了type
声明。现在代码将无错误地运行。
你可以根据自己的喜好选择使用哪种方法。我总是喜欢使用接口来声明组件类型。
- 使用行内类型声明
第三种声明类型的方式是通过定义行内类型,如下所示:
const App = ({ title }: { title: string }) => { return <div>App</div>;};export default App;
如上所示,我们已经移除了FC
的使用,因为它不再需要,而且在解构title
属性时,我们定义了其类型。
所以在这三种方式中,你可以选择任何一种。我总是比较喜欢使用带有FC
的接口。这样,如果以后想要添加更多属性,代码也不会变得复杂(如果你使用行内类型的话,代码就会变得复杂)。
现在,让我们使用title
属性,并在UI上显示出来。
使用以下代码替换App.tsx
文件的内容:
import { FC } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = ({ title }) => { return <h1>{title}</h1>;};export default App;
正如你所见,我们在FC
中使用一个接口,然后将title
属性解构并在屏幕上显示出来。
现在,打开src/index.css
文件,并在其中添加以下CSS:
h1 { text-align: center;}
如果你在浏览器中查看应用程序,你将看到正确显示了TypeScript Demo
文本的标题。
如何创建一个随机用户列表的应用程序
现在你已经对如何声明组件属性有了基本的了解,让我们创建一个简单的随机用户列表应用程序,它将在屏幕上显示一个含有10个随机用户的列表。
为此,我们将使用Random User Generator API。
我们将使用以下API URL:
https://randomuser.me/api/?results=10
让我们首先安装Axios npm库,以便我们可以使用它进行API调用。
执行以下命令来安装Axios库:
npm install axios
安装完成后,通过执行 npm run dev
命令重新启动应用程序。
现在,用以下内容替换 App.tsx
文件的内容:
import axios from 'axios';import { FC, useEffect } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = ({ title }) => { useEffect(() => { const getUsers = async () => { try { const { data } = await axios.get( 'https://randomuser.me/api/?results=10' ); console.log(data); } catch (error) { console.log(error); } }; getUsers(); }, []); return <h1>{title}</h1>;};export default App;
如上所示,我们在上面添加了一个 useEffect
钩子,用于调用 API 获取用户列表。
现在,如果在浏览器中打开控制台,你将能够在控制台中看到 API 响应的结果。
正如你所见,我们正确地获取了 10 个随机用户的列表,实际用户列表存在响应的 results
属性中。
如何将用户列表存储在状态中
现在,让我们将这些用户存储在状态中,以便在屏幕上显示它们。
在 App
组件内部,声明一个新的状态,初始值为空数组:
const [users, setUsers ] = useState([]);
并在 API 调用后,在 useEffect
钩子中调用 setUsers
函数将用户存储起来。
因此,你的 App
组件现在会像这样:
import axios from 'axios';import { FC, useEffect, useState } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = ({ title }) => { const [users, setUsers] = useState([]); useEffect(() => { const getUsers = async () => { try { const { data } = await axios.get( 'https://randomuser.me/api/?results=10' ); console.log(data); setUsers(data.results); } catch (error) { console.log(error); } }; getUsers(); }, []); return <h1>{title}</h1>;};export default App;
如你所见,这里我们使用 data.results
的值调用了 setUsers
函数。
如何在界面上显示用户
现在,我们来显示每个用户的姓名和电子邮件。
如果你检查控制台输出,你会发现每个对象都有一个包含用户名和姓氏的 name
属性。因此,我们可以将它们组合在一起以显示完整的姓名。
此外,对于每个用户对象,我们有一个直接的 email
属性,可以用来显示电子邮件。
因此,请用以下内容替换 App.tsx
文件的内容:
import axios from 'axios';import { FC, useEffect, useState } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = ({ title }) => { const [users, setUsers] = useState([]); useEffect(() => { const getUsers = async () => { try { const { data } = await axios.get( 'https://randomuser.me/api/?results=10' ); console.log(data); setUsers(data.results); } catch (error) { console.log(error); } }; getUsers(); }, []); return ( <div> <h1>{title}</h1> <ul> {users.map(({ login, name, email }) => { return ( <li key={login.uuid}> <div> 姓名:{name.first} {name.last} </div> <div>电子邮件:{email}</div> <hr /> </li> ); })} </ul> </div> );};export default App;
正如你所看到的,我们正在使用array map方法循环遍历users
数组,并且我们正在使用对象解构来解构单个user
对象的login
、name
和email
属性。此外,我们将用户的姓名和电子邮件显示为无序列表。
但是,你会在文件中看到一些TypeScript错误,如下所示:
这是因为,默认情况下,TypeScript 假定users
数组的类型为never[]
,所以它无法确定users
数组包含哪些属性。
这意味着我们需要指定我们正在使用的所有属性以及它们的类型。
所以现在,在AppProps
接口之后声明一个新的接口,像这样:
interface Users { name: { first: string; last: string; }; login: { uuid: string; }; email: string;}
在这里,我们指定每个单独的user
将是一个具有name
、login
和email
属性的对象。我们还指定了每个属性的数据类型。
如你所见,从 API 返回的每个user
对象还有很多其他属性,如phone
、location
等。但是我们只需要指定在代码中使用的那些属性。
现在,将useState
中的users
数组声明从这个:
const [users, setUsers] = useState([]);
更改为这个:
const [users, setUsers] = useState<Users[]>([]);
在这里,我们指定users
是类型为Users
的对象数组,这是我们声明的接口。
现在,如果你检查App.tsx
文件,你将看不到 TypeScript 错误。
然后,你将能够在屏幕上看到显示的10个随机用户列表:
正如之前所见,我们声明了像这样的Users
接口:
interface Users { name: { first: string; last: string; }; login: { uuid: string; }; email: string;}
但是当你有嵌套属性时,你会看到它写成这样:
interface Name { first: string; last: string;}interface Login { uuid: string;}interface Users { name: Name; login: Login; email: string;}
为每个嵌套属性声明单独的接口的优点是,如果你要在任何其他文件中使用相同的结构,你可以导出上述任何一个接口并在其他文件中重复使用它们(而不是重新声明相同的接口)。
因此,让我们将所有上述接口作为命名导出导出。代码将如下所示:
导出接口名称{ first: string; last: string;}导出接口登录{ uuid: string;}导出接口用户{ 名称: 名称; 登录: 登录; 电子邮件: string;}
就像我之前说的,你也可以在这里使用类型声明,而不是使用接口,这样它看起来像这样:
类型名称 = { first: string; last: string;};类型登录 = { uuid: string;};类型用户 = { 名称: 名称; 登录: 登录; 电子邮件: string;};
如何创建单独的用户组件
当我们使用map
方法在屏幕上显示东西时,通常会将显示部分分离到不同的组件中。这样做使得测试变得容易,而且还会使你的组件代码变得更短。
在src
文件夹中创建一个components
文件夹,并在其中创建一个User.tsx
文件。然后在该文件中添加以下内容:
const 用户=({ 登录,名称,电子邮件 })=>{ 返回( <li键={登录.uuid}> <div> 姓名:{名称.first} {名称.last} </div> <div>电子邮件:{电子邮件}</div> <hr /> </li> );}导出默认用户;
如果保存文件,你会再次看到TypeScript错误。
所以我们需要指定User
组件将接收哪些props。我们还需要指定每个props的数据类型。
所以更新后的User.tsx
文件将如下所示:
导入{ FC } 从react;导入{ 登录,名称 } 从'../App';接口用户道具{ 登录:登录; 名称:名称; 电子邮件:string;}常规人:FC<用户道具>=({ 登录,名称,电子邮件 })=>{ 返回( <li键={登录.uuid}> <div> 姓名:{名称.first} {名称.last} </div> <div>电子邮件:{电子邮件}</div> <hr /> </li> );}导出默认用户;
如上所示,我们在上面声明了一个UserProps
接口,并在User
组件中使用FC
进行指定。
另外,请注意我们没有声明name
和login
属性的数据类型。相反,我们使用了来自App.tsx
文件的导出类型:
导入{ 登录,名称 } 从'../App';
这就是为什么最好为每个嵌套属性声明单独的类型,这样我们可以在其他地方重用它们。
现在,我们可以在App.tsx
文件中使用这个User
组件。
所以将下面的代码更改为:
{users.map(({ 登录, 名称, 电子邮件 }) => { 返回( <li键={登录.uuid}> <div> 姓名:{名称.first} {名称.last} </div> <div>电子邮件:{电子邮件}</div> <hr /> </li> );})}
改成这段代码:
{users.map(({ 登录, 名称, 电子邮件 }) => { 返回 <用户键={登录.uuid} 名称={名称} 电子邮件={电子邮件} />;})}
如你所知,当使用数组map
方法时,我们需要为父元素提供key
,在我们的例子中是User
。所以我们在使用User
组件时添加了key
属性。
这意味着我们不需要在 User
组件内部使用 key,所以我们可以删除 User
组件中的 key 和 login 属性。
因此,更新后的 User
组件将如下所示:
import { FC } from 'react';import { Name } from '../App';interface UserProps { name: Name; email: string;}const User: FC<UserProps> = ({ name, email }) => { return ( <li> <div> Name: {name.first} {name.last} </div> <div>Email: {email}</div> <hr /> </li> );};export default User;
如您所见,我们已从接口中删除了 login 属性,并进行了解构。应用程序仍然与之前一样正常工作,没有任何问题,如下所示。
如何为类型声明创建单独的文件
请注意,App.tsx
文件因为接口声明而变得很大。通常会有一个单独的文件专门用于声明类型。
因此,在 src
文件夹中创建一个 App.types.ts
文件,并将所有类型声明从 App
组件移动到 App.types.ts
文件中:
export interface AppProps { title: string;}export interface Name { first: string; last: string;}export interface Login { uuid: string;}export interface Users { name: Name; login: Login; email: string;}
请注意,上述代码还导出了 AppProps
组件。
现在,更新 App.tsx
文件以使用这些类型,如下所示:
import axios from 'axios';import { FC, useEffect, useState } from 'react';import { AppProps, Users } from './App.types';import User from './components/User';const App: FC<AppProps> = ({ title }) => { const [users, setUsers] = useState<Users[]>([]); // ...};export default App;
如您所见,我们从 App.types
文件中导入 AppProps
和 Users
:
import { AppProps, Users } from './App.types';
现在,您的 User.tsx
文件将如下所示:
import { FC } from 'react';import { Name } from '../App.types';interface UserProps { name: Name; email: string;}const User: FC<UserProps> = ({ name, email }) => { return ( <li> <div> 姓名:{name.first} {name.last} </div> <div>电子邮件:{email}</div> <hr /> </li> );};export default User;
如您所见,我们从 App.types
文件中导入了 Name
。
import { Name } from '../App.types';
如何显示加载指示器
每当您进行 API 调用来显示内容时,始终最好在 API 调用进行中显示一些加载指示器。
因此,让我们在 App
组件中添加一个新的 isLoading
状态:
const [isLoading, setIsLoading] = useState(false);
如您所见,我们在声明状态时没有指定任何数据类型,像这样:
const [isLoading, setIsLoading] = useState<boolean>(false);
这是因为,当我们分配任何初始值(在我们的例子中是false
),TypeScript会自动推断我们将要存储的数据类型 – 在我们的例子中是boolean
。
当我们声明users
状态时,仅通过空数组的初始值[]
无法明确存储的内容。所以我们需要像这样指定它的类型:
const [users, setUsers] = useState<Users[]>([]);
现在,将useEffect
的代码改为以下代码:
useEffect(() => { const getUsers = async () => { try { setIsLoading(true); const { data } = await axios.get( 'https://randomuser.me/api/?results=10' ); console.log(data); setUsers(data.results); } catch (error) { console.log(error); } finally { setIsLoading(false); } }; getUsers();}, []);
在这里,我们在API调用之前使用setIsLoading
赋值为true
。在finally
块中,我们将其设置回false
。
无论成功与否,finally
块中的代码都将执行。所以无论API调用成功还是失败,我们都需要隐藏加载消息,而我们使用finally
块来实现。
现在,我们可以使用isLoading
状态值在屏幕上显示加载消息。
在h1
标签之后、ul
标签之前,添加以下代码:
{isLoading && <p>加载中...</p>}
现在,如果您检查应用程序,您将能够在加载用户列表时看到加载消息。
这样用户体验更好。
但是,如果您仔细观察显示的用户,您会发现用户在加载后得到了更改。
这是因为我们正在使用React版本18(您可以从package.json
文件中验证)和src/main.tsx
文件中的React.StrictMode
。
在React的18版本中,当我们使用React.StrictMode
时,即使没有指定依赖项,每个useEffect
钩子也会执行两次。
这只会在开发环境中发生,而在部署应用程序时不会发生。
因此,API调用被执行了两次。由于随机用户API每次调用API时都会返回一组新的随机用户,我们使用useEffect
钩子中的setUsers
调用将不同的一组用户设置到users
数组中。
这就是为什么我们在不刷新页面的情况下看到用户的更改。
如果您不希望在开发过程中出现这种行为,可以从main.tsx
文件中删除React.StrictMode
。
所以将以下代码进行修改:
import React from 'react';import ReactDOM from 'react-dom/client';import App from './App.tsx';import './index.css';ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App title='TypeScript Demo' /> </React.StrictMode>);
修改为以下代码:
import ReactDOM from 'react-dom/client';import App from './App.tsx';import './index.css';ReactDOM.createRoot(document.getElementById('root')!).render( <App title='TypeScript Demo' />);
现在,如果您检查应用程序,您将看到在加载后用户列表不会改变。
如何在按下按钮时加载用户
现在,不再在页面加载时进行API调用,我们将添加一个”显示用户”按钮,并在点击该按钮时进行API调用。
所以在h1
标签之后,添加一个新的按钮,如下所示:
<button onClick={handleClick}>显示用户</button>
现在,在App
组件中添加handleClick
方法,并将所有代码从getUsers
函数移动到handleClick
方法中:
const handleClick = async () => { try { setIsLoading(true); const { data } = await axios.get('https://randomuser.me/api/?results=10'); console.log(data); setUsers(data.results); } catch (error) { console.log(error); } finally { setIsLoading(false); }};
现在,您可以删除或注释掉useEffect
钩子,因为它不再需要。
您更新后的App.tsx
文件现在应如下所示:
import axios from 'axios';import { FC, useState } from 'react';import { AppProps, Users } from './App.types';import User from './components/User';const App: FC<AppProps> = ({ title }) => { const [users, setUsers] = useState<Users[]>([]); const [isLoading, setIsLoading] = useState<boolean>(false); const handleClick = async () => { try { setIsLoading(true); const { data } = await axios.get('https://randomuser.me/api/?results=10'); console.log(data); setUsers(data.results); } catch (error) { console.log(error); } finally { setIsLoading(false); } }; return ( <div> <h1>{title}</h1> <button onClick={handleClick}>显示用户 </button> {isLoading && <p>加载中...</p>} <ul> {users.map(({ login, name, email }) => { return <User key={login.uuid} name={name} email={email} />; })} </ul> </div> );};export default App;
现在,如果您检查应用程序,您将只在点击”显示用户”按钮时看到用户被加载。我们还会看到加载消息。
如何处理变更事件
现在,让我们添加一个输入字段。当我们在该输入字段中键入任何内容时,我们将在该输入字段下方显示输入的文本。
在按钮后添加一个输入字段,如下所示:
<input type='text' onChange={handleChange} />
并声明一个新的状态来存储输入的值,如下所示:
const [username, setUsername] = useState('');
现在,在App
组件中添加handleChange
方法,如下所示:
const handleChange = (event) => { setUsername(event.target.value);};
然而,您会看到我们得到了一个TypeScript错误,指的是事件参数。
在TypeScript中,我们总是需要指定每个函数参数的类型。
在这里,TypeScript无法识别event
参数的类型。
要查找event
参数的类型,我们可以将下面的代码更改为:
<input type='text' onChange={handleChange} />
改为以下代码:
<input type='text' onChange={(event) => {}} />
这里,我们使用了内联函数,因为当使用内联函数时,正确的类型会自动传递给函数参数,所以我们不需要指定它。
如果你将鼠标悬停在event
参数上,你将能够看到我们可以在handleChange
函数中使用的事件的确切类型,如下所示:
现在,你可以将下面的代码还原为:
<input type='text' onChange={(event) => {}} />
使用以下代码:
<input type='text' onChange={handleChange} />
现在,让我们在输入框下面显示username
状态变量的值:
<input type='text' onChange={handleChange} /><div>{username}</div>
如果你检查应用程序,你将能够看到输入的文本显示在输入框下面。
感谢阅读
这就是本教程的全部内容。希望你从中学到了很多。
想观看本教程的视频版本吗?你可以查看这个视频。
你可以在这个仓库中找到这个应用程序的完整源代码。
如果你想精通 JavaScript、ES6+、React 和 Node.js,并有易于理解的内容,请关注我的YouTube 频道。别忘了订阅。
想要及时获取有关 JavaScript、React 和 Node.js 的常规内容吗?在 LinkedIn 上关注我。
Leave a Reply