当前位置: 首页 > article >正文

TS React 项目中使用TypeScript

在 React 项目中使用 TS

  1. 创建新项目
  1. 在现有项目中添加 TS

创建新项目

  • 命令:npx create-react-app my-app --template typescript
  • 说明:在命令行中,添加 --template typescript 表示创建支持 TS 的项目
  • 项目目录的变化:
    1. 在项目根目录中多了一个文件:tsconfig.json
      • TS 的配置文件
    1. 在 src 目录中,文件的后缀有变化,由原来的 .js 变为 .ts.tsx
      • .ts ts 文件的后缀名
      • .tsx 是在 TS 中使用 React 组件时,需要使用该后缀
    1. 在 src 目录中,多了 react-app-env.d.ts 文件
      • .d.ts 类型声明文件,用来指定类型

基本使用

创建类组件

在vscode中通过tsrcc快速创建类组件

import React, { Component } from 'react'

type Props = {}

type State = {}

export default class App extends Component<Props, State> {
  state = {}

  render() {
    return (
      <div>App</div>
    )
  }
}

其中泛型Props指外部数据的数据类型

State指内部数据的数据类型。

创建函数组件

定义函数组件第一种方式:

在vscode中通过tsrfc快速创建函数组件

import React from 'react'

type Props = {}

export default function Header({}: Props) {
  return (
    <div>Header</div>
  )
}

Props:指外部数据的数据类型

第二种方式:

import { FC } from 'react';

type Props = {
    
}

// FC:函数组件
const Nav: FC<Props> = function() {
    return <div></div>
}

外部数据

简单的数据类型定义
import React, { Component } from 'react'

type Props = {
    msg: string
}

type State = {}

export default class Footer extends Component<Props, State> {
  state = {}

  render() {
    return (
      <div>
        消息: {this.props.msg}
      </div>
    )
  }
}

复杂数据类型定义

定义复杂数据类型后,可以导出数据类型方便其他组件引入使用。

import React, { Component } from 'react'

export interface User {
    name: string,
    age: number
}

export type UserList = User[];

type Props = {
    msg: string,
    user: User,
    userList: UserList
}

type State = {}

export default class Footer extends Component<Props, State> {
  state = {}

  render() {
    return (
      <div>
        消息: {this.props.msg}
        <br />
        姓名:{this.props.user.name}
        年龄:{this.props.user.age}
      </div>
    )
  }
}

在父组件引入数据类型使用

import React, { Component } from 'react'
import Footer, { User, UserList } from './components/Footer'

type Props = {}

type State = {}

const user: User = {name: '张三', age: 20}
const userList: UserList = [{name: '李四', age: 30}];


export default class App extends Component<Props, State> {
  state = {}

  render() {
    return (
      <div>
        <Footer msg={'消息'} user={user} userList={userList} />
      </div>
    )
  }
}

内部数据

类组件的内部数据State

内部数据通过泛型传入State数据类型。后续使用中提示更加友好。

import React, { Component } from 'react'

type Props = {
}

type State = {
    address: string
}

export default class Footer extends Component<Props, State> {
  state = {
    address: '红旗河沟'
  }

  changeAddr = () => {
    this.setState({
        address: '渝北区'
    })
  }

  render() {
    return (
      <div>
        地址:{this.state.address}
        <button onClick={this.changeAddr}>修改地址</button>
      </div>
    )
  }
}

函数组件的内部数据State

在函数组件中通过useState创建内部数据

在创建某些复杂数据时,要注意显示去传入state的泛型数据类型,否则数据类型很容容易报错。

import React, { useEffect } from 'react'
import { useState } from 'react';

type Props = {

}

interface User {name: string, age: number}

export default function Header(props: Props) {


    let [count, setCount] = useState(0);
    let [user, setUser] = useState<User>({} as any);
    let [userList, setUserList] = useState<User[]>([]);

    function changeCount() {
        setCount(10);
    }

    function changeUserList() {
        setUserList([{
            name: '张三',
            age: 20
        }]);
    }

  return (
    <div>
        count:{count}
        <button onClick={changeCount}>修改count</button>
        <br />
        姓名:{user.name}
        年龄:{user.age}
        <button onClick={changeUserList}>修改userList</button>
    </div>
  )
}

对父子通信进行类型限定

首先让脚手架支持TypeScript,可以在安装脚手架的时候进行配置即可,命令如下。

npx create-react-app react-ts-study --template typescript

然后就是创建两个组件,并且完成props通信。

import React from 'react'
interface WelcomeProps {
  msg?: string
  count?: number
  list: string[]
  info: { username: string; age: number }
  status?: 'loading' | 'success' | 'error'
}

function Welcome(props: WelcomeProps) {
  const { count = 0 } = props;
  return (
    <div>
      <h2>hello Welcome, {count}</h2>
    </div>
  )
}
export default function App() {
  return (
    <div>
      <h2>01_react-ts</h2>
      <Welcome msg="hello" count={123} list={['a', 'b', 'c']} info={{username: 'xiaoming', age: 20}} />
      <Welcome list={['a', 'b', 'c']} info={{username: 'xiaoming', age: 20}} />
      <Welcome status="loading" list={['a', 'b', 'c']} info={{username: 'xiaoming', age: 20}} />
    </div>
  )
}

下面来看一下函数表达式写法的情况下,如何指定props的类型,可通过内置的FC类型来进行实现。

const Welcome: React.FC<WelcomeProps> = (props) => {
  return (
    <div>
      <h2>hello Welcome</h2>
    </div>
  )
}

children与event限制

children的类型限制

父子通信时候的内容分发进行限制。

import React from 'react'
interface WelcomeProps {
  children?: React.ReactNode
}
function Welcome(props: WelcomeProps) {
  return (
    <div>
      <h2>hello Welcome, {props.children}</h2>
    </div>
  )
}
export default function App() {
  return (
    <div>
      <h2>02_react-ts</h2>
      <Welcome />
      <Welcome>
        aaaaa
      </Welcome>
    </div>
  )
}

我们把children属性作为可选参数,这样当<Welcome>组件进行内容分发和不进行内容分发都是可以的。

event限制

event在React中主要通过内置的ev: React.MouseEvent<HTMLButtonElement>来进行限定。

import React from 'react'
interface WelcomeProps {
  children?: React.ReactNode
  handleMsg?: (ev: React.MouseEvent<HTMLButtonElement>)=> void
}
function Welcome(props: WelcomeProps) {
  const handleChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
    console.log(ev.target.value)
  }
  return (
    <div>
      <h2>hello Welcome, {props.children}</h2>
      <button onClick={props.handleMsg}>点击</button>
      <input type="text" onChange={handleChange} />
    </div>
  )
}
export default function App() {
  return (
    <div>
      <h2>02_react-ts</h2>
      <Welcome />
      <Welcome handleMsg={(ev)=>{}}>
        aaaaa
      </Welcome>

    </div>
  )
}

props.children问题

在tsx中props中要访问children,那么应该使用PropsWithChildren去定义props数据类型

import React, { useEffect } from 'react'
import { useState, PropsWithChildren } from 'react';

type Props = {

}

export default function Header(props: PropsWithChildren<Props>) {

  return (
    <div
        {props.children}
    </div>
  )
}

PropsWithChildren是一个数据类型,接口泛型Props数据类型,然后得到一个注入了children数据类型的Props数据类型。

通过FC创建的函数组件的props也没有children属性,也需要使用PropsWithChildren去定义

import { FC, PropsWithChildren } from 'react';

type Props = {

}

// FC:函数组件
const Nav: FC<PropsWithChildren<Props>> = function(props) {
    return <div>{props.children}</div>
}

style与component限制

style限制

当我们进行style样式通信的时候,也是可以指定类型,防止样式传递的时候不复合规范。

import React from 'react'
interface HeaderProps {
  username: string
}
interface WelcomeProps {
  style: React.CSSProperties
}
function Welcome(props: WelcomeProps) {
  return (
    <div>
      <h2>hello Welcome</h2>
    </div>
  )
}
export default function App() {
  return (
    <div>
      <h2>03_react-ts</h2>
      <Welcome style={{'border': '1px red solid', display: 'none'}} />
    </div>
  )
}

主要通过React.CSSProperties来指定样式的类型,这样当传递的样式属性或者值不符合规范的时候,就不会产生TS的提示。

component限制

如果组件进行通信的时候,也可以进行类型的限制。

import React from 'react'
interface HeaderProps {
  username: string
}
interface WelcomeProps {
  style: React.CSSProperties
  component: React.ComponentType<HeaderProps>
}
function Welcome(props: WelcomeProps) {
  return (
    <div>
      <h2>hello Welcome</h2>
      <props.component username="xiaoming"></props.component>
    </div>
  )
}
function Header(props: HeaderProps) {
  return (
    <div>hello Header</div>
  )
}
export default function App() {
  return (
    <div>
      <h2>03_react-ts</h2>
      <Welcome style={{'border': '1px red solid', display: 'none'}} component={Header} />
    </div>
  )
}

主要通过React.ComponentType<>来指定组件的类型,那么一旦不符合指定的接口类型,就会报错。
 

use函数限制

在React函数组件中,主要就是对use函数进行类型的注解。常见的注解use函数如下:

  • useState -> 联合类型、对象字面量类型
  • useEffect -> 自动类型推断
  • useRef -> 泛型标签类型
import React, { useEffect, useState, useRef } from 'react'
interface WelcomeProps {
}
function Welcome(props: WelcomeProps) {
  return (
    <div>
      <h2>hello Welcome</h2>
    </div>
  )
}
type Info = {username: string; age: number}
export default function App() {
  //const [count, setCount] = useState(0)
  const [count, setCount] = useState<number|string>(0)
  const [list, setList] = useState<string[]>([])
  //const [info, setInfo] = useState<{username: string; age: number}|null>(null)
  const [info, setInfo] = useState<Info>({} as Info)
  const myRef = useRef<HTMLButtonElement>(null)
  useEffect(()=>{
    console.log( myRef.current?.innerHTML )  // 可选链(类型保护)
    //console.log( myRef.current!.innerHTML )  // 非空断言(慎用)   
    return ()=>{
    }
  }, [])
  const handleClick = () => {
    setCount(1)
    setList(['a', 'b'])
  }
  return (
    <div>
      <h2>04_react-ts</h2>
      <button onClick={handleClick} ref={myRef}>点击</button>
      { info.username }, { info.age }
      <Welcome />      
    </div>
  )
}

useState和useRef都是通过泛型的方式进行类型注解,useEffect主要利用自动类型推断来完成。

类组件类型限制

类组件在React中并不是重点,但是也要了解怎么对类组件进行类型的限制。

import React, { Component } from 'react'
interface WelcomeProps {
  msg: string
  count: number
}
interface WelcomeState {
  username: string
}
class Welcome extends Component<WelcomeProps, WelcomeState> {
  state = {
    username: 'xiaoming'
  }
  render() {
    return (
      <div>hello Welcome {this.state.username}</div>
    )
  }
}
export default function App() {
  return (
    <div>
      <h2>05_react-ts</h2>
      <Welcome msg="hello" count={123} />      
    </div>
  )
}

主要就是给继承的类Component传递泛型,Props和State,这样可以实现父子通信的数据进行类型限制,又可以对内部的state进行类型限制。
 

路由如何使用TS进行开发

react-router-dom类型限制

React路由与TS配合常见的使用为以下这些操作:

  • RouteObject 内置类型,限制路由表
  • React.createElement() 进行组件编写
  • 扩展 meta 元信息
// /router/index.ts
import { createBrowserRouter } from 'react-router-dom'
import type { RouteObject } from 'react-router-dom'
import App from '../App';
import Index from '../views/Index/Index';
import User from '../views/User/User';
import Login from '../views/Login/Login';
import React from 'react';
declare module 'react-router' {
  interface NonIndexRouteObject {
    meta?: { title: string }
  }
  interface IndexRouteObject {
    meta?: { title: string }
  }
}
export const routes: RouteObject[] = [
  {
    path: '/',
    element: React.createElement(App),
    meta: { title: '/' },
    children: [
      {
        path: 'index',
        element: React.createElement(Index),
        meta: { title: 'index' }
      },
      {
        path: 'user',
        element: React.createElement(User),
        meta: { title: 'user' }
      },
      {
        path: 'login',
        element: React.createElement(Login)
      }
    ]
  }
];
const router = createBrowserRouter(routes);
export default router;

状态管理如何使用TS进行开发

Redux Toolkit限制类型

Redux状态管理与TS配合常见的使用为以下这些操作:

  • 得到全局state类型: ReturnType<typeof store.getState>
  • 限定payload类型: PayloadAction
// /store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './modules/user';
import { useDispatch } from 'react-redux'
const store = configureStore({
  reducer: {
    user: userReducer
  }
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const useAppDispatch: () => AppDispatch = useDispatch
export default store;
// /store/modules/user.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
export const loginAction = createAsyncThunk(
  'users/loginAction',
  async (userId: number) => {
    const response = await new Promise((resolve)=>{
      resolve('response data')
    })
    return response
  }
)
const userSlice = createSlice({
  name: 'user',
  initialState: {
    name: 'xiaoming'
  },
  reducers: {
    change(state, action: PayloadAction<string>){
      state.name = action.payload
    }
  }
})
export const { change } = userSlice.actions
export default userSlice.reducer

tsconfig的介绍

  • tsconfig.json是typescript项目的配置文件,用于配置typescript
  • tsconfig.json配置文件可以通过 tsc --init 生成
  • 说明:所有的配置项都可以通过鼠标移入的方式,来查看配置项的解释说明。
  • tsconfig 文档链接
{
  // 编译选项
  "compilerOptions": {
    // 生成代码的语言版本:将我们写的 TS 代码编译成哪个版本的 JS 代码
    // 命令行: tsc --target es5 11-测试TS配置文件.ts
    "target": "es5",
    // 指定要包含在编译中的 library
    "lib": ["dom", "dom.iterable", "esnext"],
    // 允许 ts 编译器编译 js 文件
    "allowJs": true,
    // 跳过类型声明文件的类型检查
    "skipLibCheck": true,
    // es 模块 互操作,屏蔽 ESModule 和 CommonJS 之间的差异
    "esModuleInterop": true,
    // 允许通过 import x from 'y' 即使模块没有显式指定 default 导出
    "allowSyntheticDefaultImports": true,
    // 开启严格模式
    "strict": true,
    // 对文件名称强制区分大小写
    "forceConsistentCasingInFileNames": true,
    // 为 switch 语句启用错误报告
    "noFallthroughCasesInSwitch": true,
    // 生成代码的模块化标准
    "module": "esnext",
    // 模块解析(查找)策略
    "moduleResolution": "node",
    // 允许导入扩展名为.json的模块
    "resolveJsonModule": true,
    // 是否将没有 import/export 的文件视为旧(全局而非模块化)脚本文件
    "isolatedModules": true,
    // 编译时不生成任何文件(只进行类型检查)
    "noEmit": true,
    // 指定将 JSX 编译成什么形式
    "jsx": "react-jsx"
  },
  // 指定允许 ts 处理的目录
  "include": ["src"]
}

typescript声明文件

今天几乎所有的 JavaScript 应用都会引入许多第三方库来完成任务需求。

这些第三方库不管是否是用 TS 编写的,最终都要编译成 JS 代码,才能发布给开发者使用。

我们知道是 TS 提供了类型,才有了代码提示和类型保护等机制。

但在项目开发中使用第三方库时,你会发现它们几乎都有相应的 TS 类型,这些类型是怎么来的呢? 类型声明文件

  • 类型声明文件:用来为已存在的 JS 库提供类型信息

这样在 TS 项目中使用这些库时,就像用 TS 一样,都会有代码提示、类型保护等机制了。

  1. TS 的两种文件类型
  1. 类型声明文件的使用说明

TS 中的两种文件类型

  • TS 中有两种文件类型:1 .ts 文件 2 .d.ts 文件
  • .ts 文件:
    1. 既包含类型信息又可执行代码
    2. 可以被编译为 .js 文件,然后,执行代码
    3. 用途:编写程序代码的地方
  • .d.ts 文件:
    1. 只包含类型信息的类型声明文件
    2. 不会生成 .js 文件,仅用于提供类型信息,在.d.ts文件中不允许出现可执行的代码,只用于提供类型
    3. 用途:为 JS 提供类型信息
  • 总结:.ts 是 implementation(代码实现文件);.d.ts 是 declaration(类型声明文件)
  • 如果要为 JS 库提供类型信息,要使用 .d.ts 文件

类型声明文件的使用说明

  • 在使用 TS 开发项目时,类型声明文件的使用包括以下两种方式:
    1. 使用已有的类型声明文件
    2. 创建自己的类型声明文件

使用已有的类型声明文件

  1. 内置类型声明文件
  1. 第三方库的类型声明文件
  1. 自己提供的

内置类型声明文件

  • TS 为 JS 运行时可用的所有标准化内置 API 都提供了声明文件
  • 比如,在使用数组时,数组所有方法都会有相应的代码提示以及类型信息:
const strs = ['a', 'b', 'c']
// 鼠标放在 forEach 上查看类型
strs.forEach
  • 实际上这都是 TS 提供的内置类型声明文件
  • 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看内置类型声明文件内容
  • 比如,查看 forEach 方法的类型声明,在 VSCode 中会自动跳转到 lib.es5.d.ts 类型声明文件中
  • 当然,像 window、document 等 BOM、DOM API 也都有相应的类型声明(lib.dom.d.ts)

第三方库的类型声明文件

  • 目前,几乎所有常用的第三方库都有相应的类型声明文件
  • 第三方库的类型声明文件有两种存在形式:1 库自带类型声明文件 2 由 DefinitelyTyped 提供。

  1. 库自带类型声明文件:比如,axios
    • 查看 node_modules/axios 目录

解释:这种情况下,正常导入该库,TS 就会自动加载库自己的类型声明文件,以提供该库的类型声明。

  1. 由 DefinitelyTyped 提供
  • DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明
  • DefinitelyTyped 链接
  • 可以通过 npm/yarn 来下载该仓库提供的 TS 类型声明包,这些包的名称格式为:@types/*
  • 比如,@types/react、@types/lodash 等
  • 说明:在实际项目开发时,如果你使用的第三方库没有自带的声明文件,VSCode 会给出明确的提示

import _ from 'lodash'

// 在 VSCode 中,查看 'lodash' 前面的提示
  • 解释:当安装 @types/* 类型声明包后,TS 也会自动加载该类声明包,以提供该库的类型声明
  • 补充:TS 官方文档提供了一个页面,可以来查询 @types/* 库
  • @types/* 库

创建自己的类型声明文件

  1. 项目内共享类型
  1. 为已有 JS 文件提供类型声明

项目内共享类型

  • 如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。
  • 操作步骤:
    1. 创建 index.d.ts 类型声明文件。
    2. 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。
    3. 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。

为已有 JS 文件提供类型声明

  1. 在将 JS 项目迁移到 TS 项目时,为了让已有的 .js 文件有类型声明。
  1. 成为库作者,创建库给其他人使用。

  • 注意:类型声明文件的编写与模块化方式相关,不同的模块化方式有不同的写法。但由于历史原因,JS 模块化的发展 经历过多种变化(AMD、CommonJS、UMD、ESModule 等),而 TS 支持各种模块化形式的类型声明。这就导致 ,类型声明文件相关内容又多又杂。
  • 演示:基于最新的 ESModule(import/export)来为已有 .js 文件,创建类型声明文件。

类型声明文件的使用说明

  • 说明:TS 项目中也可以使用 .js 文件。
  • 说明:在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。
  • declare 关键字:用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。
    1. 对于 type、interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
    2. 对于 let、function 等具有双重含义(在 JS、TS 中都能用),应该使用 declare 关键字,明确指定此处用于类型声明。
let count = 10
let songName = '痴心绝对'
let position = {
  x: 0,
  y: 0
}

function add(x, y) {
  return x + y
}

function changeDirection(direction) {
  console.log(direction)
}

const fomartPoint = point => {
  console.log('当前坐标:', point)
}

export { count, songName, position, add, changeDirection, fomartPoint }

定义类型声明文件

declare let count:number

declare let songName: string

interface Position {
  x: number,
  y: number
}

declare let position: Position

declare function add (x :number, y: number) : number

type Direction = 'left' | 'right' | 'top' | 'bottom'

declare function changeDirection (direction: Direction): void

type FomartPoint = (point: Position) => void

declare const fomartPoint: FomartPoint

export {
  count, songName, position, add, changeDirection, FomartPoint, fomartPoint
}

http://www.kler.cn/news/309102.html

相关文章:

  • 【Java】synchronized 基础线程安全
  • git 更新LingDongGui问题解决
  • Reactive 编程-Loom 项目(虚拟线程)
  • 1. YOLOv10: Real-Time End-to-End Object Detection
  • Linux进阶系列(三)——重定向、tee、rsync、xargs
  • Android注册广播
  • 开源免费的NAS系统-TrueNAS CORE搭建和使用(保姆级教程)
  • 基于C++实现(MFC)职工工作量统计系统
  • 机器学习--逻辑回归
  • JavaScript如何判断输入的是空格
  • 常见的反爬虫和应对方法
  • 【SQL】数据库详解-标准SQL语句
  • 协同过滤算法商品推荐系统设计与实现
  • 解决ruoyi-vue-pro-master框架引入报错,启动报错问题
  • 毕设开源 基于python的搜索引擎设计与实现
  • 智能 Uber 发票 PDF 合并工具
  • 【乐企-业务篇】乐企开票具体代码实现
  • Java和西门子S7-1200通讯调试记录
  • GC的算法
  • 从基础到高级:模块化RAG技术全览
  • 【云原生监控】Prometheus之Alertmanager报警
  • 线性基速通
  • 哪款宠物空气净化器是除浮毛王者?希喂、范罗士、霍尼韦尔宠物空气净化器实测
  • 爬坑--docker构建容器ssh连接容器环境变量会发生变化
  • Redis的IO模型
  • 计算机网络分层结构解析:OSI与TCP/IP模型
  • Blender渲染太慢怎么办?blender云渲染已开启
  • 在设计开发中,如何提高网站的用户体验?
  • Qt开发技巧(四)“tr“使用,时间类使用,Qt容器取值,类对象的删除,QPainter画家类,QString的转换,用好 QVariant类型
  • Vite项目中eslint的简单配置