JavaScript
TypeScript在React项目中的使用实践总结
序言
本文会侧重于TypeScript(以下简称TS)在项目中与React的结合使用情况,而非TS的基本概念。关于TS的类型查看可以使用在线TS工具👉TypeScript游乐场
React元素相关
React元素相关的类型主要包括ReactNode
、ReactElement
、JSX.Element
。
ReactNode
。表示任意类型的React节点,这是个联合类型,包含情况众多;ReactElement/JSX
。从使用表现上来看,可以认为这两者是一致的,属于ReactNode
的子集,表示“原生的DOM组件”或“自定义组件的执行结果”。
使用示例如下:
const MyComp: React.FC<{ title: string; }> = ({title}) => <h2>{title}</h2>; // ReactNode const a: React.ReactNode = null || undefined || <div>hello</div> || <MyComp title="world" /> || "abc" || 123 || true; // ReactElement和JSX.Element const b: React.ReactElement = <div>hello world</div> || <MyComp title="good" />; const c: JSX.Element = <MyComp title="good" /> || <div>hello world</div>;
原生DOM相关
原生的 DOM 相关的类型,主要有以下这么几个:Element
、 HTMLElement
、HTMLxxxElment
。
简单来说: Element = HTMLElement + SVGElement
。
SVGElement
一般开发比较少用到,而HTMLElement
却非常常见,它的子类型包括HTMLDivElement
、HTMLInputElement
、HTMLSpanElement
等等。
因此我们可以得知,其关系为:Element > HTMLElement > HTMLxxxElement
,原则上是尽量写详细。
React合成事件相关
在 React 中,原生事件被处理成了React 事件,其内部是通过事件委托来优化内存,减少DOM事件绑定的。言归正传,React 事件的通用格式为[xxx]Event,常见的有MouseEvent
、ChangeEvent
、TouchEvent
,是一个泛型类型,泛型变量为触发该事件的 DOM 元素类型。
示例如下:
// input输入框输入文字 const handleInputChange = (evt: React.ChangeEvent<HTMLInputElement>) => { console.log(evt); }; // button按钮点击 const handleButtonClick = (evt: React.MouseEvent<HTMLButtonElement>) => { console.log(evt); }; // 移动端触摸div const handleDivTouch = (evt: React.TouchEvent<HTMLDivElement>) => { console.log(evt); };
与hooks的结合
在hooks中,并非全部钩子都与TS有强关联,比如useEffect就不依赖TS做类型定义,我们挑选比较常见的几个和TS强关联的钩子来看看。
useState
如果初始值能说明类型,就不用给 useState 指明泛型变量;
// ❌这样写是不必要的,因为初始值0已经能说明count类型 const [count, setCount] = useState<number>(0); // ✅这样写好点 const [count, setCount] = useState(0);
如果初始值是 null 或 undefined,那就要通过泛型手动传入你期望的类型,并在访问属性的时候通过可选链来规避语法错误。
interface IUser { name: string; age: number; } const [user, setUser] = React.useState<IUser | null>(null); console.log(user?.name);
useRef
这个 hook 比较特别,它通常有两种用途:
用来连接 DOM,以获取到 DOM 元素;
// 连接DOM,初始值赋值为null,不能是undefined,如要指明泛型变量需要具体到HTMLxxxElement // 如果我们确定inputRef.current在调用时一定是有值的,可以使用非空断言,在null后添加! const inputRef = useRef<HTMLInputElement>(null!); const handleClick = () => { inputRef.current.focus(); // 当然不用非空断言,用inputEl.current?.focus()可选链也是可以的 } return ( <input ref={inputRef} /> <button onClick={handleClick}>点击</button> )
2.用来存储变量,由于是存储在函数式组件的外部,比起 useState,它不会存在异步更新的问题,也不会存在由capture-value
特性引发的过时变量的问题,但是要注意赋值后由于ref引用没变,不会引起重渲染。
// 通过初始值来自动指明泛型变量类型 const sum = useRef(0); // 通过.current直接赋值 sum.current = 3; // 不存在异步更新问题 console.log(sum.current); // 3
useSelector
useSelector用于获取store中的状态,其第一个固定参数为函数,函数的入参即为store,而store的类型RootState需要在store中提前定义好,一种常见的定义如下:
在store.ts中:
const store = createStore(rootReducer); export type RootState = ReturnType<typeof rootReducer>;
使用时:
const { var1, var2 } = useSelector((store: RootState) => store.xxx);
自定义 hook
如果我们需要仿照 useState 的形式,返回一个数组出去,则需要在返回值的末尾使用as const,标记这个返回值是个常量,否则返回的值将被推断成联合类型。
const useInfo = () => { const [age, setAge] = useState(0); return [age, setAge] as const; // 类型为一个元组,[number, React.Dispatch<React.SetStateAction<number>>] };
redux相关
对于action的定义,我们可以使用官方暴露的AnyAction,放宽对于action内部键值对的限制,如下:
import { AnyAction } from "redux"; const DEF_STATE = { count: 0, type: 'integer' }; // 使用redux的AnyAction放宽限制 function countReducer(state = DEF_STATE, action: AnyAction) { switch (action.type) { case "INCREASE_COUNT": return { ...state, count: state.count + 1, }; case "DECREASE_COUNT": return { ...state, count: state.count - 1, }; default: return state; } } export default countReducer;
规约
子组件的入参命名为[组件名]Props,如:
// 比如当前组件名为InfoCard export interface InfoCardProps { name: string; age: number; }
interface接口类型以大写开头;
为后端接口的出入参书写interface,同时使用利于编辑器提示的jsdoc风格做注释,如:
export interface GetUserInfoReqParams { /** 名字 */ name: string; /** 年龄 */ age: number; /** 性别 */ gender: string; }
其他
键名或键值不确定如何处理?
// 表示键名不确定,键值限制为number类型 export interface NotSureAboutKey { [key: string]: number; } // 当键名键值都不确定时,以下接口对任何对象都是适用的 export interface AllNotSure { [key: string]: any; }
如何在接口中使用泛型变量?
所谓泛型,就是预定义类型。它的目的是:达到类型定义的局部灵活,提高复用性。我们通常会在接口中使用泛型,如:
// 通常,我们会为接口的泛型变量指定一个默认类型 interface IHuman<T = unknown> { name: string; age: number; gender: T; } // 其他地方使用时 const youngMan: IHuman<string> = { name: 'zhangsan', age: 18, gender: 'male' }
到此这篇关于TypeScript在React项目中的使用总结的文章就介绍到这了,更多相关TypeScript在React使用内容请搜索 以前的文章或继续浏览下面的相关文章希望大家以后多多支持 !