前提准备:
环境配置
安装node.js
官网安装:当前使用版本18.15.0
安装新的react应用:
运行命令新建react-app
npx create-react-app study-ts-app
当前版本:
- “react”: “^18.2.0”,
- “react-dom”: “^18.2.0”,
如果出现如下问题,无法安装依赖
可以尝试运行npm i react,如果报错,执行推荐命令
运行命令:
sudo chown -R 502:20 "/Users/rcdl/.npm"
命令详解参考:https://blog.csdn.net/wangcheeng/article/details/128165359
然后继续执行create-react-app
安装typeScript
npm i typescript --save
- “typescript”: “^5.0.4”,
在react中使用ts
参考:https://juejin.cn/post/7021674818621669389
在类组件
参数没有都可以被省略
// 使用方式:React.Component<P, S={}>{...}
// 示例:
import * as React from 'react'
interface IProps {
name: string;
}
interface IState {
age: number;
}
class Hello extends React.Component<IProps, IState> {
state = {
age: 18
};
render() {
return (
<div>
{this.state.age}
{this.props.name}
</div>
);
}
}
export default Hello;
React.Component
中P是props
类型的定义,S是state
类型的定义,都是泛型接口
React.PureComponent<P, S={} SS={}>{...}
React.PureComponent
前两个值含义相同,第三个值代表getSnapshotBeforeUpdate
的返回值
在函数组件
在函数组件中,也可以接收一个参数(可省),参数表示props
的类型,示例如下
interface IProps {
name: string
}
const HelloFunction = (props: IProps) => {
const {name} = props;
return (
<div className="App">
<h1>hello world</h1>
<h2>{name}</h2>
</div>
);
}
export default HelloFunction;
定义函数组件的第二种方式,React.FunctionComponent<P={}>
来定义,也可以使用其简写React.FC<P={}>
,参数可省,示例如下
interface IProps {
name: string
}
const App: React.FC<IProps> = (props) => {
const {name} = props;
return (
<div className="App">
<h1>hello world</h1>
<h2>{name}</h2>
</div>
);
}
export default App;
当使用这种形式来定义函数组件时,props中默认会带有children属性,它表示该组件在调用时,其内部的元素,来看一个例子,首先定义一个parent组件,组件中引入了Child1和Child2组件:
import Child1 from "../childFunction/child1";
import Child2 from "../childFunction/child2";
interface IProps {
name: string;
}
const Parent: React.FC<IProps> = (props) => {
const { name } = props;
return (
<Child1 name={name}>
<Child2 name={name} />
my test content
</Child1>
);
};
export default Parent;
child1组件如下
interface IProps {
name: string;
}
const Child1: React.FC<React.PropsWithChildren<IProps>> = (props) => {
const { name, children } = props;
console.log(children);
return (
<div className="App">
<h1>hello child1</h1>
<h2>{name}</h2>
</div>
);
};
export default Child1;
这里的props.children是默认属性,输出结果是children的内容,就是parent组件中child1包裹起来的child2对象和文字
useState
如定义一个数字类型,有初始值可以直接写
const [flag, setFlag] = useState<number>(1)
没有初始值或初始值为null可以写成:
const [flag, setFlag] = useState<number | null>()
如初始化是一个对象
在确定对象中的属性类型和数量时,可以使用泛型定义
interface list {
title: string,
type: boolean
}
const [list, setList] = useState<list>({ title: '', type: false })
setList({ title: '', type: true })
在未知对象属性数量和类型时
???
其他类型见下方扩展:ts基本类型
useRef
react写法
const frameRef = useRef(null);
ts写法
// 改变值时类型null不加也没显示报错--存疑?
const frameRef = useRef<HTMLInputElement | null>(null)
实例:
const myText = useRef<HTMLInputElement | null>(null);
console.log(myText.current) // null
const changeText = (e) => {
// myText的值会随着input中内容的输入实时改变
myText.current.innerHTML = e.target.value;
}
return (
<div>
<input type="text" onChange={(e) => changeText(e)} />
<h2 ref={myText} />
</div>
);
useMemo
useMemo返回的是一个值,所以需要有返回类型,如果返回类型和定义类型不同会报错
注:useCallback不需要定义返回值类型,因为他返回的是一个函数
const calculatedValue1 = useMemo<number>(() => a ** 2, [a]);
事件处理
常见的Event事件对象如下:
- 剪切板事件对象:ClipboardEvent<T = Element>
- 拖拽事件对象:DragEvent<T = Element>
- 焦点事件对象:FocusEvent<T = Element>
- 表单事件对象:FormEvent<T = Element>
- Change事件对象:ChangeEvent<T = Element>
- 键盘事件对象:KeyboardEvent<T = Element>
- 鼠标事件对象:MouseEvent<T = Element, E = NativeMouseEvent>
- 触摸事件对象:TouchEvent<T = Element>
- 滚轮事件对象:WheelEvent<T = Element>
- 动画事件对象:AnimationEvent<T = Element>
- 过渡事件对象:TransitionEvent<T = Element>
例如:
MouseEvent
是上面的鼠标事件对象(click事件)HTMLDivElement
代表该函数作用于一个div元素
const handleChangeCurrent = (e: React.MouseEvent<HTMLDivElement>) => {}
事件处理函数的类型声明
type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void }["bivarianceHack"];
type ReactEventHandler<T = Element> = EventHandler<SyntheticEvent<T>>;
// 剪切板事件处理函数
type ClipboardEventHandler<T = Element> = EventHandler<ClipboardEvent<T>>;
// 复合事件处理函数
type CompositionEventHandler<T = Element> = EventHandler<CompositionEvent<T>>;
// 拖拽事件处理函数
type DragEventHandler<T = Element> = EventHandler<DragEvent<T>>;
// 焦点事件处理函数
type FocusEventHandler<T = Element> = EventHandler<FocusEvent<T>>;
// 表单事件处理函数
type FormEventHandler<T = Element> = EventHandler<FormEvent<T>>;
// Change事件处理函数
type ChangeEventHandler<T = Element> = EventHandler<ChangeEvent<T>>;
// 键盘事件处理函数
type KeyboardEventHandler<T = Element> = EventHandler<KeyboardEvent<T>>;
// 鼠标事件处理函数
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
// 触屏事件处理函数
type TouchEventHandler<T = Element> = EventHandler<TouchEvent<T>>;
// 指针事件处理函数
type PointerEventHandler<T = Element> = EventHandler<PointerEvent<T>>;
// 界面事件处理函数
type UIEventHandler<T = Element> = EventHandler<UIEvent<T>>;
// 滚轮事件处理函数
type WheelEventHandler<T = Element> = EventHandler<WheelEvent<T>>;
// 动画事件处理函数
type AnimationEventHandler<T = Element> = EventHandler<AnimationEvent<T>>;
// 过渡事件处理函数
type TransitionEventHandler<T = Element> = EventHandler<TransitionEvent<T>>;**
扩展:ts基本类型
布尔值true/false:boolean
let isDone: boolean = false;
数字:number
ts支持二进制、八进制、十进制和十六进制字面量。
let age: number = 6;
字符串:string
let name: string = 'jack';
null和undefined
let u: undefined = undefined;
let n: null = null;
Symbol
Symbol是不可改变且唯一的。
let sym: symbol = Symbol()
数组:[]
定义数组的两种方式:
1 在元素类型后接上 []
2 使用数组泛型,Array<元素类型>
let list: number[] = [1, 2, 3]; //定义各项都是数字的数组。
let list: Array<number> = [1, 2, 3];
let list: string[] = ['1', '2', '3']; //定义各项都是字符串的数组。
let list: Array<string> = ['1', '2', '3'];
元组 Tuple
元组类型可以定义已知元素数量和各元素类型的数组。
当访问已知索引的元素,会得到正确的类型;当访问越界的元素,会使用联合类型代替。
let data: [string, number];
data = ['jack', 20];
枚举:enum
枚举类型可以为一组数值赋予名字。默认情况下,元素从0开始编号,也可以手动指定元素的值。
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
枚举类型还可以根据枚举的值得到他的名字。
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
console.log(colorName); // Green
Any
any用于在不确定变量类型时使用。比如用户输入或第三方代码库引用,或者定义不同类型元素的数组。
ts默认这些值是不需要类型检查直接编译。
let notSure: any = 4;
notSure = "a string";
let list: any[] = [1, true, "jack"];
Unknown
与any类似,但是会做类型检查再编译。
function divide(param: unknown) {
// return param / 2; // 不知道params的类型使用了运算符会编译错误
return param as number / 2;
}
Void
void表示没有任何类型。void类型只能赋值为null或undefined。
当一个函数没有返回值或者返回值是undefined时,他的返回值类型可以用void。
function warnUser(): void {
console.log("This is my warning message");
}
Never
never类型表示永不存在的值的类型;如:
1 函数执行时抛出异常
2 函数中的代码无限循环,到不了返回值那一步
never是任何类型的子类型,可以赋值给任何类型。
// 异常
function fn(msg: string): never {
throw new Error(msg)
}
// 死循环
function fn(): never {
while (true) {}
}
Object
定义object对象类型的方式:
1 直接定义类型为object
2 将对象内属性的类型都逐个定义
let obj: object = { name: 'lin', age: 18 };
let person: {
age: number, name: string
} = { age: 12, name: 'jack' };
类型扩展
类型断言
当绝对确认一个实体的类型时使用,使用方式有两种;
1.实体 as 类型(支持JSX)
2.<类型>实体
let someValue: any = "this is a string";
let strLength1: number = (someValue as string).length;
or
let strLength2: number = (<string>someValue).length;
类型别名
当定义的类型名过长或复杂时可以定义个别名,方便书写。
type s = string | number;
const str: s = 'jack';
也可以写成对象形式,定义具体的变量类型
type A = { name: string }
其他常用类型
联合类型
联合类型是由两个及以上种类型组成的类型,表示变量可以是其中的任意一种类型;且只能访问他们共有的方法和属性。
let timer: number | string | null = 1;
内置类型
ECMAScript的内置对象
const nums: Array<number> = [1,2,3]
const date: Date = new Date()
const err: Error = new Error('Error!');
const reg: RegExp = /abc/;
字面量类型
用type定义成一个字面量然后使用,能够减少代码量,降低耦合性。
type ButtonSize = 'mini' | 'small' | 'normal' | 'large'
const btnSize: ButtonSize = 'small'
函数
创建
ts可以创建有名字的函数和匿名函数。
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function (x: number, y: number): number {
return x + y;
};
let myAdd1: (x: number, y: number) => number =
function (x: number, y: number): number {
return x + y;
};
传参
已知参数个数
传递给一个函数的参数个数必须与函数期望的参数个数一致,不一致会报错。
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error
let result3 = buildName("Bob", "Adams");
如果参数可传可不传,可以在参数名后加 ? ,实现参数可选功能,没传默认是undefined。
可选参数必须放在必须参数后面。
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob");
let result2 = buildName("Bob", "Adams", "Sr."); // error, 应有 0-2 个参数,但获得 3 个。
let result3 = buildName("Bob", "Adams");
如果是带默认值的参数放在必须参数前,要求必须明确的传入undefined值获得默认值。
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result4 = buildName(undefined, "Adams");
未知参数个数
如果传参个数不确定,可以把所有参数收集到一个变量中。
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
泛型
泛型的关键目的是给成员之间提供有意义的约束。成员可以是:
1 类的实例成员
2 类的方法
3 函数参数
4 函数返回值
使用泛型可以创建可重用的通用组件,一个组件可以支持多种类型的数据,在定义的时候不预先指定具体的类型,而是在使用的时候再指定。不同于any,他不会丢失信息,传入和返回类型相同。
定义泛型函数
给函数添加类型变量T(type),可以捕获到用户传入的类型;并使用T作为返回值类型。
其他常见泛型变量:
1 K(Key):表示对象中的键类型;
2 V(Value):表示对象中的值类型;
3 E(Element):表示元素类型。
function identity<T>(arg: T): T {
return arg;
}
使用泛型函数
使用泛型函数的方法有两种
1 传入所有参数和参数类型
2 传入参数,不传类型,编译器会根据传入的参数自动确定T的类型
let output = identity<string>("myString");
let output = identity("myString");
泛型接口
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
返回两种类型的对象
// 创建一个Identities 接口
interface Identities<V, M> {
value: V,
message: M
}
// 将 Identities 接口作为 identity 函数的返回类型
function identity<T, U> (value: T, message: U): Identities<T, U> {
console.log(value + ": " + typeof (value));
console.log(message + ": " + typeof (message));
let identities: Identities<T, U> = {
value,
message
};
return identities;
}
console.log(identity(68, "Semlinker"));
// 68: number
// Semlinker: string
// {value: 68, message: "Semlinker"}
泛型类
泛型类与泛型接口类似。 泛型类使用(<T, …>)括起泛型类型,定义多个类型变量,跟在类名后面。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
泛型约束
用于限制每个类型变量接受的类型数量。
如要求类型变量上对应的类型存在某些属性(length,push),在没有明确T类型时是不能使用length属性的。
function identity<T>(arg: T): T {
console.log(arg.length); // Error
return arg;
}
可以让类型变量extends一个含有所需属性(length)的接口(Length),可以获取到length属性。
但是如果调用identiy时传入参数不带有length属性会报错。
interface Length {
length: number;
}
// 也可以使用多种约束类型
// <T extends Length, Type2, Type3, ...>
function identity<T extends Length>(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}