自动生成changelog

news2024/11/17 15:49:27

本文主要记录我如何在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简单的封装也就完成了。

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



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

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

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

相关文章

安全分析模型

安全分析模型自动化调优 MLOps&#xff08;Machine Learning Operations&#xff09;是一种人工智能 的工程实践&#xff0c;是面向机器学习项目的研发运营管理体系 。旨在实现 ML 管道的操作、ML 模型的部署和管理标准化&#xff0c;支持ML 模型的发布、激活、监控、性能跟踪…

Pytorch 学习之:关于 GPU 训练你必须知道的几件事

文章目录torchvision 下载的 pretrain 模型路径cuda 版本一定要适配多 cuda 训练 DataParallel 使用须知torchvision 下载的 pretrain 模型路径 使用 torchvision 来直接下载 pretrained 模型&#xff0c;有时候服务器的下载速度很慢&#xff0c;不如直接下载到本地之后传上去…

简单个人静态HTML网页设计作品 基于HTML+CSS+JavaScript仿小米手机网站 html静态在线购物商城网页制作

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

一种用于模拟电晕放电的高效半拉格朗日算法(Matlab代码实现)

目录 摘要 1 概述 2 数学模型与方法 3 讲解 3.1 测试1 3.2 测试2 3.3 测试3 3.4 测试4 4 Matlab代码实现 摘要 提出了一种无需通量校正的高效电晕放电模拟算法。称为位置-状态分离 (POSS) 方法的算法用于求解电晕放电建模中通常存在的以对流为主的连续性方程。所…

Java项目:ssm实验室预约维修管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目分为超级管理员、管理员、学生三种角色&#xff0c; 超级管理员角色包含以下功能&#xff1a; 通知管理、用户管理、实验室管理、实验室预…

字节跳动抖音本地生活前端招聘

一、认识Typescript (1)Javascript是一种动态类型的弱类型语言 Javascript超集&#xff1a; A.包含与兼容所有JS特性&#xff0c;支持共存 B.支持渐进式引入与升级 (2)TypeScript是一种静态类型的弱类型语言 静态类型的优点&#xff1a; A.可读性增强&#xff1a;基于语法…

Linux内存分配原理

Linux内存分配原理虚拟内存分区Linux内存分配时的maps文件brk()与sbrk()mmap()与munmap()mmap()munmap()内存分配过程更多资讯、知识&#xff0c;微信公众号搜索&#xff1a;“上官宏竹”。 虚拟内存分区 虚拟内存由于用途不同&#xff0c;分类也不尽相同&#xff0c;一般我们…

底层网络知识详解:从二层到三层-第6讲-交换机与VLAN:办公室太复杂,我要回学校

上一次,我们在宿舍里组建了一个本地的局域网LAN,可以愉快地玩游戏了。这是一个非常简单的场景,因为只有一台交换机,电脑数目很少。今天,让我们切换到一个稍微复杂一点的场景,办公室。 拓扑结构是怎么形成的? 我们常见到的办公室大多是一排排的桌子,每个桌子都有网口,…

C++11标准模板(STL)- 算法(std::minmax_element)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 返回范围内的最小元素和最…

非零基础自学Golang 第11章 文件操作 11.2 文件基本操作 11.2.1 文件创建与打开

非零基础自学Golang 文章目录非零基础自学Golang第11章 文件操作11.2 文件基本操作11.2.1 文件创建与打开第11章 文件操作 11.2 文件基本操作 在学习文件操作之前&#xff0c;我们先来了解一下Linux下的文件权限。 文件有三种权限&#xff0c;分别为读取、写入和执行&#x…

设计模式2 - 创建型模式

23种设计模式分析与见解开篇、UML、软件设计原则https://blog.csdn.net/lili40342/article/details/128358435创建型模式https://blog.csdn.net/lili40342/article/details/128358392结构型模式https://blog.csdn.net/lili40342/article/details/128358313行为型模式https://bl…

【MATLAB100个实用小技巧】——图形处理(67-75)

文章目录前言系列文章67. 图像的块操作68. 图形的过滤操作69. 图像的频率操作70. 函数变换71. RADON 函数变换72. 图像分析&#xff08;1&#xff09;73. 过滤图像74. 图像的区域处理75. 图像的颜色处置前言 &#x1f30f;MATLAB是一个功能强大的软件&#xff0c;我们可以利用M…

Mycat(3):mycat的安装

1、前言 使用mycat要安装JDK.不会的去看Linux里面的安装JDK的知识点&#xff0c;这是不再做说明 也可以直接使用yum install java-1.7.0-openjdk 因为mycat 基于jdk1.7开发的&#xff0c;所有最好安装jdk1.7的版本 重要说明&#xff1a; Mycat-server-1.6-release 版本发布的版…

手机技巧:苹果手机这8个实用小技巧

今天给大家大家分享苹果手机8个实用小技巧&#xff0c;你都会用吗&#xff1f; 1、快速搜索相机照片 相信大家的相册里的照片应该和我一样不说有几千张&#xff0c;几百张总是有的&#xff0c;有时候想找照片&#xff0c;又不想一张一张找怎么办&#xff1f;很简单&#xff0c…

Docker配置从私有仓库拉取镜像

修改Docker配置文件 修改docker的配置文件daemon.json&#xff0c;如果配置文件不存在则直接创建。 vim /etc/docker/daemon.json文件内容如下&#xff0c;其中insecure-registries属性值“registry.luntek-inc.com”代表私有仓库的地址&#xff0c;你需要将registry.luntek-…

JavaSE13-方法

目录 1.方法的基本用法 1.1.什么是方法 1.2.方法定义语法 1.3.方法调用的执行过程 1.4.实参和形参的关系 1.5.方法的返回值 2.方法重载 2.1.方法重载定义 2.2.代码示例 3.方法递归 3.1.方法递归定义 3.2.方法递归使用条件 3.3.递归与非递归优劣比较 3.4.递归执行…

[附源码]Nodejs计算机毕业设计基于网络C++实验管理系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

SpringBoot+Vue项目部门人事管理系统的设计与实现

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

Vit 中的 Token 改进版本:Token Mreging: Your Vit But Faster 论文阅读笔记

Vit 中的 Token 改进版本&#xff1a;Token Mreging: Your Vit But Faster 论文阅读笔记一、Abstract二、引言三、相关工作3.1 有效的 Transformer3.2 Token 的减少3.3 Token 的联合四、Token 融合4.1 策略4.2 Token 相似性4.3 双边软匹配4.4 追踪 Token 的尺寸4.5 采用融合操作…

[附源码]计算机毕业设计Python餐馆点餐管理系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…