如何在React项目中使用TypeScript

news2025/7/17 20:17:35

本文主要记录我如何在React项目中优雅的使用TypeScript,来提高开发效率及项目的健壮性。

项目目录及ts文件划分

由于我在实际项目中大部分是使用umi来进行开发项目,所以使用umi生成的目录来做案例。

.
├── README.md
├── global.d.ts
├── mock
├── package.json
├── src
│   ├── assets
│   ├── components
│   │   └── PublicComA
│   │   ├── index.d.ts
│   │   ├── index.less
│   │   └── index.tsx
│   ├── layouts
│   ├── models
│   ├── pages
│   │   ├── PageA
│   │   │   ├── index.d.ts
│   │   │   ├── index.less
│   │   │   └── index.tsx
│   │   ├── index.less
│   │   └── index.tsx
│   └── utils
├── tsconfig.json
├── typings.d.ts
└── yarn.lock 

在项目根目录下有typings.d.ts和global.d.ts这两个文件, 前者我们可以放置一些全局的导出模块,比如css,less, 图片的导出声明;后者可以放一些全局声明的变量, 接口等, 比如说window下全局变量的声明等。如下:

// typings.d.ts
declare module '*.css';
declare module '*.less';
declare module "*.png";
declare module "*.jpeg";
declare module '*.svg' {export function ReactComponent(props: React.SVGProps<SVGSVGElement>): React.ReactElementconst url: stringexport default url
} 
// global.d.ts
interface Window {helloWorld: () => void;
} 

接下来介绍一下src目录:

  • assets 存放静态资源如图片/视频/音频等, 参与webpack的打包过程
  • layouts 存放公共布局
  • components 存放全局公共组件
  • models dva的models文件夹
  • pages 存放页面的目录, 内部可以有页面组件components, 结构类似于全局的components
  • utils 存放js工具库, 请求库等公共js文件

在pages和components中有存放当前组件/页面所需要的类型和接口声明的index.d.ts。另外如models中的文件由于是每个model私有类型和接口声明,所以可以直接在文件内部去声明。 具体的目录规划如上,可以根据实际项目来做更合理的划分。

在项目中使用TypeScript具体实践

组件声明

1.函数组件 推荐使用React.FC<P={}>来表示函数类型,当使用该类型定义组件时,props中会默认带有children属性。

interface IProps {count: number
}

const App: React.FC<IProps> = (props) => {const {count} = props;return (<div className="App"><span>count: {count}</span></div>);
} 

2.类组件 类组件接受两个参数,第一个是props的定义,第二个是state的定义,如果使用React.PureComponent<P, S={} SS={}>定义组件,则还有第三个参数,表示getSnapshotBeforeUpdate的返回值。

interface IProps {name: string;
}

interface IState {count: number;
}

class App extends React.Component<IProps, IState> {state = {count: 0};render() {return (<div>{this.state.count}{this.props.name}</div>);}
} 

React Hooks使用

useState

声明定义:

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
// convenience overload when first argument is omitted
	/**
	 * Returns a stateful value, and a function to update it. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usestate */function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];/** * An alternative to `useState`. * * `useReducer` is usually preferable to `useState` when you have complex state logic that involves * multiple sub-values. It also lets you optimize performance for components that trigger deep * updates because you can pass `dispatch` down instead of callbacks. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usereducer */ 

如果初始值能够体现出类型,那么可以不用手动声明类型,TS会自动推断出类型。如果初始值为null或者undefined则需要通过泛型显示声明类型。如下:

const [count, setCount] = useState(1);

const [user, setUser] = useState<IUser | null>(null); 

useRef

声明定义:

 function useRef<T>(initialValue: T): MutableRefObject<T>;
 // convenience overload for refs given as a ref prop as they typically start with a null value
 /** * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument * (`initialValue`). The returned object will persist for the full lifetime of the component. * * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable * value around similar to how you’d use instance fields in classes. * * Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type * of the generic argument. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#useref */ 

使用该Hook时,要根据使用场景来判断传入泛型类型,如果是获取DOM节点,则传入对应DOM类型即可;如果需要的是一个可变对象,则需要在泛型参数中包含’| null’。如下:

// 不可变DOM节点,只读
const inputRef = useRef<HTMLInputElement>(null);

// 可变,可重新复制
const idRef = useRef<string | null>(null);
idRef.current = "abc"; 

useCallback

声明定义:

 function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;
 /*** `useMemo` will only recompute the memoized value when one of the `deps` has changed.** Usage note: if calling `useMemo` with a referentially stable function, also give it as the input in* the second argument.** ```ts* function expensive () { ... }** function Component () {* const expensiveResult = useMemo(expensive, [expensive])* return ...* }* ```** @version 16.8.0* @see https://reactjs.org/docs/hooks-reference.html#usememo*/ 

useCallback会根据返回值自动推断出类型,如果传入的参数不指定类型,则会默认为any,所以为了严谨和可维护性,一定要指定入参的类型。也可以手动传入泛型指定函数类型。如下:

// 会自动推导出类型: (a: number, b: number) => number;
const add = useCallback((a: number, b: number) => a + b, [a, b])

// 传入泛型,则指定函数类型
const toggle = useCallback<(a: number) => number>((a: number) => a * 2, [a]) 

useMemo

声明定义:

function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T; /*** `useDebugValue` can be used to display a label for custom hooks in React DevTools.** NOTE: We don’t recommend adding debug values to every custom hook.* It’s most valuable for custom hooks that are part of shared libraries.** @version 16.8.0* @see https://reactjs.org/docs/hooks-reference.html#usedebugvalue*/ 

useMemo和useCallback类似,只是定义类型为具体返回值的类型,而不是函数的类型。如下:

// 会自动推导出类型: number;
const add = useCallback((a: number, b: number) => a + b, [a, b])

// 传入泛型,则指定函数类型
const toggle = useCallback<number>((a: number) => a * 2, [a]) 

useContext

声明定义:

function useContext<T>(context: Context<T>/*, (not public API) observedBits?: number|boolean */): T;
/*** Returns a stateful value, and a function to update it.** @version 16.8.0* @see https://reactjs.org/docs/hooks-reference.html#usestate*/ 

useContext会根据传入的上下文对象自动推导出context的类型,当然也可以使用泛型来设置context的类型,如下:

interface ITheme {
	color: string;
}
const ThemeContext = React.createContext<ITheme>({ color: "red" });

// 自动推导出类型为ITheme
const theme = useContext(ThemeContext); // 等同于const theme = useContext<ITheme>(ThemeContext); 

useReducer

声明定义:

function useReducer<R extends Reducer<any, any>>(reducer: R,initialState: ReducerState<R>,initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
/*** `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument* (`initialValue`). The returned object will persist for the full lifetime of the component.** Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable* value around similar to how you’d use instance fields in classes.** @version 16.8.0* @see https://reactjs.org/docs/hooks-reference.html#useref*/ 

上面只列出了一种类型定义,我在项目中也是使用这种定义去指定useReducer的类型。普通的案例如下:

type StateType = {name: string;age: number;
}

type Actions = {type: 'Change_Name';payload: string;
} | {type: 'Change_Age';payload: number;
}

const initialState = {name: '小明',age: 18
}

const reducerAction: Reducer<StateType, Actions> = ( state,action, ) => {switch (action.type) {case 'Change_Name':return { ...state, name: action.payload };case 'Change_Age':return { ...state, age: action.payload };default:return state;}
};

function Index() {const [state, dispatch] = useReducer(reducerAction, initialState);return (<div><div>姓名:{state.name}</div><div>年龄:{state.age}</div></div>);
} 

可以看到,这样能够得到正确的类型推断,但是略微繁琐。在这篇文章中, 学到了一个泛型定义,可以稍微简化一下定义Actions的过程。案例如下:

// 定义一个生成Action类型的泛型
type ActionMap<M extends Record<string, any>> = {[Key in keyof M]: M[Key] extends undefined? {type: Key}: {type: Keypayload: M[Key]}
}

type StateType = {name: string;age: number;
}

// 定义具体的Action类型
type PayloadType = {Change_Name: string;Change_Age: number;
}

/** ActionMap<PayloadType>会生成类型{Change_Name: {type: Types.Name;payload: string;};Change_Age: {type: Types.Age;payload: number;};}而keyof ActionMap<PayloadType>则会生成 'Change_Name' | 'Change_Age'的类型。所以Action最终的类型便为:type Actions = {type: Types.Name;payload: string;} | {type: Types.Age;payload: number;}
*/
type Actions = ActionMap<PayloadType>[keyof ActionMap<PayloadType>]

const initialState = {name: '小明',age: 18
}

const reducerAction: Reducer<StateType, Actions> = ( state,action, ) => {switch (action.type) {case Types.Name:return { ...state, name: action.payload };case Types.Age:return { ...state, age: action.payload };default:return state;}
}; 

我们定义了一个ActionMap泛型,该泛型会将传入的类型{key: value}生成为新的{key: {type: key, payload: value }类型。然后我们利用keyof关键字获取到所有的key,就可以得到我们所需要的{type: key1, payload: value1} | {type: key2, payload: value2}的类型了。只要我们定义好PayloadType类型,则可以自动推导出我们需要的Actions类型。 如果你觉得这样写还是很繁琐,那么可以去看我的这篇文章在TypeScript中使用useReducer,里面介绍了简化的useReducer使用方式。

useImperativeHandle

声明定义:

 function useImperativeHandle<T, R extends T>(ref: Ref<T>|undefined, init: () => R, deps?: DependencyList): void;// NOTE: this does not accept strings, but this will have to be fixed by removing strings from type Ref<T>/** * `useImperativeHandle` customizes the instance value that is exposed to parent components when using * `ref`. As always, imperative code using refs should be avoided in most cases. * * `useImperativeHandle` should be used with `React.forwardRef`. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#useimperativehandle */ 

useImperativeHandle可以让自定义组件通过ref属性,将内部属性暴露给父组件进行访问。因为是函数式组件,所以需要结合forwardRef一起使用。案例如下:

interface FancyProps {}

interface FancyRef {focus: () => void;
}

const FancyInput = forwardRef<FancyRef, FancyProps>((props, ref) => {const inputRef = useRef<HTMLInputElement>(null);useImperativeHandle(ref, () => ({focus: () => {inputRef.current?.focus();}}));return (<input ref={inputRef} {...props} />);
})

const Parent = () => {// 定义子组件refconst inputRef = useRef<FancyRef>(null);return (<div><FancyInput ref={inputRef}/><button onClick={() => {// 调用子组件方法inputRef.current?.focus();}}>聚焦</button></div>)
} 

Axios请求/响应定义封装

axios是很流行的http库,他的ts封装已经很完美了,我们只做简单的二次封装,返回通用的数据响应格式。 首先在utils/request.ts中创建一个构造axios实例的生成器:

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';

// 拦截器定义
export interface RequestInterceptors {// 请求拦截requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfigrequestInterceptorsCatch?: (err: any) => any// 响应拦截responseInterceptors?: (config: AxiosResponse) => AxiosResponseresponseInterceptorsCatch?: (err: any) => any
}

// 生成axios实例的参数,实例可以单独传入拦截器
export interface RequestConfig extends AxiosRequestConfig {interceptorsObj?: RequestInterceptors
}

// loading请求数量
let loadingCount: number = 0;

// 打开loading
const showLoading = () => {loadingCount ++;if(loadingCount > 0) {// 显示loading// Loading.show()}
}

// 关闭loading
const hideLoading = () => {loadingCount --;if(loadingCount <= 0) {// 隐藏loading// Loading.hide();}
}

function RequestBuilder(config: RequestConfig) {const { interceptorsObj, ...res } = config;const instance: AxiosInstance = axios.create(res);// 全局请求拦截器instance.interceptors.request.use((request: AxiosRequestConfig) => {// 显示loadingshowLoading();console.log('全局请求拦截器');// TODO:全局的请求头操作等等return request;},(err: any) => err,)/** * 实例请求拦截器  * 要注意 axios请求拦截器为倒序执行,所以要将实例请求拦截器注册在全局请求拦截器后面 */instance.interceptors.request.use(interceptorsObj?.requestInterceptors,interceptorsObj?.requestInterceptorsCatch,)/** * 实例响应拦截器 * axios响应拦截器为正序执行,所以要将实例响应拦截器注册在全局响应拦截器前面 */instance.interceptors.response.use(interceptorsObj?.responseInterceptors,interceptorsObj?.responseInterceptorsCatch,)// 全局响应拦截器instance.interceptors.response.use((response: AxiosResponse) => {console.log('全局响应拦截器');// 关闭loadinghideLoading();// TODO: 通用的全局响应处理,token过期重定向登录等等// 返回值为res.data,即后端接口返回的数据,减少解构的层级,以及统一响应数据格式。return response.data},(err: any) => {// 关闭loadinghideLoading();// TODO: 错误提示等return err;},)return instance;
}

export const http = RequestBuilder({baseURL: '/api'}); 

该生成器可以实现每个实例有单独的拦截器处理逻辑,并且实现全局的loading加载效果,全局拦截器的具体实现可以根据项目实际需求进行填充。生成器已经完成,但是还没法定制我们的通用响应数据,接下来我们在typings.d.ts中重新定义axios模块:

import * as axios from 'axios';

declare module 'axios' {// 定制业务相关的网络请求响应格式, T 是具体的接口返回类型数据export interface CustomSuccessData<T> {code: number;msg?: string;message?: string;data: T;[keys: string]: any;}export interface AxiosInstance {// <T = any>(config: AxiosRequestConfig): Promise<CustomSuccessData<T>>;request<T = any, R = CustomSuccessData<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;get<T = any, R = CustomSuccessData<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;delete<T = any, R = CustomSuccessData<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;head<T = any, R = CustomSuccessData<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;post<T = any, R = CustomSuccessData<T>, D = any>(url: string,data?: D,config?: AxiosRequestConfig<D>,): Promise<R>;put<T = any, R = CustomSuccessData<T>, D = any>(url: string,data?: D,config?: AxiosRequestConfig<D>,): Promise<R>;patch<T = any, R = CustomSuccessData<T>, D = any>(url: string,data?: D,config?: AxiosRequestConfig<D>,): Promise<R>;}
} 

完成以上操作后,我们在业务代码中具体使用:

import { http } from '@/utils/request';

interface Req {userId: string;
}

interface Res {userName: string;userId: string;
}

// 获取用户信息接口
const getUserInfo = async (params: Req) => {return http.get<Res>('/getUserInfo', {params})
} 

这个时候getUserInfo返回的就是CustomSuccessData<Res>类型的数据了。至此我们对axios简单的封装也就完成了。

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/8398.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot SpringBoot 开发实用篇 1 热部署 1.2 自动启动热部署

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇1 热部署1.2 自动启动热部署1.2.1 问题引入1.2.2 自动启动热部署1.2.…

Nacos2.1.1集群和持久化配置以及Nginx负载均衡分发(重点)

Nacos集群和持久化配置&#xff08;重点&#xff09; 1、官网说明 官网文档地址&#xff1a;https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html 对如上图片进行翻译如下 根据下图&#xff0c;需要配置MySQL作为持久化保存Nacos的信息 默认Nacos使用嵌入式数据库实现…

如何在 Visual Paradigm 中创建流程图丨使用教程

让我们看看如何在 Visual Paradigm 中绘制流程图。我们将在这里使用一个非常简单的流程图示例。完成本教程后&#xff0c;您可以扩展示例。 1.从主菜单中选择图表 > 新建。 2.在New Diagram窗口中&#xff0c;选择Flowchart并单击Next。 3.您可以从空图表开始&#xff0c;…

股价暴跌了74.5%后,乐信第三季度财报可能会低于市场预期

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 乐信2022年第三季度财报前瞻 此前&#xff0c;乐信(LX)曾在2022年11月10日宣布&#xff0c;公司将于2022年11月16日发布2022年第三季度财报。 猛兽财经认为&#xff0c;乐信2022年第三季度的实际财务业绩可能会令市场失望。…

最佳使用案例NO.1–干涉测量

为了营造今年的节日气氛&#xff0c;我们决定发布4份特别的新闻通讯展示我们的“2019最佳使用案例” 在VirtualLab Fusion中所实现的快速物理光学技术为著名的干涉仪的快速仿真提供了强有力的工具&#xff0c;从而使我们能够研究干涉图样中的相干和色散效应。 基于物理光学的Vi…

webpack5 Css 兼容性处理postcss-loader

postcss-loader | webpack 中文文档webpack 是一个模块打包器。它的主要目标是将 JavaScript 文件打包在一起&#xff0c;打包后的文件用于在浏览器中使用&#xff0c;但它也能够胜任转换&#xff08;transform&#xff09;、打包&#xff08;bundle&#xff09;或包裹&#xf…

震撼来袭,最具中国特色的微服务组件:新一代SpringCloud Alibaba

都说程序员工资高、待遇好&#xff0c; 2022 金九银十到了&#xff0c;你的小目标是 30K、40K&#xff0c;还是 16薪的 20K&#xff1f;作为一名 Java 开发工程师&#xff0c;当能力可以满足公司业务需求时&#xff0c;拿到超预期的 Offer 并不算难。然而&#xff0c;提升 Java…

【嵌入式 · 机器人】在 Linux 下安装 ROS2(机器人操作系统)

安装要求 安装 ROS2 需要保证 Linux 系统的发行版是 Ubuntu&#xff0c;并且不同版本的 Ubuntu 只能安装对应版本的 ROS2。 系统不是 Ubuntu 怎么办&#xff1f; 系统是 Mac OS 或 Windows&#xff1a;安装一个 Ubuntu 的虚拟机&#xff0c;在虚拟机里安装 ROS2。&#xff08;…

【深入浅出Spring6】第三期——作用域和工厂模式

一、Bean 的作用域 作用域以下用 scope 代替&#xff0c;在不同的应用情景下可以使用的参数值下是不同的我们以普通 Java 工程为例&#xff1a;可选的参数值有两个 singleton、prototype $ singleton 默认情况下&#xff0c;scope 的属性值就为 singleton,当然我们也可以显式…

一种清洁机器人设计及仿真

目 录 第1章 绪论 1 第2章 清洁机器人路径规划方法的分类及现状 5 2.1 基于事例的学习规划方法 5 2.2 基于环境模型的规划方法 6 2.3 基于行为的结构 7 第3章 清洁机器人的设计任务及方案分析 10 3.1 清洁机器人竞赛介绍 10 3.2 设计任务分解 11 3.3 清洁机器人任务分析及基于行…

Unity和UE4两大游戏引擎,你该如何选择?

目录 游戏引擎 2 ——> 难易区别 编程语言 3 ——> 游戏产品 UE4制作的游戏产品 Unity制作的游戏产品 产品类型 5 ——> 资源商店 6 ——> 人才需求 平均薪资 总结 游戏引擎 Unity和UE4都是游戏引擎&#xff0c;所谓游戏引擎就是集成了复杂功能的游戏…

短视频/直播+教育成为教育新常态

互联网时代&#xff0c;网络视听应用已经成为吸引新网民的主要力量&#xff0c;2020年&#xff0c;在新增的3625万网民中&#xff0c;有23.9%是为了使用网络视听应用而来。网络视听应用中&#xff0c;最受欢迎的当属短视频&#xff0c;已然成为新的国民级应用行业。 如今&…

Excel之数据透视NotePad之列编辑

在日常工作中&#xff0c;经常有数据处理的需求&#xff0c;要统计个数&#xff0c;这里就可以使用到工具的一些功能&#xff0c;如 Excel、Notepad&#xff0c;记录下来&#xff0c;分享功能。 一、Excel 软件 Excel 功能过于强大&#xff0c;下面只是简单分享下日常使用到的…

【附源码】计算机毕业设计JAVA大数据文章发布系统

【附源码】计算机毕业设计JAVA大数据文章发布系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA …

C++ Reference: Standard C++ Library reference: Containers: deque: deque: begin

C官网参考链接&#xff1a;https://cplusplus.com/reference/deque/deque/begin/ 公有成员函数 <deque> std::deque::begin C98 iterator begin(); const_iterator begin() const; C11 iterator begin() noexcept; const_iterator begin() const noexcept;返回指向开始…

基于五等均分法和Bob Stone法衡量RFM顾客价值

最近学习了衡量RFM模型的两种顾客价值的方法&#xff0c;即五等均分法和Bob Stone法。仅以此博客记录我的学习过程&#xff0c;后序学习到了其他方法再来补充。关于RFM实战案例可参考我的其他文章大数据分析案例-基于RFM模型对电商客户价值分析 大数据分析案例-用RFM模型对客户…

p53 与铁死亡有关?Nature 解锁新机制

众所周知&#xff0c;p53 是一种肿瘤抑制基因&#xff0c;被广泛称为“基因组的守护者”。自 1979 年被发现至今&#xff0c;p53 基因一直是分子生物学和肿瘤学的研究热门。据 Elie Dolgin 在 Nature 上发表的 “The most popular genes in the human genome” 统计&#xff0c…

wpf Viewport3D 学习

呈现 Viewport3D 元素的 2-D 布局范围内包含的 3-D 内容。就是3D画布&#xff0c; 继承 Object DispatcherObject DependencyObject Visual UIElement FrameworkElement Viewport3D 从FrameworkElement继承&#xff0c;FrameworkElement继承自UIElement&#xff1b; 该 V…

(五)Spring之Bean的作用域

文章目录环境单例的&#xff08;singleton&#xff09;多例的&#xff08;prototype&#xff09;其它scope自定义scope上一篇&#xff1a;&#xff08;四&#xff09;Spring对IoC的实现 环境 spring6里程碑版本的仓库 依赖&#xff1a;spring context依赖、junit依赖、log4j2…

【单片机基础】单片机中断和定时

中断什么是中断&#xff1f;中断IE寄存器中断查询次序例程定时器/计数器实现定时的方法&#xff1f;定时计数的概念什么是单片机定时器/计数器&#xff1f;定时器/计数器内部结构定时器的相关寄存器51单片机定时器初值计算方法详解例程中断 什么是中断&#xff1f; 中断是为使…