简介
TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
区别
准备工作
1.安装 TypeScript
npm install -g typescript
验证 TypeScript
tsc -v
编译 TypeScript 文件
tsc helloworld.ts
一般工作流程
TypeScript 初体验
hello.ts
function greet(person: string) {
return 'Hello, ' + person;
}
console.log(greet("TypeScript"));
编译后 tsc hello.ts
"use strict";
function greet(person) {
return 'Hello, ' + person;
}
console.log(greet("TypeScript"));
观察以上编译后的输出结果,我们发现 person 参数的类型信息在编译后被擦除了。TypeScript 只会在编译阶段对类型进行静态检查,如果发现有错误,编译时就会报错。而在运行时,编译生成的 JS 与普通的 JavaScript 文件一样,并不会进行类型检查。
基础类型
let isDone: boolean = false;
let count: number = 10;
let name: string = "semliker";
const sym = Symbol();
let obj = {
[sym]: "semlinker",
};
console.log(obj[sym]); // semlinker
let list: number[] = [1, 2, 3];
// ES5:var list = [1,2,3];
Enum 类型(反向映射)
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.SOUTH;//1 从0开始
or
let dir = Direction[0];//'SOUTH'
or
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]; // 'Green'
or
let colorName: string = Color[Color.Green]; // 'Green'
联合类型
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
对象的类型——接口
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
定义的变量比接口少或多了一些属性是不允许的
继承
interface LengthInterface {
length: number
}
interface WidthInterface {
width: number
}
interface HeightInterface {
height: number
}
interface RectInterface
extends LengthInterface,
WidthInterface,
HeightInterface {
// length:number
// width:number
// height:number
color: string
}
let rect: RectInterface = {
length: 10,
width: 20,
height: 30,
color: 'red',
}
高级类型
交叉类型
interface DogInterface {
run():void
}
interface CatInterface {
jump():void
}
let pet: DogInterface & CatInterface = {
// 看上去和接口多继承很像,但有一点区别。继承可以有自己的属性,交叉不行。
run(){},
jump(){},
};
联合类型
// 现在有两种形状,area函数用来计算每种形状的面积。
interface Square{
kind: "square";
size: number;
}
interface Rectangle{
kind: "rectangle",
width: number,
height: number,
}
type Shape = Square | Rectangle;
function area(s: Shape) {
switch (s.kind) {
case "square":
return s.size * s.size; // 此区块内,确保只有size属性
case "rectangle":
return s.height * s.width;
}
}
console.log(area({kind:"square",size:10})); // 100
// 现在要添加一个形状:圆形。需要定义接口Circle、为Shape添加联合类型Circle,然后为area函数内增加一个case。但是,如果我们忘了修改area函数,会发生什么?
interface Circle{
kind: "circle",
r: number,
}
type Shape = Square | Rectangle | Circle;
console.log(area({kind:"circle",r:10})); // undefined,这里并不报错,并不符合我们的预期。我们希望bug能够及时暴露出来,增加程序的稳定性。
做如下改动:
function area(s: Shape) {
switch (s.kind) {
case "square":
return s.size * s.size;
case "rectangle":
return s.height * s.width;
case "circle":
return Math.PI * s.r;
default:
return ((e: any)=>{throw new Error(`没有定义 ${s} 的面积计算方式`)})(s) // 这一步很重要,一定要在这里抛出异常
}
}
索引类型
当我们使用不存在的索引时,会返回undefined,没有约束(如下代码)。因此我们需要有对索引的约束。
let obj = {
a: 1,
b: 2,
c: 3,
};
function getValue(obj: any,keys: string[]){
return keys.map(key => obj[key]);
}
console.log(getValue(obj,["a","b"]));
console.log(getValue(obj,["c","f"])); // 会发现,'f'对应的输出是undefined,没有约束,需要用到索引类型
下面使用索引类型:
function getValue<T,K extends keyof T>(obj: T, keys: K[]): T[K][] { // T[k][]表示,返回值必须是obj中的值组成的列表
return keys.map(key => obj[key]); // 此时keys中的元素只能是obj中的键
}
console.log(getValue(obj,["a","b"]));
console.log(getValue(obj,["c","f"])); // 这时就会报错,有了约束 'f' is not in "a" | "b" | "c"
我们来解释一下:
这里会用到两个操作符,查询操作符 keyof T 和 访问操作符 T[k](看下面示例)。<T, K extends keyof T> 用到了泛型约束,表示K所约束的参数的值只能是T所约束参数数据中的“键”。
// keyof T
interface Obj{
a: number;
b: string;
}
let key: keyof Obj; // 此时key表示 'a' | 'b'
// T[k]
let value: Obj['a'] // number
映射类型
可以从旧的类型生成新的类型。比如,将接口中的所有成员变成只读、可选。
interface Obj{
a: string;
b: number;
c: boolean;
}
// 将Obj接口中每个成员变成只读属性,生成一个新的接口。
type ReadonlyObj = Readonly<Obj>; // Readonly是TS内置的映射类型,下同
// Readonly实现原理,利用了索引类型的操作方法
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
再如:
// 将所有属性变成可选
type PartialObj = Partial<Obj>;
// Partial实现原理
type Partial<T> = {
[P in keyof T]?: T[P];
}
// 获取原类型的子集
type PickObj = Pick<Obj,'a'|'b'>;
// 等同于
interface PickObj {
a: string,
b: number
}
// 将原类型当做新类型的成员
type RecordObj = Record<'x'|'y',Obj>;
// 等同于
interface RecordObj {
x: Obj,
y: Obj,
}
条件类型
条件类型指由表达式所决定的类型。条件类型使类型具有了不唯一性,增加了语言的灵活性。
T extends U ? X : Y 表示如果类型T可以被赋值给类型U,name结果就赋予X类型,否则赋予Y类型
再如
type TypeName<T> =
T extends string ? string :
T extends number ? number :
T extends boolean ? boolean :
T extends undefined ? undefined :
T extends Function ? Function :
object;
type T1 = TypeName<string>; // T1为字符串类型
type T2 = TypeName<string[]>; // T2为object类型
type T3 = TypeName<Function>; // T3为function类型
type T4 = TypeName<string | string[]>; // T4为 string和object的联合类型
可以用来做什么?
(A | B) extends U ? X : Y
解析为(A extends U ? X : Y) | (B extends U ? X : Y)
可以利用这一特性做类型的过滤,例如:
type Diff<T,U> = T extends U ? never : T;
type T5 = Diff< 'a'|'b'|'c', 'a'|'e' >; // 作用是过滤掉第一个参数中的'a' 。T5为 'b' | 'c'联合类型
解析过程:
Diff<'a', 'a'|'e'> | Diff<'b', 'a'|'e'> | Diff<'c', 'a'|'e'>
never | 'b' | 'c'
'b' | 'c'
TS内置的条件类型:
Exclude<T, U> // 从T中剔除可以赋值给U的类型,相当于上面例子中的Diff
Extract<T, U> // 提取T中可以赋值给U的类型。
NonNullable<T> // 从T中剔除null和undefined。
ReturnType<T> // 获取函数返回值类型。
InstanceType<T> // 获取构造函数类型的实例类型。
内置函数
Exclude<T, U> - 用于从类型T中去除不在U类型中的成员
eg:type T00 = Exclude<"a" | "b" | "c" | "d","a" | "c" | "f">;
eg: type T00 = "b" | "d"
let t0:T00 = "b"
console.log(t0)
//在这个例子中,因为用到了Exclude这个条件类型,会尝试寻找在T类型中有,但在U类型中没有的成员,最后将获取到的Union类型"b" | "d"赋给T00
Extract<T, U> - 用于从类型T中取出可分配给U类型的成员 交集
eg:type T01 = Exclude<"a" | "b" | "c" | "d","a" | "c" | "f">;
re: type T01 = "b" | "d"
NonNullable<T> - 通过运行NonNullable,清除了undefined类型成员
eg:type notNull = NonNullable<number | string | null | undefined>
re: type T01 = type notNull = string | number
ReturnType<T> - 通过ReturnType,返回了范型参数T的返回值类型
eg:type T11 = ReturnType<()=>string>
re: type T11 = string
InstanceType<T> - 获取构造函数函数类型的返回类型
eg:type NewType = InstanceType<new (name: string) => { name: string; age: number }>;
re:
type NewType = {
name: string;
age: number;
}
Partial<T>
eg:type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
re:
interface User {
name: string;
sex: string;
age: number;
}
type NewType = Partial<User>;
re:
type NewType = {
name?: string | undefined;
sex?: string | undefined;
age?: number | undefined;
}
Readonly<T> - 将 T 中的所有属性设置为只读
type Readonly<T> = { readonly [P in keyof T]: T[P]; }
eg:
interface User {
name: string;
sex: string;
age: number;
}
type NewType = Readonly<User>;
re:
type NewType = {
readonly name: string;
readonly sex: string;
readonly age: number;
}
Required<T> - 将 T 中的所有属性设置为必须
type Required<T> = { [P in keyof T]-?: T[P]; }
-? 表示取消可选
eg:
interface User {
name: string;
sex?: string;
age?: number;
}
type NewType = Required<User>;
re:
type NewType = {
name: string;
sex: string;
age: number;
}
Pick<T, K> - 从 T 中,选择一组位于 K 中的属性
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
eg:
interface User {
name: string;
sex: string;
age: number;
}
type NewType = Pick<User, 'name' | 'sex'>;
re:
type NewType = {
name: string;
sex: string;
}
Extract<T, U> - 从 T 中提取可分配给 U 的类型
type Extract<T, U> = T extends U ? T : never;
eg:
interface User {
name: string;
sex: string;
age: number;
}
type NewType = Extract<keyof User, 'name' | 'sex'>;
re:
type NewType = "name" | "sex"
Omit<T, K> - 构造一个属性为 T 的类型,但类型 K 中的属性除外
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
eg:
interface User {
name: string;
sex: string;
age: number;
}
type NewType = Omit<User, 'name' | 'sex'>;
re:
type NewType = {
age: number;
}
Parameters<T> - 获取函数类型的参数,返回的是个元组
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
eg:
interface User {
name: string;
sex: string;
age: number;
getFamily: (name: string) => User[];
}
type NewType = Parameters<User['getFamily']>;
re:
type NewType = [string]
大小写
type NewType = Uppercase<'aaa'>; // AAA 全大写
type NewType = Lowercase<'AAA'>; // aaa 全小写
type NewType = Capitalize<'aaa'>; // Aaa 首字母大写
type NewType = Uncapitalize<'AAA'>; // aAA 首字母小写
demo(基于vue3和typescript)
import { ref, Ref, watch } from 'vue'
interface Range {
min?: number,
max?: number
}
interface Result {
count: Ref<number>,
inc: (delta?: number) => void,
dec: (delta?: number) => void,
set: (value: number) => void,
reset: () => void
}
export default function useCount(initialVal: number, range?: Range): Result {
const count = ref(initialVal)
const inc = (delta?: number): void => {
if (typeof delta === 'number') {
count.value += delta
} else {
count.value += 1
}
}
const dec = (delta?: number): void => {
if (typeof delta === 'number') {
count.value -= delta
} else {
count.value -= 1
}
}
const set = (value: number): void => {
count.value = value
}
const reset = () => {
count.value = initialVal
}
watch(count, (newVal: number, oldVal: number) => {
if (newVal === oldVal) return
if (range && range.min && newVal < range.min) {
count.value = range.min
} else if (range && range.max && newVal > range.max) {
count.value = range.max
}
})
return {
count,
inc,
dec,
set,
reset
}
}
<template>
<div class="homePage">
<HelloWorld :msg="year"/>
<div>
<div>{{ width }}</div>
<div>{{ height }}</div>
</div>
<div>
<div>{{count}}</div>
<button @click='inc'>+</button>
<button @click='dec'>-</button>
<button @click='set(100)'>set 100</button>
<button @click='reset'>reset</button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs,watch } from "vue";
import HelloWorld from './components/HelloWorld.vue'
import useCount from './hooks/useCount'
import useWindowResize from "./hooks/useWindowResize";
export default defineComponent({
components: {
HelloWorld
},
setup() {
const year = ref(0);
const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });
const state = reactive({ nickname: "xiaofan", age: 20 });
const { count, inc, dec,set,reset } = useCount(10);
setInterval(() => {
year.value++;
user.age++;
state.age++;
}, 1000);
// 修改age值时会触发 watch的回调
watch(
() => state.age,
(curAge, preAge) => {
console.log("新值:", curAge, "老值:", preAge);
},
{ deep: true }
);
const stopWatchRoom = watch(year, (newVal, oldVal) => {
console.log("新值1:", newVal, "老值1:", oldVal);
});
const { width, height } = useWindowResize();
setTimeout(()=>{
stopWatchRoom()
}, 3000)
return {
count,
inc,
dec,
set,
reset,
width,
height,
year,
// 使用reRefs
...toRefs(user),
...toRefs(state),
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>