前言
java语言对于数据的处理,有各种类型的约束,在js中没有类型约束,我们每次在写代码的时候经常不确定书写的格式类型,编译器无法及时的判断我们使用的正确与否,所以增加了TypeScript就可以使得我们更稳健,更牢靠的相信我们写下去的代码。
基本类型
boolean
: 布尔类型true/false
string
: 字符串number
: 数字类型,整数/小数bigint
: 大整数symbol
:类似于字符串,但独一无二,Symbol()
函数生成object
: 对象undefined
:未定义null
: 为空
TypeScript
继承了 JavaScript
的类型设计,以上8种类型可以看作 TypeScript
的基本类型。
typescript
const x:string = 'hello'; // 定义字符串 foo 为string 类型
const x:boolean = true; // 定义boolean类型
const x:number = 123; // 数字整数类型
const y:number = 3.14; // 数字浮点类型
const x:bigint = 123n; // 大整数类型
const x:object = { foo: 123 } //对象类型
// 特殊场景 undefined 和 null
let x:undefined = undefined;
const x:null = null;
Object和object区别
Object类型代表 JavaScript 语言里面的广义对象, 注意: 但除了undefined
和null
这两个值不能转为对象
ini
obj = true;
obj = 'hi';
obj = 1;
obj = { foo: 123 };
obj = [1, 2];
obj = (a:number) => a + 1;
let obj:Object;
obj = undefined; // 报错
obj = null; // 报错
object类型代表 JavaScript 里面的狭义对象, 只包含对象、数组和函数,不包括原始类型的值
ini
let obj:object;
// 可以是对象,数组,函数
obj = { foo: 123 };
obj = [1, 2];
obj = (a:number) => a + 1;
obj = true; // 报错
obj = 'hi'; // 报错
obj = 1; // 报错
值/联合类型(|
)
值类型: 单个值的类型
csharp
let x:'hello'; // 类型是字符串hello,无法赋值其他字符串值
const x = 'https'; // 推断类型为https,因const一旦声明就不能改变
联合类型 : 多个类型组成的一个新类型,使用符号|表示。用A|B
表示,任何一个类型只要属于A
或B
,就属于联合类型A|B
ini
let x:string|number; // x既可以是字符串类型,也可以是数字类型
x = 'sdf'
x = 123
通过联合类型可以与值类型相结合,表示一个变量的值有若干种可能
bash
let gender:'male'|'female';
let setting:true|false;
交叉类型(&
)
指的多个类型组成的一个新类型, 主要用途是表示对象的合成,表明同时具有几种属性
csharp
let obj: { foo: string } & { bar: string }; //即有foo,又有bar
obj = {
foo: 'hello',
bar: 'world'
};
type
(定义别名)
可以用于定义一个类型的别名,如下用于了将number
类型命名为Age
别名,更具有意义。
ini
type Age = number;
let age:Age = 55;
注意: 不能重复声明,作用域只在块级作用域。
一般使用的时候通过定义对象模板
ini
type Country = {
name: string;
capital: string;
}
const country: Country={name:'gjc',capital:'sdf'}
有关type的扩展类型需要借助 交叉类型
go
type MyStr = string & {type: 'new'};
keyof
运算符
返回联合字面量类型
lua
interface Person {
name: string
age: number
location: string
}
type SomeNewType = keyof Person // "name" | "age" | "location"
typeof
运算符
JavaScript
语言中,typeof
运算符是一个一元运算符,返回一个字符串,代表操作数的类型
javascript
typeof undefined; // "undefined"
typeof true; // "boolean"
typeof 1337; // "number"
typeof "foo"; // "string"
typeof {}; // "object"
typeof parseInt; // "function"
typeof Symbol(); // "symbol"
typeof 127n // "bigint"
在ts中,返回ts类型
ini
const a = { x: 0 };
type T0 = typeof a; // { x: number }
type T1 = typeof a.x; // number
特殊类型
any
类型 : 表示没有任何限制,该类型的变量可以赋予任意类型的值unknown 类型
: 类型不确定,是严格版的any
,只有明确unknown
变量的实际类型,才允许使用它never
类型 : 该类型为空,不包含任何值 , 逻辑处理分支的兜底使用
ini
let x:any;
x = 1; // 正确
x = 'foo'; // 正确
x = true; // 正确
let x:unknown;
x = true; // 正确
x = 42; // 正确
x = 'Hello World'; // 正确
let v1:unknown = { foo: 123 };
v1.foo // 报错, 无法使用
// 明确缩小类型可以用
let a:unknown;
if (typeof a === 'number') {
let r = a + 10; // 正确
}
// 兜底使用
function fn(x:string|number) {
if (typeof x === 'string') {
// ...
} else if (typeof x === 'number') {
// ...
} else {
x; // never 类型
}
}
数组
数组的类型有两种写法:
- 第一种写法是在数组成员的类型后面,加上一对方括号。
- 第二种写法 是使用
TypeScript
内置的 Array 接口
数组声明
-
指定类型的数组形式
jsxlet arr:number[] = [1, 2, 3];
-
指定多个类型的可能性的形式(联合类型)
jsxlet arr:(number|string)[];
-
任意类型的数组 ,
any
jsxlet arr:any[];
-
Array
写法jsxlet arr:Array<number> = [1, 2, 3]; let arr:Array<number|string>;
数组类型推断
如果数组变量没有声明类型,TypeScript 就会推断数组成员的类型。这时,推断行为会因为值的不同,而有所不同。
jsx
const arr = [];
arr // 推断为 any[]
arr.push(123);
arr // 推断类型为 number[]
arr.push('abc');
arr // 推断类型为 (string|number)[]
注意: 数组有了初始值,就会推断出指定的类型,其他类型不允许了
jsx
// 推断类型为 number[]
const arr = [123];
arr.push('abc'); // 报错
只读数组
TypeScript 允许声明只读数组,方法是在数组类型前面加上readonly
关键字
jsx
const arr:readonly number[] = [0, 1];
arr[1] = 2; // 报错
arr.push(3); // 报错
delete arr[0]; // 报错
元组
元组(tuple
)是 TypeScript 特有的数据类型,JavaScript
没有单独区分这种类型。它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同
php
const s:[string, string, boolean] = ['a', 'b', true];
与数组不同的是
成员类型写在方括号里面的就是元组,写在外面的就是数组
less
// 数组
let a:number[] = [1];
// 元组
let t:[number] = [1];
成员可选 ?
元组成员的类型可以添加问号后缀(?
),表示该成员是可选的
注意 :所有可选成员必须在必选成员之后
ini
let a:[number, number?] = [1];
不确定数量
typescript
type t1 = [string, number, ...boolean[]];
对象
声明对象的类型
对象类型的最简单声明方法,就是使用大括号表示对象,在大括号内部声明每个属性和方法的类型
jsx
const obj:{x:number;y:number;} = { x: 1, y: 1 };
注意: 一旦声明了类型,对象赋值时,就不能缺少指定的属性,也不能有多余的属性,同样地,也不能删除类型声明中存在的属性,修改属性值是可以的
可选属性 ?
如果某个属性是可选的(即可以忽略),需要在属性名后面加一个问号。
jsx
const obj: {
x: number;
y?: number;
} = { x: 1 };
只读属性 readonly
声明对象中的age
属性为只读
jsx
const person:{
readonly age: number
} = { age: 20 };
person.age = 21; // 报错
解构赋值
从对象中提取属性的同时,追加其类型
jsx
const {id, name, price}:{
id: string;
name: string;
price: number
} = product;
函数
function函数
定义函数的入参类型和出参类型
typescript
// 声明入参num为number类型,出参为string类型
function toString(num:number):string {
return String(num);
}
注意: 可以不定义入参num
的类型,ts
能推断出类型,没有足够信息,推断为any
类型,返回值也可以不写,ts能推断出来
箭头函数
typescript
// 箭头函数形式的 函数类型
const IdGenerator = (name: string, id: number) => string {
return name + id;
};
=====> 等同于 function函数
function IdGenerator (name: string, id: number): string {
return name + id;
}
函数可选参数 ?
scss
function f(x?:number) {
// ...
}
f(); // OK
f(10); // OK
参数x
后面有问号,表示该参数可以省略, 该参数的类型实际上是原始类型|undefined
函数参数默认值 =
设置了默认值的参数,就是可选的。如果不传入该参数,它就会等于默认值
注意: 可选参数和默认值不能同时使用
typescript
function createPoint(x:number = 0, y:number = 0):[number, number] {
return [x, y];
}
函数参数结构 {} /[]
传入的变量里面是对象/数组的,可以定义一一对应的解构
typescript
// 对象解构
function sum( { a, b, c }: { a: number; b: number; c: number } ) {
}
// 数组解构
function f([x, y]: [number, number]) {
}
rest 参数 (剩余)
rest
参数表示函数剩余的所有参数,它可以是数组(剩余参数类型相同),也可能是元组(剩余参数类型不同)。
php
// rest 参数为数组
function joinNumbers(...nums:number[]) {
// ...
}
// rest 参数为元组
function f(...args:[boolean, number]) {
// ...
}
只读 readonly
如果函数内部不能修改某个参数,可以在函数定义时,在参数类型前面加上readonly
关键字,表示这是只读参数。
php
function arraySum(arr:readonly number[]) {
arr[0] = 0; // 报错
}
never 类型(兜底)
函数fail()
会抛出错误,不会正常退出,所以返回值类型是never
typescript
function fail(msg:string):never {
throw new Error(msg);
}
函数sing()
会永远执行,不会返回,所以返回值类型是never
javascript
const sing = function():never {
while (true) {
console.log('sing');
}
};
interface
interface
是对象的模板,可以看作是一种类型约定,中文译为"接口"。使用了某个模板的对象,就拥有了指定的类型结构
css
interface Person {
firstName: string;
lastName: string;
age: number;
}
=======> 对应使用
const p:Person = {
firstName: 'John',
lastName: 'Smith',
age: 25
};
interface 可以表示对象的各种语法,属性
对象属性
x和y都是对象的属性,分别使用冒号指定每个属性的类型
typescript
interface Point {
x: number;
y: number;
b?: string; // 可选
readonly a: string; // 只读
}
对象的属性索引
属性索引共有string
、number
和symbol
三种类型。
注意: 一个接口中,最多只能定义一个字符串索引 , 字符串索引会约束该类型中所有名字为字符串的属性,一个接口中最多也只能定义一个数值索引, 如果同时定义了字符串索引和数值索引,那么数值索引必须服从于字符串索引
typescript
interface A {
[prop: string]: number; // 约束属性名称为字符串,对应属性值得是number
a: boolean; // 编译错误
b:1 //正确
}
interface A {
[prop: number]: string; // 约束属性名称为数值,对应属性值得是字符串
}
const obj:A = ['a', 'b', 'c']; //对应数组就是,名称是数值,值是字符串
// 数值索引必须兼容字符串索引的类型声明,都是number
interface B {
[prop: string]: number;
[prop: number]: number; // 正确
}
对象方法
typescript
// 写法一
interface A {
f(x: boolean): string;
}
// 写法二
interface B {
f: (x: boolean) => string;
}
// 写法三
interface C {
f: { (x: boolean): string };
}
// 对应的使用
const i : interface={
f:(x: boolean) : string => return ` ${x} hello`
}
独立函数模板
接口Add声明了一个函数类型
typescript
interface Add {
(x:number, y:number): number;
}
const myAdd:Add = (x,y) => x + y;
继承 extends
extends
关键字会从继承的接口里面拷贝属性类型,这样就不必书写重复的属性。interface
允许多重继承
注意: 如果子接口与父接口存在同名属性,那么子接口的属性会覆盖父接口的属性。子接口与父接口的同名属性必须是类型兼容的
typescript
interface Shape {
name: string;
}
interface Circle extends Shape {
radius: number;
}
interface
与 type
的异同
很多对象类型即可以用 interface
表示,也可以用 type
表示。而且,两者往往可以换用,几乎所有的 interface
命令都可以改写为 type
命令
主要区别:
type
能够表示非对象类型,而interface
只能表示对象类型(包括数组、函数等)。interface
可以继承其他类型,type
不支持继承, 注意继承可以相互换用的,interface
可以extends
实现type
,type
可以&
来扩展interface
- 同名
interface
会自动合并,同名type
则会报错。 interface
不能包含属性映射(mapping),type
可以this
关键字只能用于interface
type
可以扩展原始数据类型,interface
不行interface
无法表达某些复杂类型(比如交叉类型和联合类型),但是type
可以
ini
// 同名`interface`会自动合并
interface A { foo:number };
interface A { bar:number };
const obj:A = {foo: 1,bar: 1};
// `type` 可以扩展原始数据类型
type MyStr = string & {
type: 'new'
};
// type表达某些复杂类型
type A = { /* ... */ };
type B = { /* ... */ };
type AorB = A | B;
使用上的抉择:
如果有复杂的类型运算,那么没有其他选择只能使用type;一般情况下,interface灵活性比较高,便于扩充类型或自动合并,建议优先使用
泛型
使用泛型的主要目的是为了处理不特定类型的数据,使得代码可以适用于多种数据类型而不失去类型检查
泛型函数
csharp
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let result = identity<string>("Hello");
let numberResult = identity<number>(42);
泛型接口
sql
interface Pair<T, U> {
first: T;
second: U;
}
// 使用泛型接口
let pair: Pair<string, number> = { first: "hello", second: 42 };
console.log(pair); // 输出: { first: 'hello', second: 42 }
泛型约束
限制泛型的类型范围,泛型实现指定的类型
typescript
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(arg.length);
}
logLength({length:12})
泛型默认值
可以给泛型设置默认值,使得在不指定类型参数时能够使用默认类型
ini
function defaultValue<T = string>(arg: T): T {
return arg;
}
// 使用带默认值的泛型函数
let result1 = defaultValue("hello");
枚举
把相关的常量放在一个容器里。
数值枚举
ini
enum Color {
Red, // 0
Green, // 1
Blue // 2
}
let c = Color.Red // 0
let c1:Color = Color.Red;
let c2:Color = Color.Green;
let c3:number = Color.Red
注意: 由于 Enum 结构编译后是一个对象,所以不能有与它同名的变量(包括对象、函数、类等)
Enum 成员值都是只读的,不能重新赋值
arduino
const enum Color {
Red, // 0
Green, // 1
Blue // 2
}
字符串枚举
可以定义一组相关字符串的集合
ini
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
let s = Direction.Up; // UP
let s1:Direction = Direction.Up
keyof
运算符搭配
keyof
运算符可以取出 Enum 结构的所有成员名,作为联合类型返回。可以和typeof
搭配使用获取枚举 。不能直接使用 keyof xxEnum
ini
enum MyEnum {
A = 'a',
B = 'b'
}
// 'A'|'B'
type Foo = keyof typeof MyEnum;
typeof MyEnum
会将MyEnum
当作一个值处理,从而先其转为对象类型,就可以再用keyof
运算符返回该对象的所有属性名
反向映射
数值Enum
存在反向映射,即可以通过成员值获得成员名 ,注意字符串Enum
不行
ini
enum Direction{
UP = 1,
DOWN = 2,
}
Direction[Direction.UP] // 'UP'
模块
import
在a文件中导出一个类型模板和变量
typescript
// a.ts
export interface A {
foo: string;
}
export let a = 123;
在b文件中导入有两种方法,第一种是直接在对于类型前面加上type
关键字,第二种就是单独使用导入类型
typescript
import { type A, a } from './a'; //第一种
import type { A } from './a'; // 第二种
这么做是为了区分类型还是正常接口
export
导出也是分两种写法,一种写在一起type,一种是分开分别type
bash
type A = 'a'
type B = 'b'
export type {A,B}
export {type A,type B}
搭配React的使用
函数组件
声明类型对象模板,为了约束函数组件接受上层组件的传值props的类型
typescript
interface IProps {
name: string
}
const App = (props: IProps) => {
const {name} = props;
return (
<div>{name}</div>
);
}
export default App;
使用 React.FunctionComponent<P={}>
来定义,简写为React.FC<P={}>
,使用它可以 自动推断 children
属性 ,不用手动声明
javascript
// 重写上面的案例
const App:React.FC<IProps> (props) => {
const {name} = props;
return (
<div>{name}</div>
);
}
React 内置类型
JSX.Element
: 表示一个 JSX 元素
javascript
const jsx = <div>hello</div>
React.ReactElement
: 表示一个 JSX 元素
ini
const element: React.ReactElement = <div>Hello World</div>;
React.ReactNode
: 表示任何可以被渲染的内容,包括 JSX 元素、字符串、数字、null、布尔值等
ini
const content: React.ReactNode = 'This is a text node';
const element: React.ReactNode = <div>{content}</div>;
Hooks
useState
: 声明的时候定义类型 , 如果初始值为null,需要显式地声明 state 的类型
typescript
const [count, setCount] = useState<number>(1)
const [person, setPerson] = useState<Person>({ name:"gjc", age:1 });
const [count, setCount] = useState<number|null>(null);
useRef
: 引用dom
元素
csharp
// inputEl 只能用于 input elements
const inputEl = React.useRef<HTMLInputElement>(null);
// TS 会检查 inputEl 类型, current需要自行判断
inputEl.current?.focus()