原始类型
/**
* 原始数据类型
*/
const a: string = "a";
const b: number = 123; // NaN, Infinity
const c: boolean = true; // false
// 严格模式下,不能是null
const d: string = null;
// 也有单独check null的选项:strictNullChecks
const e: void = undefined; // 严格模式下只能是undefined,非严格模式下可以是null
const f: null = null;
const g: undefined = undefined;
const h: Symbol = Symbol();
标准库声明
比如我在tsconfig文件里的target设置为es2015,直接我声明的Symbol类型的变量就会直接报错:
想解决就很简单,把target修改为es2105就好,但是为什么?
实际上就是因为这是JavaScript内置对象,Symbol是es6中新增的,所以内置对象是找不到的。
实际上任何一个在es6中新增的对象,我们直接使用都会遇到这种问题,比如Promise
另外还有一种办法那就是:在lib数组中添加es2015,另外如果如此添加lib中,console就又会报错了:
这是因为lib只设置了es2015,默认的标准库都被覆盖了,我们需要添加回来默认的标准库,需要注意的是在Typescript中把DOM和BOM都合并到DOM标准库中了,所以我们只需要添加DOM就行:
这样一来就搞定了报错问题。
所谓标准库实际上就是内置对象声明的所对应的声明文件
在代码中使用内置对象必须要使用对应的标准库,否则Typescript找不到对应的内置类型,就会报错。
中文错误信息
报错的消息显示中文会对国内开发者比较友好,如何使用?
yarn tsc --locale zh-CN
如此这般:
vscode切换中英文也是在settings里搜索locale
切换这里的选项即可,但是不推荐这里这样做,因为很多错误提示我们一般通过chrome在stackoverflow或者其他类似的平台上可以直接寻求帮助,用英文版更加方便
Object 类型
export default {};
// 不能接受原始值
// object类型不单指普通对象
const foo: object = () => {}; //{} //[]
// 我们需要普通对象类型,就用类似对象字面量的语法:不能多不能少
const obj: { foo: number; func: () => void } = { foo: 1, func: () => {} };
// 限制对象的内容应该用Typescript更专业的接口功能。
数组类型
const arr1: Array<number> = [1, 2, 3];
const arr2: Array<string> = ["1", "2", "3"];
function sum(...args: number[]) {
return args.reduce((prev, value) => {
return prev + value;
}, 0);
}
console.log(sum(...arr1));
export default {};
元祖类型
明确元素数量和类型的数组,类型不必要完全相同,在Typescript中可以使用类似数组字面量的语法去定义元祖类型
const tuple: [number, string, string] = [1, "string", "s"];
// 访问方式1:跟调用数组一样差不多
const age = tuple[0];
const str = tuple[1];
const [ageBase, strBase] = tuple;
// 方式2:解构
console.log(ageBase, strBase, age, str);
// es2017的方法Object.entries的返回值也是一个元祖
Object.entries({ foo: 123, bar: "s" });
枚举类型
export default {};
// 枚举
// enum StatusNumber {
// ZERO = 0,
// One = 1,
// TWO = 2,
// }
// 枚举也可以不用等号指定值
// 不指定默认就是从0开始累加,如果指定了一个成员的值
// 后面的值都会逐渐累加
enum StatusNumber {
ZERO,
One,
TWO,
}
// enum StatusNumber {
// ZERO = 1,
// One, //2
// TWO, //3
// }
// 如果枚举值是字符串的话,成员每一个都需要指定具体的字符串的值
// enum StatusNumber {
// ZERO = "one",
// One = "two",
// TWO = "three",
// }
const post = {
title: "title",
content: "Typescript",
status: StatusNumber.ZERO,
};
枚举类型可能会入侵到我们运行中的代码:会影响我们编译之后的结果。
我们的大多数Typescript中定义的类型会在我们编译转换之后被移除掉,只是为了我们在编译过程中做类型检查
这段代码无外乎就是把枚举的名称作为对象的键来存储枚举的值,再把值作为键来存储枚举的键
目的就是可以动态地通过枚举值,也就是0,1,2这样的值来获取枚举的名称:
StatusNumber[0]; // ZERO
如果我们代码中确认没有使用索引器的方式来访问枚举,那我们建议使用常量枚举,在enum前加一个const,就可以看到在编译之后的枚举会被移除掉,使用到枚举的地方就会被替换为具体的数值。
常量枚举适用于那些在编译时被内联的情况,可以提高性能,而普通枚举则提供了更丰富的功能,包括反向映射等
函数类型
export default {};
function func1(a: number, b: number): string {
return "func1";
}
func1(1, 2);
// 形参和实参必须保持完全一致
// 如果需要可选:
function func2(a: number, b?: number): string {
return "func2";
}
func2(1);
// 或者使用ES6的默认参数:
function func3(a: number, b: number = 10): string {
return "func3";
}
func3(1);
//可选参数必须要出现在必选参数的最后
// 如果我们要接受任意多个参数,我们可以使用ES6的rest操作符:
function func4(a: number, b: number, ...rest: number[]) {
return "func4Rest";
}
func4(1, 2, 3, 4, 4, 5, 5, 5, 55);
const functionArrow: (a: number, b: number) => string = function (
a: number,
b: number
): string {
return "";
};
any类型
export default {};
function stringify(value: any) {
return JSON.stringify(value);
}
// any类型依然属于动态类型,Typescript不会对any做类型检查
// any不会有类型检查所以会有类型安全的问题,尽量不要使用
隐式类型推断
如果我们没有明确通过类型注解来标记变量类型,Typescript就会通过自己的推断来标记变量的类型
export default {};
// number
let age = 19;
// any
let foo;
类型断言
在有些特殊的情况下,Typescript无法自行推断类型的,但是在开发者的角度下,我们是可以额明确的知道变量的类型的。这种情况下我们就需要使用类型断言。比如:
export default {};
const nums = [11, 12, 13, 14, 10, 9, 8];
const res = nums.find((i) => i > 0);
// const resSuqare = res * res;
// 断言方式1:
const num = res as number; //推荐
// 断言方式2:
const num2 = <number>res; //jsx语法下不可以使用
我们知道这个变量nums里一定有一个大于0的数字,但是Typescript依旧会报错,这个时候我们就可以使用断言了↑
接口
export default {};
interface Post {
title: string;
content: string;
}
function printPost(post: Post) {
console.log(post.title);
console.log(post.content);
}
printPost({ title: "Typescript", content: "Interface" });
总结接口:接口就是用来约束对象的结构,一个对象实现一个接口,他就必须要拥有接口里的所有成员。
Typescript中的接口只是为我们有结构的数据做类型约束,在实际运行的阶段,接口其实并没有意义。
可选成员
export default {};
interface Post {
title: string;
content: string;
// 可选,加一个?即可,在实际调用时可以加上也可以不加上
should?: boolean;
}
const printPost = (post: Post) => {
console.log(post.title);
console.log(post.content);
};
printPost({ title: "Typescript", content: "Interface" });
printPost({ title: "Typescript", content: "Interface", should: true });
readonly
不允许外界设置的话就加上readonly
export default {};
interface Post {
title: string;
content: string;
// 可选,加一个?即可,在实际调用时可以加上也可以不加上
should?: boolean;
// readonly在初始化完成之后再次修改的话就会报错
readonly summary: string;
}
const printPost = (post: Post) => {
console.log(post.title);
console.log(post.content);
};
printPost({ title: "Typescript", content: "Interface", summary: "sss" });
printPost({
title: "Typescript",
content: "Interface",
should: true,
summary: "sss",
});
动态成员
interface Cache {
[prop: string]: string;
}
const cache: Cache = {};
cache.foo = '111'
cache.bar = '222'
cache.zoom = '333'
cache可以自由的添加成员,但是必须要是string类型。
类
描述一类具体事物的抽象特征
ES6以前,函数+原型来模拟实现类
ES6之后,有一个专门的class
在Typescript中增强了class的相关语法
export default {};
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHi = (msg: string): void => {
console.log(`${msg}`);
};
}
访问修饰符
- private 私有属性
添加了private之后,这种属性只能在类的内部访问 - public 公有成员
Typescript中是默认的public,加不加也没有所谓 - protected 受保护成员
使用 protected 访问修饰符表示成员只能在类内部和继承的子类中访问,外部无法访问
外部也不能访问,和private的区别在哪儿?
class Student extends Person {
constructor(name: string, age: number) {
// 调用父类的构造函数
super(name, age);
// 可以访问受到保护的属性sex
console.log(this.sex);
}
}
这些访问修饰符的作用就是控制类的成员的可访问级别
另外:我们如果把构造函数给private,那么就不能在外面被实例化,也不能够被继承,解决办法就是在class内部添加一个静态方法:
class Person {
name: string;
// 私有属性
private age: number;
protected sex: string;
private constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.sex = "male";
}
sayHi = (msg: string): void => {
console.log(`${msg}`);
};
// 新建一个静态方法在内部使用构造函数,在外部调用就可以了
static create(name: string, age: number) {
return new Person(name, age);
}
}
const ming = Person.create("ming", 12);
protected这样在外部也是无法实例化的,唯一区别在于可以继承,不再赘述了。
类的只读属性
也是一样,加readonly,但是如果有访问修饰符的话,要加在访问修饰符之后,比如:
protected readonly sex: string
在初始化之后,我们的sex属性就无法再修改了,不管是在内部还是外部。
类与接口
相比于类,接口的概念更加抽象一点
就比方说:手机和座机,都能打电话,但是手机还可以调用摄像头拍视频,座机不能,这其中,打电话作为一个公共的特征,可以抽象为一个接口
export default {};
interface EatAndRun {
eat(food: string): void;
run(distance: string): void;
}
class Person implements EatAndRun {
eat(food: string): void {
console.log("用餐具吃:", food);
}
run(distance: string): void {
console.log("跑了:", distance);
}
}
class Animal implements EatAndRun {
eat(food: string): void {
console.log("原始的吃:", food);
}
run(distance: string): void {
console.log("爬行了:", distance);
}
}
实现接口使用interface,implements实现一下EatAndRun接口,那么在这个类型中,必须要有这个类型的成员,没有就会报错
但是在C#和Java这种语言中,建议我们尽可能让每个接口的定义更加简单和细化:比如eatAndRun抽象了两个能力,吃和跑,但是这两个并不一定都同时存在。
更为合理的是:一个接口只会约束一个能力,同时让一个类型同时实现多个接口,这样会更加的合理:
export default {};
interface Eat {
eat(food: string): void;
}
interface Run {
run(distance: string): void;
}
class Person implements Eat, Run {
eat(food: string): void {
console.log("用餐具吃:", food);
}
run(distance: string): void {
console.log("跑了:", distance);
}
}
class Animal implements Eat, Run {
eat(food: string): void {
console.log("原始的吃:", food);
}
run(distance: string): void {
console.log("爬行了:", distance);
}
}
抽象类
抽象类在某种程度上和接口有点类似,不同于接口的是,抽象类可以包含一些具体的实现。
// 定义抽象类
abstract class AbstractClass {
// 定义抽象方法,子类需要实现这个方法
abstract abstractMethod(): void;
// 普通方法
regularMethod() {
console.log("This is a regular method");
}
}
// 继承抽象类的子类
class ConcreteClass extends AbstractClass {
// 实现抽象方法
abstractMethod() {
console.log("Abstract method implemented in ConcreteClass");
}
// 子类可以覆盖普通方法
regularMethod() {
console.log("This is an overridden regular method");
}
}
// 实例化子类
const instance = new ConcreteClass();
// 调用抽象方法
instance.abstractMethod(); // 输出: Abstract method implemented in ConcreteClass
// 调用普通方法
instance.regularMethod(); // 输出: This is an overridden regular method
泛型
泛型就是把定义时不能明确的类型变为一个参数,在使用的时候再传递这样一个类型参数:
// 泛型
export default {};
function createNumberArray(length: number, value: number): number[] {
const arr = Array<number>(length).fill(value);
return arr;
}
const res = createNumberArray(3, 100);
function createArray<T>(length: number, value: T): T[] {
const arr = Array<T>(length).fill(value);
return arr;
}
const resNumber = createArray<number>(3, 100);
const resString = createArray<string>(3, "1");
类型声明
比如我们用到第三方的npm包:
就会报错,关于类型错误的,但是这里只是举一个例子,因为lodash是支持Typescript的类型的,有些npm包如果是没有支持的话呢,就可以用到类型声明的declare了:
// 类型声明
import { camelCase } from "lodash";
declare function camelCase(input: string): string;
camelCase("string hello");
当然了,如果npm包是支持Typescript的话,你安装对应的@types/xxx就可以了,在node_modules里可以看到他们:
xxx.d.ts这种后缀的文件基本就是Typescript中的类型的声明文件。