在 React 项目中使用 TypeScript:
- 可以执行
create-react-app 项目名 --template typescript
命令来新建一个基于 TypeScript 的 React 项目。需要保留默认的
react-app-env.d.ts
文件,是 React 默认提供的类型声明文件。 - 也可以在现有的 React 项目中执行
npm install --save typescript @types/react @types/react-dom
来使现有 React 项目支持 TypeScript。.js
后缀的文件需要改为.ts
后缀。
.jsx
后缀的文件需要改成.tsx
后缀。
函数式组件的类型约束:
对 props 的类型约束:
可以直接对 props 进行类型约束。
import React from 'react'
import type { ReactNode } from 'react'
interface IProps {
id: number,
count: number,
// 使用 React 提供的 ReactNode 来对 props.children 进行类型约束
children?: ReactNode,
}
// 对 props 进行类型约束
const Home = (props: IProps) => {
return <div>Home</div>
}
export default Home
也可以使用泛型函数来对 props 进行类型约束。这种方式使用更多。
import React from 'react'
import type { ReactNode } from 'react'
interface IProps {
id: number,
count: number,
// 使用 React 提供的 ReactNode 来对 props.children 进行类型约束
children?: ReactNode,
}
// 使用 React 提供的 React.FunctionComponent(可以简写为React.FC) 来对函数式组件进行类型约束,然后使用泛型来对 props 进行类型约束
const Home: React.FC<IProps> = () => {
return <div>Home</div>
}
export default Home
这两种写法是有区别的: 如果直接对 props 进行类型约束,此时 TS 只知道 Home 是个函数;但是如果使用泛型函数来对 props 进行类型约束,此时 TS 知道 Home 是个函数式组件,编写会有更友好的提示,更推荐。
可以看到,ReactNode 是很多类型的联合类型。
对 State 的类型约束:
import React, {useState} from 'react'
interface IList {
name: string,
age: number,
}
const Home = () => {
// 对 state 中的 list 进行类型约束
const [list,setList] = useState<IList[]>([])
return (
<div>
{list.map(item => <div>{item.name}-{item.age}</div>)}
</div>
)
}
export default Home
类组件的类型约束:
import React from 'react'
import type {ReactNode} from 'react'
interface IProps = {
name: string,
}
interface IState = {
age: number
}
// 对 props 和 state 进行类型约束
class Home extends React.Component<IProps, IState> {
state: State = {
age: 20,
}
// 约束 render() 函数返回值是 ReactNode 类型
render(): ReactNode {
return (
<div>{this.props.name}今年{this.state.age}岁!</div>
)
}
}
export default Home
路由的类型约束:
import React from 'react'
import {Navigate} from 'react-router-dom'
import type {RouteObject} from 'react-router-dom'
const Home = React.lazy(() => import('src/pages/home'))
const About = React.lazy(() => import('src/pages/about'))
// 使用 React-Router 提供的 RouteObject 作为路由的类型约束
const routes: RouteObject[] = [
{
path: '/',
element: <Navigate to='/home' />
},
{
path: '/home',
element: <Home/>
},
{
path: '/about',
element: <About/>
},
]
export default routes
Redux 状态管理的类型约束:
以在函数式组件中使用的 Hook 为例。
import {configureStore} from '@reduxjs/toolkit'
import counterReducer from './reducers/counter'
import {useSelector, useDispatch} from 'react-redux'
import type {TypedUseSelectorHook} from 'react-redux'
const store = configureStore({
reducer: {
counter: counterReducer,
}
})
// 对 store.state 进行类型约束,否则的话,在组件中使用的时候会报错提示
// 1. 获取 store.getState() 函数的类型
type GetStateFnType = typeof store.getState
// 2. 获取 store.getState() 函数返回值的类型,也就是 store.state 的类型。获取到 state 的类型后当然可以直接在组件中使用,但是每个组件都需要写一遍很麻烦,因此可以使用下面的方式让 TS 自动推导出
type GetStateReturnType = ReturnType<GetStateFnType>
// 3. 定义一个新的 useSelector Hook,并且指定其类型。在组件中不再使用 useSelector 而是使用 useAppSelector,就可以自动推导出第一个函数参数中的 state 参数的类型
export const useAppSelector: TypedUseSelectorHook<GetStateReturnType> = useSelector
// 1. 获取 store.dispatch 的类型
type DispatchType = typeof store.dispatch
// 2. 定义一个新的 useDispatch Hook,并且指定其类型。在组件中不再使用 useDispatch 而是使用 useAppDispatch。其实封不封装都可以,只是为了统一管理,没有什么额外的用处
export const useAppDispatch: () => DispatchType = useDispatch
export default store
设置路径别名:
在基于 TypeScript 的 React 项目中想要设置路径别名,既需要配置 TypeScript 使其在编译阶段能正确地解析别名,也需要配置 Webpack 使其在构建阶段能正确地解析别名。
- 配置 TypeScript 使其在编译阶段能正确地解析别名。
// tsconfig.json { ... "compilerOptions": { // 指定 TypeScript 模块解析的根路径。当使用相对路径导入模块时,TypeScript 会以 baseUrl 为基准来解析这些路径。 "baseUrl": ".", // 创建模块的映射关系,可以将特定的模块名映射到实际的文件路径。 "paths": { "@/*": ["src/*"] } } }
- 配置 Webpack 使其在构建阶段能正确地解析别名 。
// craco.config.js。一般使用 craco 在 React 项目中配置 Webpack,而不是直接弹出 React 中的 Webpack 配置 const path = require('path') module.exports = { webpack: { //配置别名 alias: { //使用 @ 代表 src 文件所在路径 '@': path.resolve(__dirname, 'src') } } }
- 在项目中导入模块时,就可以使用
@
来替代src
文件夹的路径了。// index.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import App from '@/App'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); root.render( <App /> );