一、使用淘宝镜像:
npm 安装插件的时候可能会比较慢,甚至慢到失败,这是因为 npm 的服务器在国外,所以需要一个国内的镜像来替代,淘宝就弄出来一个镜像来给方便我们使用。使用方法一共两种:
- 使用阿里定制的 cnpm 命令行工具代替默认的 npm,输入下面代码进行安装:
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm -v
检测 cnpm 的版本。使用的话只需要把 npm install
换成 cnpm intall
即可。
如果你不想使用 cnpm 同时又不想安装插件速度太慢,就可以使用方法二更换下载源为淘宝源。
- 更换 npm 的下载地址
- 单次使用(只生效一次)
npm install --registry=https://registry.npm.taobao.org
- 永久使用(推荐)
设置成全局的下载镜像站点,这样每次install的时候就不用加--registry,默认会从淘宝镜像下载,设置方法如下:
npm config set registry https://registry.npm.taobao.org
检测是否成功:
// 配置后可通过下面方式来验证是否成功
npm config get registry
如果想还原 npm 仓库地址,只需再把地址配置成 npm 镜像就可以了
npm config set registry https://registry.npmjs.org/
淘宝镜像官网地址:http://npm.taobao.org/
二、Typescript 安装和编译
全局安装 Typescript:
npm install -g typescript
命令行中输入 tsc -h 或 tsc --help 可以查看 tsc 命令行使用参数和示例。
手动编译为 js 文件:
//编译一个
tsc index.ts
//编译多个
tsc index.ts main.ts
//指定编译的文件名,支持多个文件
tsc index.ts --outFile main.js
// 指定编译的目录,支持多个文件
tsc index.ts --outDir main.js
//一直监控文件的编译
tsc index.ts --watch
如何配合 VSCode 进行自动编译?
- 生成配置文件
tsc --init
message TS6071: Successfully created a tsconfig.json file.
创建完成,会生成一个 tsconfig.json 文件,里面的内容你可以不更改,使用默认的值。这时打开 VSCode ,Terminal => Run Task... => tsc:watch - tsconfig.json
程序会自动执行代码一下代码:
> Executing task: tsc -p e:\BaiduNetdiskDownload\project_tsc\tsconfig.json --watch <
可以看到其实就是用命令行的方式通过绝对路径来执行配置文件。那我们就可以在命令行输入:tsc -p ./tsconfig.json --watch
来达到相同的效果。
这里需要说明的是在有 tsconfig.json 文件的情况下进行编译:
命令行中直接输入 tsc 不加任何参数,编译器会从当前目录开始去查找 tsconfig.json 文件,如果没有配置文件,逐级向上搜索父目录,直至找到配置文件。
tsc 后面使用命令行参数--project(或-p)表示指定一个包含tsconfig.json 文件的目录,当命令行上指定了输入文件时,当前所在目录里面的 tsconfig.json 文件会被忽略。
假设当前目录有 tsconfig.json 文件的话, 命令行编译效果:
tsc = tsc -p tsconfig.json = tsc --build tsconfig.json
我们学习 Typescript 进行调试的方法是多种多样,我选择使用 VSCode 来监控文件的编译同时改写 tsconfig.json 文件的配置,通过 index.html 来引入编译后的 js 文件在浏览器进行测试。
如果你习惯使用 NodeJs ,也可以选择使用 Node 来进行调试,例如在 ts_project 里面进行学习,我们先手动安装两个依赖:
npm init -y
npm i -S typescript ts-node
编写好 ts 文件我们只需要使用命令:
npx ts-node xxx.ts//typescript ts-node是全局安装的话就不用加 npx。
还是觉得 tsc 配合 --watch 好用。
三、Typescript 的数据类型
Typescript 说是一门语言,但是给人的感觉更像是一个工具,但又不完全是,因为 TypeScript 除了可以把写好的代码编译成 JS 外,还可以进行类型检查和对ES进行扩展,一句话总结 TS 就是静态强类型语言,对应的 JS 就是动态弱类型语言。
其中高级类型主要包括以下几个常用的:
- 联合类型
- 交叉类型
- 索引类型
- 映射类型
- 条件类型
- 类型保护(类型守卫)
- 类型别名
- 字符串字面量类型
3.1 类型注解
Typescript 与 JS 的数据类型比较如上图,接下来我们看看这些数据类型怎么在 TS 中使用,不过先了解类型注解:
// 相当于 C 强类型语言中的类型声明
(变量/函数):type
3.2 原始数据类型(加上symbol六个)和对象数据类型及几个 TS 新增的数据类型
Typescript 要求变量定义时即声明变量的类型。相比于 JavaScript 而言有个静态强类型校验,下面是代码注释讲解非常重要:
// 原始数据类型(基本数据类型)
var num:number = 123;
var str:string = "Condor Hero";
var bol:boolean = true;
// 两种数组定义方法
var arr1:number[] = [1,2,3];
var arr2:Array<number> = [1,2,3];
// 元组类型可以理解为数组的另一种写法,一般用于知道数组的长度的类型
var arr3:[string , number] = ["string" , 9999];
arr3.push(3);
console.log(arr3);//[ 'string', 9999, 3 ]
// 元组可以使用数组的方法操作元素,但是新增的元素不允许访问,以下代码不被允许
// console.log(arr3[2]);//[ 'string', 9999, 3 ]
// 初始化的元素是允许访问的,例如我们访问第一个元素
console.log(arr3[0]); // string
var nul:null = null;
var undef:undefined = undefined;
//默认情况下null和undefined是所有类型的子类型。 就是说你可以把null和undefined赋值给number类型的变量。
//然而,当你指定了--strictNullChecks标记,null和undefined只能赋值给void和它们各自。默认strictNullChecks是打开的
// 就是讲默认情况下null和undefined不能赋值给其他类型的变量
var shuzi:number = 123;
shuzi = undefined;
shuzi = null;
// 上面的代码不被允许,除非到 tsconfig.json 里面把 strictNullChecks 设置为false
// "strictNullChecks": false, /* Enable strict null checks. */
// 这个有点像JS的use strick严选模式
var empty:void;
empty = undefined;
// symbol
var s1:symbol = Symbol();
var s2 = Symbol();
console.log(s1 === s2);//false
//object表示非原始类型,也就是除 number, string, boolean, bigint, symbol, null, or undefined. 之外的类型。就是引用类型
var obj:object = {a:1,b:2};
// console.log(obj.a); //Property 'a' does not exist on type 'object'.
// 这种方法定义的不能访问
// 推荐使用下面这种方法 interface
var obj2:{a:number,b:number} = {a:1,b:2};
console.log(obj2.a);
// any 类型:一个变量,如果定义时不指定类型,默认就是 any 类型可以赋值任意类型。
// any 避免了 TS 的类型检查,相当于在 TS 中使用 JS 的变量,建议能不用就不。
let looselyTyped: any = 4;
// 随意读写,虽然是错误的代码
looselyTyped.ifItExists();
// unknown 类型相当于缩小版的 any 类型
let anyType: any = "一段文字";
// 即使 anyType 是个数字, anyType.foo 也能正常工作
anyType.length // OK
// 同样的代码,使用 unknown 类型:
let unKownType: unknown = "一段文字";
// unKownType.length // error
console.log((unKownType as string).length) // string
// 联合类型
//定义一个变量
var variable:number | string | boolean;
var state:number | string;
state = 10;
state = state.toString();
never 是其它类型(包括 null 和 undefined)代表从不会出现的值,通常用在函数中,它通常表现为抛出异常或程序无法执行到终止点(例如无限循环),never 类型是任何类型的子类型,也可以赋值给任何类型,但是 any 是不可以赋值给 never 滴。
let x: never;
let y: number;
// 运行错误,数字类型不能转为 never 类型
x = 123;
// 运行正确,never 类型可以赋值给 never类型
x = (()=>{ throw new Error('exception')})();
// 运行正确,never 类型可以赋值给 数字类型
y = (()=>{ throw new Error('exception')})();
// 返回值为 never 的函数可以是抛出异常的情况
function error(message: string): never {
throw new Error(message);
};
error("错误");
// 返回值为 never 的函数可以是无法被执行到的终止点的情况
function loop(): never {
while (true) {}
};
loop();
3.3 关于枚举
- 数字枚举最关键的特性是自增长属性
//数字枚举
// enum 枚举名 {
// 标识符[=整型常数],
// ...
// 标识符[=整型常数]
// }
//访问 enum 的值,很简单,直接访问即可,像对象一样。
enum Direction { up , down , left , right};
var left:Direction = Direction.left;
var up:Direction = Direction[0];
如果不指定 enum 中的值,则会从 0 开始,自增长为 0 / 1 / 2 / 3。有点类似数组的下标。如果指定 enum 中第一个值,则会从这个 number 类型的值子增长。
enum Direction { up , down , left = 1 , right};// 0 1 1 2
我们从中间指定了一个值,enum 总是将按照自上而下的顺序初始化:0、1、2 ...,如果遇到了初始化过值得,则停止增长,使用当前初始化的值继续增长。
一旦某个属性通过函数或者其他方式初始化,只要不是直接赋值常量初始化,比如枚举中其中一个值通过函数执行进行了初始化,这时后面属性必须手动进行初始化。
function getValue():number{
return 1;
};
//right必须手动进行数字初始化
enum Direction { up , down , left = getValue() , right = 9};
console.log(Direction["left"]);//1
- 字符串枚举
在一个字符串枚举里,每个成员都必须用字符串进行初始化,字符串枚举没有自增长的行为。
enum Color {
Red = 'red',
Blue = 'blue',
Green = 'green',
White = 'white',
};
- 异构枚举(Heterogeneous enums)
TypeScript 支持 Enum 的枚举成员既可以是 number 也可以是 string ,但是并不建议这样做。
enum Check {
No = 10,
Yes = 'YES'
}
console.log(Check["Yes"]);
3.4 类型推论
- 如果一个变量直接被赋值,变量的类型就被指定了。
var num = 10;
//不被允许num = "num";
- 变量定义时未赋值也没有指定类型,默认类型为 any
var a;//var a:any;
a = 10;
a = "a";
四、接口(interface)
TypeScript 的核心就是类型检查,前端有个数据结构特别的常见就是 json 对象,interface 接口可以讲就是专门来检查对象结构的。这方面最经典的例子就是 Jquery 的 Ajax 里面的 config。用 interface 来约束是再好不过了。
var obj:{ name : string };
obj = { name : "Condor Hero"};
console.log(obj);
我们还可以把 obj 后面的约束内容单独提取出来,使用 interface 来定义,改写之后如下:
interface People { name : string };
var obj : People;
obj = { name : "Condor Hero"};
console.log(obj);
看到这里其实也就明白了,TS 的接口其实就是对 JS 中的对象进行类型检查而已。
默认情况下 interface 中的属性必须全部传入,即 interface 中定义了多少个属性在赋值使用的时候必须一个不少的传入。如果不确定某个属性是必须的我们可以使用可选属性,例如(age字段):
//属性名后面加一个问号 ? 表示这个属性是可选的,使用的时候,即使不传入也不会检查错误。
interface People { name : string , age ?: number};
var obj : People = { name : "Condor Hero"};
console.log(obj);
接下来就是对象里面我不确定有没有这个属性怎么办?例如我随时有可能给对象添加 sex 属性或 height 属性等一些额外的属性。
interface People { name : string , age ?: number , [propName:string]:any };
var obj : People = { name : "Condor Hero", sex : "girl"};
console.log(obj);
需要注意的是添加额外的属性使用的是 [propName:string]:any
其中的 propName
可以是自定义的一个变量,但是类型一般默认使用 any 而不指定其他类型。因为凡是进入接口的属性都必须从属于额外属性的类型,那么确定属性和可选属性都必须是额外属性的子属性,例如:
interface People { name : string , [propName:string]:string };
var obj : People = { name : "Condor Hero", sex : "girl"};
console.log(obj);
必选属性和追加的额外属性属于同种数据类型,程序是没问题的,如果添加一个 age 属性:
interface People { name : string , age ?: number , [propName:string]:string };
var obj : People = { name : "Condor Hero", sex : "girl"};
console.log(obj);
虽然 age 属性是可选属性赋值时不是必须的,但是有追加属性的时候,要求无论你是可选属性还是必选属性其类型都必须从属于追加的类型,所以一般来讲追加属性类型为 any ,还不能省略。
想要追加属性还有一种方法,类型断言:(写法相对来讲不是很优美,不推荐使用)
interface People { name : string , age ?: number };
//类型断言也分两种
//第一种
var obj1 : People = { name : "Condor Hero", sex : "girl"} as People;
console.log(obj1);
//第二种
var obj2 : People = <People>{ name : "Condor Hero", sex : "girl"};
console.log(obj2);
一个对象里面某些属性值,我想保证在初始化完成就不再改变了,只能只读,这就是只读属性:能够要求只有在初始化的时候改变属性的值,其他时间不允许改变。定义方式是在属性前面加上 readonly:
interface People { readonly name : string , age ?: number };
var obj : People = { name : "Condor Hero" };
console.log(obj.name);
obj.name = "哈哈哈";//不允许
需要注意的是如果属性值为对象或数组,其值是可以改变的,例如把数组最后一项由三改为九十九:
interface Animal {
readonly arr:number[]
}
var arr:Animal = { arr : [1 ,2 ,3 ]};
arr.arr[2] = 99;
console.log(arr);
Typescript 提供了 ReadonlyArray<T> 类型,数组在创建之后不允许任何的变动,即使是子元素也不能变动。上面只读数组的例子就可以改写为:
interface Animal {
arr:ReadonlyArray<number>
}
var arr:Animal = { arr : [1 ,2 ,3 ]};
// arr.arr[2] = 99;//不被允许
console.log(arr);
前面讲了 interface 是对 JS 对象的类型检查,话只对了一半,还可以对函数参数和返回值进行类型检查(其实函数本质也是对象),即 interface 中的函数类型,所谓的函数类型其实约束的是函数的参数及返回值,实际上就是对这个方法的描述。
interface Func {
(width: number, height: number) : boolean;
name: string; // 函数默认有静态属性 name
params: number; // 需要手动添加静态属性 params
reset(): number; // 需要手动添加静态函数 reset
}
const func: Func = function (width: number, height: number): boolean {
return false;
}
func.params = 2;
func.reset = function () { return 90; };
千万注意 interface 函数类型 和 interface 在定义属性时,属性是函数,两者是不同的:
interface People {
name : string ;
getHeight(height:number):any;
};
interface People {
name : string ;
getHeight:(height:number)=>any;
};
interface 花括号里面元素的间隔,可以用分号、逗号或啥也不用,甚至三者混用。而且最后一个元素的分隔符可以省略,所以以下写法都是合法的,更推荐纯使用分号间隔元素,因为官网用的就是分号:
interface People {
sex: string;
height: number;
}
interface Props {
sex: string,
height: number,
}
interface IProps {
sex: string
height: number
}
interface Animal {
sex: string;
log: string
emy: string,
}
五、类型断言
有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
说白了就是在初始定义值的时候,你并不知道未来对值的处理,未来你突然用到了,发现没有当初没有考虑到。这时可以通过类型断言告诉 TypeScript 我已经人工检查完毕,你不要对此在进行类型校验。
类型断言有两种形式。
- 为
<>
尖括号(angle-bracket)语法:
function func(val: string | number): number {
if (<string>val) {//值为:哈哈
return (<string>val).length
} else {
return val.toString().length
}
}
console.log(func("哈哈"));
- 为
as
语法:
function func(val: string | number): number {
if (val as string) {//值为:哈哈
return (<string>val).length
} else {
return val.toString().length
}
}
console.log(func("哈哈"));
两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有 as
语法断言是被允许的。