思考一个问题,JavaScript是一门非常优秀的编程语言,但是直到今天,JavaScript在类型检测上依然是毫无进展,所以我们需要学习TypeScript,这不仅仅可以为我们的代码增加类型约束,而且可以培养我们前端程序员具备类型思维
GitHub说法:TypeScript is a superset of JavaScript that compiles to clean
TypeScript官网:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
翻译一下:TypeScript是拥有类型的JavaScript超集,它可以编译成普通、干净、完整的JavaScript代码。
安装TypeScript
# 安装命令
npm install typescript -g
# 查看版本
tsc --version
// 安装ts-node及其依赖包
npm install ts-node tslib @types/node -g
// 代码栗子,创建 .ts 文件
// 规定message的类型为string
let message: string = 'hello'
// 报错!
// message = 123
// 规定参数类型为string
function abc(name: string){}
console.log(message);
// 因为ts默认作用域会在一个,这样设置导出,会让文件形成单独作用域
export {}
// ts-node可以直接运行TypeScript代码
ts-node 文件名
具体可看之前的webpack文章,webpack 之 零基础使用常用的Loader ,这里我快速过一遍哈
npm init -y
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin ts-loader typescript -D
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/main.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
devServer: {},
resolve: {
extensions: ['.ts', '.js', '.cjs', '.json']
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html'
})
]
};
// 先不用理这个文件,后续讲解
tsc --init
创建src文件夹,里面再创建main.ts
const message: string = '123'
console.log(message);
声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解
var/let/const 标识符: 数据类型 = 赋值;
数据类型的大小写是有区别的 ,用小写即可
类型推导 : 声明一个标识符时,如果有进行直接赋值,会根据赋值的类型推导出标识符的类型
ps :
let => 进行类型推导,推导出来通用类型
const => 进行类型推导,推导出来的是字面量类型
TypeScript和JavaScript一样,不区分整数类型(int)和浮点型(double),统一为
number类型
let demo: number = 22; // 十进制
let demo: number = 0b110; // 二进制
let demo: number = 0o555; // 八进制
let demo: number = 0xf23; // 十六进制
boolean类型只有两个取值:true和false
let bool: boolean = true
let bool: boolean = 30 > 20
string类型是字符串类型,可以使用单引号或者双引号表示,同时也支持ES6的模板字符串来拼接变量和字符串
const name: string = 'star'
const info: string = `my name is ${name}`
需要制定数组类型,同时制定数组内部值的类型
// 写法一 : 不推荐
const name: Array = []
// 写法二 : 推荐
const name: string[] = []
let name1: string[] = ['张三', '李四', '王五'];
name1.push(1); // 报错, 只能添加字符串类型的数据
// 数组的类型最好是一致的,不一致的话,可以使用联合类型
let name2: (string | number)[] = ['张三', '李四', '王五'];
name2.push(1);
name2.push('赵六');
// 1. 使用自动推导
const info = {
name: 'star',
age: 18
};
// 2. 手动写入
let info: {
name: string;
age: 20; // 这里的20是字面量类型,表示age只能是20
} = {
name: 'John',
age: 20 // 必须是20,否则报错
};
const n: null = null
const n: undefined = undefined
// Symbol 生成独一无二的key
const info = {
[Symbol('name')]: 'star',
[Symbol('name')]: 'coder'
};
无法确定一个变量的类型,并且可能它会发生一些变化,这个时候可以使用any类型
any类型有点像一种讨巧的TypeScript手段:
什么时候使用any
// 任意类型都可赋值,也可赋值给任何类型
let message: any = 'star';
message = 123;
message = true;
// 任意类型的值,可以访问任意属性和方法
message.length;
message.toFixed();
unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量
和any类型有点类似,但是unknown类型的值上做任何事情都是不合法的
ps : unknown进行任意操作前,必须进行类型校验(缩小),才能进行对应的操作
function foo() {
return 'string';
}
function bar() {
return 123;
}
let flag: boolean = true;
// result 不知是什么类型时可用
let result: unknown;
if (flag) {
result = foo();
} else {
result = bar();
}
/**
* unknown 类型的值不能直接使用
* result.length; // error
* 需要先判断类型,再使用 => 类型缩小
* if (typeof result === 'string') {
* result.length;
* }
*/
if (typeof result === 'string') {
result.length;
}
let result: unknown;
// 报错
let num: number = result
// 可以赋值
let unres: unknown = result
let anyres: any = result
当函数没有返回值的时候,该函数就是void类型,不写也阔以,会自动推导
// 如果返回值时void类型,也可以返回undefined
const sum = (num1: number, num2: number): void => {
console.log(num1, num2);
return undefined // 不会报错
};
开发中很少实际去定义never类型 => 某些情况下会自动类型推导出never
永远没有返回值的时候,用never
// 其他时候在扩展工具的时候,对于一些没有处理的case,可以直接报错
function handleMessage(message: string | number | boolean) {
switch (typeof message) {
case 'string':
console.log('string', message.length);
break;
case 'number':
console.log('number', message.toFixed(2));
break;
default:
// 永远不会执行,因为上面的 case 已经覆盖了所有可能的值,赋值给never类型
const check: never = message; // error,Type 'boolean' is not assignable to type 'never'
}
}
handleMessage('hello world');
handleMessage(100.123);
/**
* 如果仅仅更改了参数类型,而没有更改 switch 的 case,TypeScript 会提示错误
*/
handleMessage(true);
tuple和数组的区别
// tuple 多种元素的组合
// 1. 数组
const info1: any[] = ['star', 1, 2, 'coder'];
info1[0].slice(1); // 是any类型 => 虽然可以使用slice方法,但是不安全
// 2.1 元组
const info2: [string, number, number, string] = ['star', 1, 2, 'coder'];
info2[0].slice(1); // 可以直接知道是string类型 => 安全
// 2.2 元组类型的抽取
type infoType = [string, number, number, string];
const info3: infoType = ['star', 1, 2, 'coder'];
// 2.3 元组类型的使用,一般用于函数返回值
function useState(initState: number): [number, (newStateValue: number) => void] {
let stateValue: number = initState;
function setStateValue(newStateValue: number) {
stateValue = newStateValue;
}
return [stateValue, setStateValue];
}
// 可以明确知道返回值的类型,state是number类型,setState是一个函数
const [state, setState] = useState(0);
// 可指定函数的参数类型和个数和返回值的类型 name: string => 指参数类型为string string => 指函数的返回值类型为string,可不写,会推导
function getInfo(name: string): string {
return name;
}
// 匿名函数不需要写类型,TypeScript会根据forEach函数的类型以及数组的类型 推断出 item的类型
// 因为函数执行的上下文可以帮助确定参数和返回值的类型
const names = ['a', 'b', 'c'];
names.forEach((item) => {});
// info是一个对象类型,对象中有两个属性
function find(info: { name: string; age: number }) {}
对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?
// money是可选类型,可传可不传
function find(info: { name: string; age: number; money?: number }) {}
find({ name: 'coder', age: 123 });
find({ name: 'coder', age: 123, money: 1000 });
TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型
联合类型(Union Type)
// 联合类型 可以为其中的一种
// 1. 联合类型基本使用
let info: number | string = 1;
info = '1';
// 使用的时候需要判断类型
if (typeof info === 'string') {
console.log(info.length);
}
// 2. 联合类型的函数参数使用
function find(id: string | number) {
// 使用联合类型的值的时候,需要特别小心
switch (typeof id) {
case 'string':
break;
case 'number':
break;
}
}
find(123);
find('456');
可选类型和联合类型的关系
一个参数是一个可选类型的时候,它其实类似于这个参数是 类型|undefined的联合类型
function foo(message?: string) {}
// 这里可以不传
foo();
function foo(message: string | undefined) {}
// 但是这里还是要传一个值
foo(undefined);
交叉类似表示需要满足多个类型的条件,交叉类型使用 & 符号
interface Ieat {
eating: () => void;
}
interface Idrink {
drink: () => void;
}
// 满足其中一个即可
type myType1 = Ieat | Idrink;
// 满足两个才可以 联合类型
type myType2 = Ieat & Idrink;
const obj1: myType1 = {
eating: () => {}
};
const obj2: myType2 = {
eating: () => {},
drink: () => {}
};
// type 用于定义类型别名
type IdType = string | number | boolean;
// 太长了
function idSet(id: string | number | boolean) {}
// 取别名可以优化
function idSet(id: IdType) {}
有时候TypeScript无法获取具体的类型信息,这个时候需要使用类型断言
// 默认是 HTMLElement 类型,范围太广了,有时会出错
// const oDom: HTMLElement = document.getElementById('star');
//
// 用 as 指定是什么元素类型
const oDom: HTMLImageElement = document.getElementById('star') as HTMLImageElement;
oDom.src = 'url地址';
TypeScript只允许类型断言转换为 更具体 或者 不太具体( any/unknow ) 的类型版本,此规则可防止不可能的强制转换
/**
* 可以跳过类型检测,无敌,不推荐使用
*
* message先转成 any,再转成 number
* (message as any) as number
*
* 转换后无法正常使用
*/
const message = 'hello';
// 可以跳过类型检测,无敌,不推荐使用
const num: number = message as any as number;
console.log(num.toFixed(2)); // 运行时报错
如果确定传入的参数是有值的,这个时候可以使用非空类型断言
非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测
// 这样是没问题的
function printStrLength(message: string) {
console.log(message.length);
}
printStrLength('aaa');
printStrLength('11');
// 但是如果参数变成了可选类型呢,编译就会报错
function printStrLengthCan(message?: string) {
console.log(message.length);
}
// 解决方式一 : 类型缩小
function printStrLengthOne(message?: string) {
// 做个if判断即可
if (message) {
console.log(message.length);
}
}
// 解决方式二 : 非空断言(有点危险,确保有值时才使用)
function printStrLengthTwo(message?: string) {
// 加个非空断言 , 保证message一定有值
console.log(message!.length);
}
可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性
// 定义一个类型
type Person = {
name: string;
// 可能有,可能没有
friend?: {
name: string;
// 可能有,可能没有
age?: number;
};
};
const info: Person = {
name: 'star'
};
const info2: Person = {
name: 'star',
friend: {
name: 'coder'
}
};
// 如果有friend,继续往后取,如果没有,返回undefined,相当于短路
console.log(info.friend);
console.log(info.friend?.name);
console.log(info.friend?.age);
// 类似于,可以节省很多代码
if (info.friend) {
if (info.friend.name) {
console.log(info.friend.name);
}
if (info.friend.age) {
console.log(info.friend.age);
}
}
export {};
const message: string = 'hello'
const flag: boolean = Boolean(message)
// !! 可以用作取反
const flag1: boolean = !!message
// ?? 有值的时候取前面的值,没值的时候取后面的值
// 和三目运算符很像,不过更简洁一点
const message: string | null = null;
const res: string = message ?? 'hello';
console.log(res); // hello
const message1: string | null = 'coder';
const res1: string = message ?? 'hello';
console.log(res1); // coder
字面量类型的类型和值要保持一致
const message = 'hello'; // 这个message的类型就是hello
let mess = 'hello' //这个mess的类型才是string
let me : 123 = 123
me = 456 // 报错
字面量类型的意义 : 必须结合联合类型
let align: 'left' | 'right' | 'center' = 'left'
// 再次修改值的时候,只能允许修改定义了类型的值
align = 'right'
const info = {
url: 'www.baidu.com',
method: 'GET'
};
function request(url: string, method: 'GET' | 'POST') {
console.log(url, method);
}
// 这里info.method会报错,因为默认推导method是字符串类型,所以不能这么传
request(info.url,info.method)
// 定义一个类型
type requestType = {
url: string;
method: 'GET' | 'POST';
};
// 定义对象的时候就使用这个类型
const info: requestType = {
url: 'www.baidu.com',
method: 'GET'
};
function request(url: string, method: 'GET' | 'POST') {
console.log(url, method);
}
request(info.url, info.method);
const info = {
url: 'www.baidu.com',
method: 'GET'
};
function request(url: string, method: 'GET' | 'POST') {
console.log(url, method);
}
// 使用类型断言
request(info.url, info.method as 'GET');
function request(url: string, method: 'GET' | 'POST') {
console.log(url, method);
}
// 这里使用类型断言成const,其内部的属性值都变成了readeronly
const info = {
url: 'www.baidu.com',
method: 'GET'
} as const;
request(info.url, info.method);
export {};
对于对象的字面量赋值,在TypeScript中有一个非常有意思的现象
interface IPerson {
name: string;
age: number;
studying(this: IPerson): void;
}
// 1. 直接赋值
// const person1: IPerson = {
// name: 'coder',
// age: 18,
// studying() {
// console.log('studying');
// }
// // 新增属性会报错
// message:'hello'
// };
// 2. 间接赋值
const obj = {
name: 'star',
age: 18,
studying() {
console.log('studying');
},
message:'hello'
}
// 这样可以赋值成功
const person2: IPerson = obj;
console.log(person2); // { name: 'star', age: 18, studying: [Function: studying] }
interface IPerson {
name: string;
age: number;
studying(this: IPerson): void;
}
function foo(person: IPerson) {}
// 1. 直接赋值 => 新增属性会报错
// foo({ name: 'coder', age: 18, studying() {}, message: 'hello' });
// 2. 间接赋值 => 新增属性不会报错
const person = { name: 'coder', age: 18, studying() {}, message: 'hello' };
foo(person);
interface IPerson {
name: string;
age: number;
studying(this: IPerson): void;
}
function foo(person: IPerson) {}
/**
* 解释现象:
* 第一次创建的对象字面量,称之为fresh object (新鲜对象)
* 对于新鲜的字面量,会进行严格的类型检测,必须完全满足接口的要求(不能多也不能少)
* 1. 所以直接赋值会报错,会被严格检测
* 2. 间接赋值,已经不是新鲜的字面量了,不会被严格检测
*/
// 1. 直接赋值 => 新增属性会报错
// foo({ name: 'coder', age: 18, studying() {}, message: 'hello' });
// 2. 间接赋值 => 新增属性不会报错
const person = { name: 'coder', age: 18, studying() {}, message: 'hello' };
foo(person);
type idType = string | number | boolean;
function getId(id: idType): idType {
if (typeof id === 'string') {
return id;
}
if (typeof id === 'number') {
return id * 2;
}
if (typeof id === 'boolean') {
return id ? true : false;
}
}
// 使用 === || == || !== || != || switch
type directionType = 'left' | 'right' | 'top' | 'bottom';
function getDirect(direct: directionType) {
if (direct === 'left') {
return 'x';
}
if (direct === 'right') {
return 'y';
}
if (direct === 'top') {
return 'z';
}
if (direct === 'bottom') {
return 'w';
}
// 或者这样亦可
switch (direct) {
case 'left':
return 'x';
case 'right':
return 'y';
case 'top':
return 'z';
case 'bottom':
return 'w';
}
}
function foo(str: string | Date) {
if (str instanceof Date) {
str.getTime();
} else {
str.length;
}
}
class Student {
studying() {}
}
class Teacher {
teaching() {}
}
function boo(obj: Student | Teacher) {
if (obj instanceof Student) {
obj.studying();
} else {
obj.teaching();
}
}
type Fish = {
swimming: () => void;
};
type Bird = {
running: () => void;
};
function work(animal: Fish | Bird): void {
// 和对象中判断是否有某个属性一样
if ('swimming' in animal) {
animal.swimming();
}
if ('running' in animal) {
animal.running();
}
}
const fish: Fish = {
swimming: () => {
console.log('swimming');
}
};
const bird: Bird = {
running: () => {
console.log('running');
}
};
枚举类型是为数不多的TypeScript特性有的特性之一:
// 枚举这些数据
enum Direction {
Up, // 默认是0 可以更改默认值 up = 1,之后的值会自动加1 或者up = 'left'
Down, // 默认是1
Left, // 默认是2
Right // 默认是3
}
function trunDirection(dir: Direction): void {
switch (dir) {
case Direction.Up:
console.log('向上');
break;
case Direction.Right:
console.log('向右');
break;
case Direction.Down:
console.log('向下');
break;
case Direction.Left:
console.log('向左');
break;
default:
// 正常来说走不到这里,这样写就是为了防止枚举的数据越界,会报错
const never: never = dir;
}
}
trunDirection(Direction.Up);
trunDirection(Direction.Right);
trunDirection(Direction.Down);
trunDirection(Direction.Left);
/**
* 函数类型表达式
* 格式:(参数: 类型, 参数: 类型, ...) => 返回值类型
* 定义的时候
* 1. 形参的名字可以不和定义的参数名一致
* 2. 对于参数的个数不进行检测,可以少传,但是不能多传
* 3. 如果函数没有返回值,返回值类型可以是 void 或者省略
* 使用的时候
* 1. 参数的名字可以不和定义的参数名一致、
* 2. 参数个数一定要和定义的一致,不能少传,也不能多传
*/
type IncrementType = (x: number, y: number) => number;
// 1. 没有参数也可以
const increment: IncrementType = () => 0;
// 2. 参数的个数不进行检测,
const increment2: IncrementType = (x) => x;
const increment3: IncrementType = (x: number, y: number) => x + y;
// 3. 参数的个数可以少传,但是不能多传
// const increment4: IncrementType = (x: number, y: number, z: number) => x + y + z; // 报错,不能多传
// 使用的时候!必须传递两个参数,不能少传
console.log(increment(1, 2)); // 0
console.log(increment2(1, 2)); // 1
console.log(increment3(1, 2)); // 3
function foo() {
console.log('foo');
}
// 定义fn为函数类型 如果返回void,可以指不返回类型,可以指返回任意类型
type FnType = (num1: number, num2: number) => void;
function bar(fn: FnType) {
// 1. 调用fn,必须传入两个参数
fn(1, 2);
}
// 2. 调用bar,必须传入一个函数,且函数必须返回void,不一定要传入两个参数
bar(foo);
从对象的角度看待函数,函数除了可以被调用,自己也是可以有属性值的
函数类型表达式并不能支持声明属性
想描述一个带有属性的函数,可以在一个对象类型中写一个调用签名(call signature)
注意这个语法跟函数类型表达式稍有不同,在参数列表和返回的类型之间用的是 : 而不是 =>
interface IBar {
age: number;
address: string;
// 告知这是一个函数,可以被调用 => 函数签名
// 格式 (参数列表): 返回值类型 ,这里是用冒号表示,而不是箭头
(num1: number, num2: number): void;
}
const bar: IBar = (num: number) => {
console.log(num);
};
bar.age = 18;
bar.address = '北京市';
// 使用的时候,必须传入两个参数
bar(1, 2); // 1
console.log(bar.age); // 18
console.log(bar.address); // 北京市
JavaScript 函数也可以使用 new 操作符调用,当被调用的时候,TypeScript 会认为这是一个构造函数(constructors),因为他们会产生一个新对象
可以写一个构造签名( Construct Signatures ),方法是在调用签名前面加一个 new 关键词
// 1. 定义一个类
class Person {}
// 2. 构造签名
interface IConstructor {
// 声明这个接口可以被new调用,返回值是Person类的实例
new (): Person;
}
/**
* 3. 定义一个函数,批量创建Person类的实例
* @param fn 接收一个类作为参数,该函数可以被new调用
* @returns 返回该类的实例
*/
function factory(fn: IConstructor) {
return new fn();
}
// 4. 调用函数
const p = factory(Person); // p是Person类的实例,可以调用Person类的方法
以指定某个参数是可选的 : 该参数的类型为 指定的类型与undefined的联合类型
/**
* money是可选类型,可传可不传
* 可选的参数必须放在必传参数的后面
* 可选类型是 指定类型和undefined 的联合类型
*/
function find(info: { name: string; age: number; money?: number }) {
// 如果要使用可选类型的属性,需要先判断是否存在,因为可能不存在
if (info.money) {
console.log(info.money + 1000);
}
}
find({ name: 'coder', age: 123 });
find({ name: 'coder', age: 123, money: 1000 });
JavaScript是支持默认参数的,TypeScript也是支持默认参数的
默认参数的类型其实是 undefined 和 number 类型的联合
/**
* num2: 默认参数
* 有默认值的参数,可以不传,也可以传undefined,不会报错
*/
function foo(num1: number, num2: number = 20) {
console.log(num1 + num2);
}
foo(10);
foo(10, undefined);
foo(10, 30);
// 第一个参数放到了initNumber,剩余的参数放到了nums数组中
function foo(initNumber: number, ...nums: number[]) {
return nums.reduce((a, b) => a + b, initNumber);
}
foo(10);
foo(10, 20);
foo(10, 20, 30);
foo(10, 20, 30, 40);
foo(10, 20, 30, 40, 50);
编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用
一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现
// 函数的重载 : 函数名称相同,参数个数不同,参数类型不同
// 这里是函数的声明,没有函数体
function increment(num1: number, num2: number): number;
function increment(num1: string, num2: string): string;
// 这里是函数的实现,使用较为宽泛的类型
function increment(num1: any, num2: any): any {
// 如果是数字,则返回数字
if (typeof num1 === 'number' && typeof num2 === 'number') {
return num1 + num2;
} else if (typeof num1 === 'string' && typeof num2 === 'string') {
// 如果是字符串,则返回字符串长度
return num1.length + num2.length;
}
}
// 会自动匹配函数的参数类型
const result = increment(1, 2);
// 会自动匹配函数的参数类型
const result2 = increment('hello', 'world');
console.log(result, result2);
// 函数的重载中,实现函数是不能直接调用的
// 如果没有匹配的函数声明,则会报错
// increment({ name: 'name' }, { age: 18 });
可使用内置工具ReturnType获取函数的返回值类型
可使用内置工具Parameters获取函数的参数类型
/**
* ReturnType : 获取函数返回值的类型
* Parameters : 获取函数参数的类型
*/
type cals = (n1: number, n2: number) => number;
// 返回值类型
type calsReturnType = ReturnType; // number
// 参数类型
type calsParameters = Parameters; // [n1: number, n2: number]
function bar(str: string): string {
return 'foo';
}
// 返回值类型
type FooReturnType = ReturnType; // string
// 参数类型
type FooParameters = Parameters; // [str: string]
在没有对ts配置的情况下,this的使用可能存在隐患
可在tsconfig.json中进行配置
在没有指定this的情况,this默认情况下是any类型的
/**
* 在没有对ts配置的情况下,this的使用可能存在隐患
* this的类型是any
*/
const obj = {
name: 'demo',
studying() {
console.log(`${this.name} is studying`);
}
};
obj.studying(); // demo is studying
obj.studying.call({});
// TypeError: Cannot read property 'name' of undefined
function fn() {
console.log(this); // "this" 隐式具有类型 "any"
}
在设置了noImplicitThis为true时, TypeScript会根据上下文推导this,但是在不能正确推导时,就会报错,需要明确的指定this
如果整个项目不想指定this类型,那么要设置为 noImplicitThis:false,注释可能没用
函数的第一个参数类型
函数的第一个参数类型
// 第一个参数是this, 第二个参数开始才是函数的参数
function foo(this: { name: string }, info: {name: string}) {
console.log(this, info)
}
foo.call({ name: "why" }, { name: "kobe" })
Typescript 提供了一些工具类型来辅助进行常见的类型转换,这些类型全局可用
用于提取一个函数类型中的this 的类型
如果这个函数类型没有this参数返回unknown
function fn(this: { name: string }, age: number) {
console.log(this.name, age);
}
// 1. 拿到函数的类型
type fnType = typeof fn; // (this: { name: string }, age: number) => void 函数的类型
// 2. 通过 ThisParameterType 拿到 this 的类型
type thisType = ThisParameterType; // { name: string } this的类型
用于移除一个函数类型Type的this参数类型, 并且返回当前的函数类型
function fn(this: { name: string }, age: number) {
console.log(this.name, age);
}
// 1. 拿到函数的类型
type fnType = typeof fn; // (this: { name: string }, age: number) => void 函数的类型
// 2. 拿到删除this参数,剩余的函数类型
type pureFnType = OmitThisParameter; // (age: number) => void
这个类型不返回一个转换过的类型,它被用作标记一个上下文的this类型
用于绑定一个上下文的this
old
interface IState {
name: string;
age: number;
}
interface IStore {
state: IState;
eating: () => void;
}
const store: IStore = {
state: {
name: '张三',
age: 18
},
// 需要指定 this 的类型,比较麻烦
eating(this: IState) {
console.log(this.name); // 希望这里的 this 指向 state
}
};
store.eating.call(store.state); // 张三
new
interface IState {
name: string;
age: number;
}
interface IStore {
state: IState;
eating: () => void;
}
/**
* 通过 ThisType 指定 this 的类型为 IState
* 使用交叉类型将 this 的类型和 store 的类型合并
* 该对象中的所有方法中的 this 都会被推断为 IState
*
* 相当于不用在每个方法中都写 this: IState!
*/
const store: IStore & ThisType = {
state: {
name: '张三',
age: 18
},
eating() {
console.log(this, this.name); // this 指向 state
}
};
// store.eating.call(store.state); // { name: '李四', age: 18 } 李四
store.eating.call({ name: '李四', age: 18 }); // { name: '李四', age: 18 } 李四
class Person {
/**
* ts中的类成员属性必须要声明后使用,普通的js中可以直接使用
* 可以给默认值,也可以在构造函数中初始化
*/
name: string;
age: number = 66; // 默认值,但是不知道有什么用,构造函数中初始化的值会覆盖默认值
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const p = new Person('Jack', 32);
console.log(p.name); // Jack
console.log(p.age); // 32
const p2 = new Person('Tom', 19);
// 父类
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
eating() {
console.log('person eating~');
}
}
// 子类
class Student extends Person {
sno: string;
constructor(name: string, age: number, sno: string) {
// 调用父类的构造方法,必须写在第一行
super(name, age);
this.sno = sno;
}
// 子类重写父类的eating
eating() {
// 调用父类的方法
super.eating();
console.log('student eating~');
}
}
const s = new Student('star', 16, 't100010');
console.log(s.name); // star
console.log(s.age); // 16
console.log(s.sno); // t100010
s.eating(); // person eating~ student eating~
// 父类
class Person {
eating() {
console.log('person eating~');
}
}
// 子类
class Student extends Person {
eating() {
console.log('Student eating~');
}
}
// 子类
class Teacher extends Person {
eating() {
console.log('Teacher eating~');
}
}
// 多态的目的是为了写出更加具备通用型的代码
// 父类引用指向子类对象
function eatFunction(persons: Person[]) {
persons.forEach((person) => {
person.eating();
});
}
// 传入子类对象
eatFunction([new Student(), new Teacher()]);
class Person {
// 默认就是public
name: string;
public name2: string;
}
class Person {
private _name: string;
// 通过这样暴露属性出去
getName() {
return this._name;
}
// 通过这样更改属性
setName(name: string) {
this._name = name;
}
}
const p = new Person();
// 外面直接访问不了
// p._name
p.getName();
p.setName('star');
class Person {
protected name: string;
}
class Student extends Person {
getName() {
// 可以直接访问父类中的name
return this.name;
}
}
const s = new Student();
s.getName();
// 外面直接访问不了
// s.name
class Person {
// 1.只读属性可以在构造函数中赋值,赋值之后就不能再更改了
// 2.属性本身不能进行修改,但如果是对象类型,那么对象中的属性是可以修改的
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
const p = new Person('star');
// 不能修改
// p.name = '132';
私有属性是不能直接访问的,或者某些属性想要监听它的获取(getter)和设置(setter)的过程,这个时候可以使用存取器
可以对属性的访问和读取进行拦截操作
class Person {
private _name: string;
private _age: number;
constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
set name(name: string) {
this._name = name;
}
get name() {
return this._name;
}
set age(age: number) {
// 限制年龄范围,不合法抛出异常
if (age < 0 || age > 200) {
throw new Error('年龄不合法');
}
this._age = age;
}
get age() {
// 可以拼接一些字符串,或者做一些其他操作
return this._age;
}
}
const p = new Person('star', 20);
console.log(p.name); // star
// 赋值
p.name = 'coder';
// 获值
console.log(p.name); // coder
// 限制年龄范围
p.age = 300; // Error: 年龄不合法
TypeScript 提供了特殊的语法,可以把一个构造函数参数转成一个同名同值的类属性
在构造函数参数前添加一个可见性修饰符 public private protected 或者 readonly 来创建参数属性,最后这些类属性字段也会得到这些修饰符
/**
* 参数属性 : 语法糖
* 1. 修饰符 + 参数名
* 2. 修饰符 + 参数名 + 类型
* 3. 修饰符 + 参数名 + 类型 + 可选标识符
*
* 修饰符: public, private, protected, readonly
*
* 相当于默认做了以下操作:
* 1. 在类中定义了一个同名的成员属性 => public name: string
* 2. 在构造函数中给成员属性赋值 => this.name = name
*/
class Person {
// 1. 定义了一个同名的成员属性
// public name: string;
constructor(public name: string) {
// 2. 在构造函数中给成员属性赋值
// this.name = name;
}
sayHello() {
return 'Hello, ' + this.name;
}
}
const person = new Person('World');
console.log(person.sayHello()); // Hello, World
class Person {
// 静态属性
static age: number = 18;
// 静态方法
static eating() {
console.log('eating');
}
}
// 可直接通过类名访问
console.log(Person.name);
Person.eating();
abstract用来定于抽象类和抽象方法
继承是多态使用的前提
抽象方法
抽象类有如下的特点:
/**
* 抽象类中的抽象方法,必须在子类中实现,否则会报错
* 抽象类不能直接被实例化,只能被继承,抽象类中的抽象方法不能包含具体实现,只能用于接口
* 不能用于实例,抽象类中的抽象方法不能被static修饰,只能用于抽象类,不能用于实例
*/
// 抽象类,不能直接被实例化,只能被继承
abstract class Person {
// 抽象方法,具体实现由子类实现
abstract sayHello(): void;
// 抽象类中也可以有具体实现的方法,子类可以不实现,也可以实现,也可以重写,也可以调用
sayHi() {
console.log('Hi');
}
}
class Student extends Person {
constructor(private _studentName: string) {
super();
}
// 子类必须实现抽象类中的抽象方法,否则会报错
sayHello() {
console.log('Hello,Student', this._studentName);
}
}
class Teacher extends Person {
constructor(private _teacherName: string) {
super();
}
// 子类必须实现抽象类中的抽象方法,否则会报错
sayHello() {
console.log('Hello,Teacher', this._teacherName);
}
}
// 1. 普通的方式
const student = new Student('小明哥');
const teacher = new Teacher('decade');
student.sayHello(); // Hello,Student 小明哥
teacher.sayHello(); // Hello,Teacher decade
// 2. 多态的方式,父类的引用指向子类的对象
function saySomething(person: Person) {
person.sayHello();
person.sayHi();
}
saySomething(new Student('空我')); // Hello,Student 空我
saySomething(new Teacher('螺旋踢')); // Hello,Teacher 螺旋踢
TypeScript对于类型检测的时候使用的鸭子类型
鸭子类型
class Student {
constructor(public name: string) {}
sayHello() {
console.log('Hello,Student');
}
}
class Teacher {
constructor(public name: string) {}
sayHello() {
console.log('Hello,Teacher');
}
}
function printTeacher(people: Teacher) {
people.sayHello();
}
// 正常使用
printTeacher(new Teacher('老师')); // Hello,Teacher
/**
* 不会报错,
* 1. Student没有继承Teacher,传错了类型但是没有问题,因为使用了鸭子类型
* 只要满足有name属性和sayHello方法就可以传入
* 2. 甚至可以传入一个对象,只要满足有name属性和sayHello方法就可以传入
*/
printTeacher(new Student('小明')); // Hello,Student
printTeacher({
name: '小红',
sayHello() {
console.log('Hello,小红'); // Hello,小红
}
});
类本身也是可以作为一种数据类型的
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
eating() {
console.log(this.name + ' is eating');
}
}
const p1: Person = new Person('aaa');
p1.eating(); // aaa is eating
/**
* 因为是鸭子类型, 所以只要有name属性和eating方法就可以
* 定义p2为Person的类型,要求p2中必须有name属性和eating方法
*/
const p2: Person = {
name: 'bbb',
eating() {
console.log(this.name + ' is eating');
}
};
p2.eating(); // bbb is eating
// 通过类型 (type) 来声明对象类型
type InfoType = {
name: string;
age: 18;
};
const info: InfoType = {
name: 'John',
age: 18
};
/**
* 另外一种方式声明对象类型,使用 interface
* interface 用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法
* 同时 interface 也可以当成类型声明去使用,可以定义可选属性,也可以定义只读属性
*/
interface IInfoType2 {
readonly name: string;
age?: number;
}
const info2: IInfoType2 = {
name: 'John'
};
区别一 : type类型使用范围更广,接口类型只能用来声明对象
// type 可以声明基础类型、联合类型、元组、枚举、类、接口、函数、对象等
type infoType = number | string;
type objType = {
name: string;
age: number;
};
// interface 可以声明接口、函数、对象等
interface infoInterface {}
区别二 : 在声明对象时,interface可以多次声明
/**
* type 只能声明一次, 不能重复声明,会报错
* 不允许两个相同名称的别名同时存在
*/
type objType = {
name: string;
age: number;
};
// err, 重复声明
// type objType = {
// address: string;
// }
const obj: objType = {
name: 'lison',
age: 18
};
/**
* interface 可以声明多次, 会进行合并
* 允许两个相同名称的接口同时存在
* 使用时,合并的属性会叠加,方法会进行合并,同名方法会被覆盖 => 必须都使用上
*/
interface infoInterface {
name: string;
}
interface infoInterface {
age: number;
address?: string;
}
const info: infoInterface = {
// name和age都必须使用,否则报错, address可选
name: 'lison',
age: 18
};
区别三 : interface可以实现继承
// interface可以实现继承
interface IPerson {
name: string;
age: number;
}
interface Ikun extends IPerson {
secret: string;
}
const smallBlack: Ikun = {
// name 和 age 和 secret 都是必须的
name: '小黑子',
age: 18,
secret: '鸡你太美!!!!!!'
};
区别四 : interface可以被类实现
// interface可以被类实现
interface IPerson {
name: string;
age: number;
}
class Person implements IPerson {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
如果是非对象类型的定义 => 使用type
如果是对象类型的声明 => 使用interface,会更加灵活
type 和 interface根本区别 :
不能提前知道一个类型里的所有属性的名字,但是知道这些值的特征
这种情况,可以用一个索引签名 (index signature) 来描述可能的值的类型
// 定义类型
interface IndexLanguage {
// 索引为number,值为string
[index: number]: string;
}
const language: IndexLanguage = {
1: 'typescript',
2: 'javascript',
3: 'python',
4: 'ruby',
5: 'c++'
// 报错,索引只能是number
// '7':1
};
console.log(language[1]); // typescript
console.log(language[2]); // javascript
// type calcFn = (n1: number, n2: number) => number;
interface ICalcFn {
// 定义一个函数类型
(n1: number, n2: number): number;
}
function calc(n1: number, n2: number, calcFn: ICalcFn): number {
return calcFn(n1, n2);
}
const add = (n1: number, n2: number): number => n1 + n2;
console.log(calc(20, 30, add));
接口是支持多继承的(类不支持多继承)
interface Ieat {
eating: () => void;
}
interface Idrink {
drink: () => void;
}
// 可以多继承
interface action extends Ieat, Idrink {
sleep: () => void;
}
const person: action = {
// 都要实现
eating: () => {},
drink: () => {},
sleep: () => {}
};
类可以实现多个接口
interface IPerson {
name: string;
age: number;
studying(this: IPerson): void;
}
/**
* implements 实现接口
* 属性和方法必须实现
*/
class Person implements IPerson {
age: number;
constructor(public name: string, age: number) {
this.age = age;
}
studying() {
console.log(this.name, 'studying');
}
}
const p = new Person('why', 18);
p.studying();
interface IPerson {
eating: () => void;
}
interface IPerson {
drink: () => void;
}
// 相同的接口名会合并成一个接口
const p: IPerson = {
// 都要实现
eating: () => {},
drink: () => {}
};
// 封装一个函数,传入一个参数,并且返回这个参数
function sum(n: T): T {
return n;
}
/**
* 完整的写法
* 调用方式一 : 明确指定参数类型
* 在调用函数时,用明确的类型来代替T
*/
const n1 = sum(1); // n1的类型是number
const n2 = sum('str'); // n2的类型是string
const n3 = sum([true, false]);
const n4 = sum([
[1, 2, 3],
[4, 5, 6]
]);
/**
* 简化的写法
* 调用方式二 : 不指定参数类型,类型推断,推出来的是字面量类型
* 在调用函数时,不用明确的类型来代替T,而是通过传入的参数来推断出T的类型
* const => 返回的是字面量类型
* let => 返回的是具体的类型
*/
const n5 = sum(1); // n5的类型是字面量类型1
let n6 = sum('str'); // n6的类型是string
/**
* 通过泛型来约束,传入的参数和返回值的类型
* @param initState state的初始值
* @returns 返回一个数组, 第一个元素是state, 第二个元素是setState
*/
function useState(initState: T): [T, (newState: T) => void] {
let state = initState;
function setState(newState: T) {
state = newState;
}
return [state, setState];
}
const [count, setCount] = useState(0); // count: number | setCount: (newState: number) => void
const [age, setAge] = useState('jack'); // age: string | setAge: (newState: string) => void
function foo(arg1: T, arg2: E, arg3: O): [T, E, O] {
return [arg1, arg2, arg3];
}
// 1. 指定类型
const [a, b, c] = foo([1, 2], { name: 'why' }, true);
// 2. 类型推断
const res = foo(1, 'str', true); // [1, 'str', true] => [number, string, boolean]
/**
* 泛型
* T: 类型变量
* O:类型变量,默认值为string
*/
interface Iperson {
name: T;
age: O;
address: string;
}
// 传递类型变量,T为string,O为number,使用默认值
const p: Iperson = {
name: '张三',
age: 18,
address: '北京'
};
// 传递类型变量,T为number,O为string
const p1: Iperson = {
name: 11,
age: '22',
address: '北京'
};
class point {
x: T;
y: T;
constructor(x: T, y: T) {
this.x = x;
this.y = y;
}
}
// 1. 指定泛型类型
const p1 = new point(1, 2);
const p3: point = new point('1.11', '1.22');
// 2. 类型推断
const p2 = new point('1.11', '1.22');
// 3. 指定数组有两种,一种是元素类型后面加[], 一种是Array<元素类型>
const names1: string[] = ['Max', 'Manu'];
const names2: Array = ['Max', 'Manu'];
interface Ilength {
length: number;
}
// 规定传入的参数必须包含length属性
function getLength(target: T): T {
return target;
}
// 都拥有length属性,但是不一定是字符串,也可以是数组,对象,其他类型,所以不能用类型断言,只能用接口
const r1 = getLength('123');
const r2 = getLength([1]);
const r3 = getLength({ length: 10, name: '123' });
/**
* keyof => 获取对象的所有key, 返回一个联合类型
*/
interface IKun {
name: string;
age: number;
}
type kunkun = keyof IKun; // "name" | "age"
/**
* K extends keyof O => K必须是O的key, 且K的类型必须是O的key的类型
* 把传入的k进行类型约束
* 确保不会传入一个不存在的key
*/
function getObjectProperty(obj: O, key: K) {
return obj[key];
}
const kun: IKun = {
name: 'kun',
age: 18
};
const name = getObjectProperty(kun, 'name');
// const address = getObjectProperty(kun, 'address'); // 报错, 因为address不是kun的key
export {};
一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型
大部分内置的工具都是通过映射类型来实现的
大多数类型体操的题目也是通过映射类型完成的
映射类型建立在索引签名的语法上
// 1. 定义一个接口
interface IPerson {
name: string;
age: number;
}
/**
* 2. 映射类型函数
* 2.1. 映射类型不能用于interface, 只能用于type
* 2.2 => 表示要对Type进行映射
* 2.3 keyof Type => 表示Type中的所有key组成的联合类型 => "name" | "age"
* 2.4 [K in keyof Type] => 表示对Type中的每一个key进行映射
*/
type MapToPerson = {
[K in keyof Type]: Type[K];
};
// 3. 使用映射类型,将IPerson中的所有属性都映射到新的类型中
type newPerson = MapToPerson; // {name: string, age: number}
//4. 定义一个对象,必须包含IPerson中的所有属性
const person: newPerson = {
name: 'why',
age: 18
};
修饰符 :
interface IPerson {
name: string;
age: number;
address: string;
}
/**
* readonly : 将所有的属性变成只读的
* ? : 将所有的属性变成可选的
*/
type MapToPerson = {
readonly [k in keyof T]?: T[k];
};
/**
* 更改后的结果: 都变成了只读的可选属性
* readonly name?: string | undefined;
* readonly age?: number | undefined;
* readonly address?: string | undefined;
*/
type newPerson = MapToPerson;
还可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀
interface IPerson {
name: string;
age?: number;
readonly height: number;
address?: string;
}
/**
* -? : 删除属性的可选性
* -readonly : 删除属性的只读性
*/
type MapToPerson = {
-readonly [k in keyof T]-?: T[k];
};
/**
* 删除后的结果: 所有属性都是必选的
* name: string;
* age: number;
* height: number;
* address: string;
*/
type PersonRequire = MapToPerson;
/**
* 条件类型例子一
*/
// 1. 定一个类型
type IDType = string | number;
// 2. 条件类型 => T extends IDType ? true : false; => 三元表达式
type ResType = number extends IDType ? true : false;
console.log('-------------------------------------------------------------');
/**
* 条件类型例子二
* 函数重载
*/
// 1. 写法一 => 默认写法
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
return a + b;
}
const one1 = add(1, 2); // one1 => number类型 => 3
const onw2 = add('1', '2'); // onw2 => string类型 => '12'
// 2. 写法二 => 调用签名,函数实现签名
interface IAdd {
(a: number, b: number): number;
(a: string, b: string): string;
}
const increment: IAdd = (a: any, b: any): any => {
return a + b;
};
const two1 = increment(1, 2); // two1 => number类型 => 3
const two2 = increment('1', '2'); // two2 => string类型 => '12'
// 3. 写法三 => 使用条件类型
function sum(a: T, b: T): T extends number ? number : string;
function sum(a: any, b: any) {
return a + b;
}
const three1 = sum(1, 2); // three1 => number类型 => 3
const three2 = sum('1', '2'); // three2 => string类型 => '12'
条件类型提供了 infer 关键词
可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果
ps : 在返回值处重新写一遍
any>
/**
* 使用infer关键字
* 自定义封装一个ReturnType,获取函数的返回值类型
* 自定义封装一个Parameters,获取函数的参数类型
*/
// 使用TypeScript内置的ReturnType和Parameters
function bar(n: number): string {
return `${n}hello`;
}
// 函数的返回值类型
type FooReturnType = ReturnType; // string
// 函数的参数类型
type FooParameters = Parameters; // [n: number]
console.log('------------------------------------');
// 使用infer关键字,自定义封装一个ReturnType,获取函数的返回值类型
/**
* (...args: any[]) => any 代表一个函数类型
* T extends (...args: any[]) => any 代表泛型T必须是一个函数类型,且函数的参数类型和返回值类型是什么不确定,所以用T代替 => 限制T的范围
* (...args: any[]) => infer R 代表函数的返回值类型是什么不确定,所以用R代替
*/
type MyReturnType any> = T extends (...args: any[]) => infer R
? R
: never;
type FooReturnType2 = MyReturnType; // string
console.log('------------------------------------');
// 使用infer关键字,自定义封装一个Parameters,获取函数的参数类型
/**
* (...args: any[]) => any 代表一个函数类型
* T extends (...args: any[]) => any 代表泛型T必须是一个函数类型,且函数的参数类型和返回值类型是什么不确定,所以用T代替 => 限制T的范围
* (...args: infer P) => any 代表函数的参数类型是什么不确定,所以用P代替
*/
type MyParameters any> = T extends (...args: infer P) => any
? P
: never;
type FooParameters2 = MyParameters; // [n: number]
当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成 分发的
很多内置工具底层都是用了分发,很重要!!!
type toArray = T extends any ? T[] : never;
type NumberArray = toArray; // number[]
type StringArray = toArray; // string[]
/**
* number[] | string[] 而不是 (number | string)[]
* 因为extxtends是分发式的, 会把联合类型拆分成多个条件, 然后再进行条件判断, 最后再合并成一个联合类型
* 1. 当传入string | number时,会遍历联合类型中的每一个成员
* 2. 相当于执行了ToArray | ToArray
* 3. 最后合并成一个联合类型,也就是string[] | number[]
*/
type NumberOrStringArray = toArray; // number[] | string[]
主要有两种 :
commonjs => node环境 => webpack/vite
module.exports = {}
ESmodule => 在TypeScript中最主要使用的模块化方案就是ES Module
import / export 语法
util/math.ts
export function sum(num1: number, num2: number): number {
return num1 + num2;
}
util/math.ts
export type Idtype = string | number;
export interface IPersion {
name: string;
age: number;
}
main.ts
// 导入普通的js模块,正常使用即可
import { sum } from './util/math';
console.log(sum(1, 2)); // 3
/**
* 当导入类型时,可以使用type关键字
* 因为最终编译后的代码中,不会有ts的类型代码
*
* 让编译器自己识别的话,会比较耗性能,因为要多一步类型的判断
* 所以用type声明后,方便编译打包时的删除,减少代码量,提高性能
*/
import { type Idtype, type IPersion } from './util/type';
// 如果导入的全都是类型,可以使用import type
// import type { Idtype, IPersion } from './util/type';
const id: Idtype = 123;
const person: IPersion = {
name: 'why',
age: 18
};
了解即可,直接使用ES模块
// 定义命名空间
namespace time {
// 只有导出的函数才能被外部调用
export function format(): number {
return new Date().getTime();
}
function foo(): string {
return '123';
}
const bar = '123';
// 只有导出的变量才能被外部调用
export const baz = '456';
}
// 调用命名空间导出的函数
console.log(time.format()); // 1702541990130
// 调用命名空间导出的变量
console.log(time.baz); // 456
// 导出命名空间,可以被其他模块使用
export namespace price {
export function format(): string {
return '123.456';
}
}
// 其他模块
// import { price } from './文件名.ts';
内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件
内置类型声明通常在我们安装typescript的环境中会带有的
TypeScript 使用模式命名这些声明文件lib.[something].d.ts
可在 tsconfig.json 的 target以及l ib 中进行配置内置声明
外部类型声明通常是我们使用一些库(比如第三方库)时,需要的一些类型声明
外部定义类型声明 通常有两种类型声明的方式
在自己的库中进行类型声明 ( 编写.d.ts文件 )
例如 : axios,可在node_modules中找到
通过社区的一个 公有库DefinitelyTyped 存放类型声明文件
例如 : react,在node_modules中找不到
DefinitelyTyped的github地址 : GitHub - DefinitelyTyped
( 推荐 ) 查找声明安装地址的方式 : TypeScript: Search for typed packages
假如第三方库中没有声明文件,需要自己编写.d.ts文件~
只要有代码的实现,声明一下,在代码中即可运行,这里使用webpack配置的环境
ps : 不需主动引入,会自动查找
在index.html中写代码的实现
ps :
按道理来说,这里是最顶层,这里定义后,其他的文件中都能使用,但是不行
因为其他的打包后会到bound.js中,然后在这里被引用
Document
在src目录下生成star.d.ts文件
需要生声明后,变成全局后,才能在其他ts中使用
// 声明变量
declare const starName: string;
// 声明函数
declare function foo(): void;
// 如果用到了图片,需要声明下,要不然解析不了, 因为图片是一个文件, 所以不能直接在声明文件中引用
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.svg';
declare module '*.bmp';
declare module '*.tiff';
// 声明vue文件
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
// 声明命名空间,是全局的,可通过Star.xxx访问
declare namespace Star {
export const coderName: string;
}
在main.js中进行调用
// 如果没有在.d.ts文件中声明,是访问不到的
console.log(starName); // 123
foo(); // foo
JavaScript 项目可以使用 jsconfig.json 文件,它的作用与 tsconfig.json 基本相同,只是默认启用了一些 JavaScript 相关的编译选项
tsconfig.json文件有两个作用:
在调用 tsc 命令并且没有其它输入文件参数时,编译器将由当前目录开始向父级目录寻找包含 tsconfig 文件的目录
调用 tsc 命令并且没有其他输入文件参数,可以使用 --project (或者只是 -p)的命令行选项来指定包含了 tsconfig.json 的目录
webpack中使用ts-loader进行打包时,也会自动读取tsconfig文件,根据配置编译TypeScript代码
练习 练习+答案
用于构造一个Type下面的所有属性都设置为可选的类型
interface Ikun {
name: string;
age: number;
say?: () => void;
}
/**
* Ikun都变成可选的
* Partial 的作用就是将某个类型里的属性全部变为可选项 ?
* 结果如下:
* interface Ikun {
* name?: string | undefined;
* age?: number | undefined;
* say?: () => void | undefined;
* }
*/
type IkunOptional = Partial;
interface Ikun {
name: string;
age: number;
say?: () => void;
}
/**
* 使用映射类型实现Partial
* 将某个类型里的属性全部变为可选项 ?
*/
type MyPartial = {
[P in keyof T]?: T[P];
};
/**
* 结果如下:
* type IkunOptional = {
* name?: string | undefined;
* age?: number | undefined;
* say?: () => void | undefined;
* }
*/
type IkunOptional = MyPartial;
用于构造一个Type下面的所有属性全都设置为必填的类型,这个工具类型跟 Partial 相反
interface Ikun {
name?: string;
age: number;
say?: () => void;
}
/**
* Ikun都变成必填的
* Required 的作用就是将某个类型里的属性全部变为可选项 ?
* 结果如下:
* interface Ikun {
* name: string;
* age: number;
* say: () => void;
* }
*/
type IkunOptional = Required;
interface Ikun {
name?: string;
age: number;
say?: () => void;
}
/**
* 使用映射类型实现Required
* 将某个类型里的属性全部变为必传属性
*/
type MyRequired = {
[P in keyof T]-?: T[P];
};
/**
* 结果如下:
* type IkunRequired = {
* name: string;
* age: number;
* say: () => void;
* }
*/
type IkunOptional = MyRequired;
用于构造一个Type下面的所有属性全都设置为只读的类型,意味着这个类型的所有的属性全都不可以重新赋值
interface Ikun {
name?: string;
age: number;
say: () => void;
}
/**
* Ikun都变成只读的
* Readonly 会将所有属性变成只读的,也就是不可修改
* 结果如下:
* interface Ikun {
* readonly name?: string | undefined;
* readonly age: number;
* readonly say: () => void;
* }
*/
type IkunOptional = Readonly;
interface Ikun {
name?: string;
age: number;
say: () => void;
}
/**
* 使用映射类型实现readonly
* 将某个类型里的属性变成只读的
*/
type MyReadonly = {
readonly [P in keyof T]: T[P];
};
/**
* 结果如下:
* interface Ikun {
* readonly name?: string | undefined;
* readonly age: number;
* readonly say: () => void;
* }
*/
type IkunOptional = MyReadonly;
用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型
type IAddresskeys = '上海' | '北京' | '广州' | '深圳';
interface Ikun {
name: string;
age: number;
say?: () => void;
}
/**
* Record 用于将 K 中所有的属性的值转化为 T 类型
* 结果的类型为一个新的对象类型,该对象包含了 K 中所有的属性,每个属性值都是 T 类型
* 结果如下:
* type IBlack = {
* 上海: Ikun;
* 北京: Ikun;
* 广州: Ikun;
* 深圳: Ikun;
* }
*/
type IBlack = Record;
const black: IBlack = {
上海: { name: '小红', age: 18 },
北京: { name: '小蓝', age: 18 },
广州: { name: '小紫', age: 18 },
深圳: {
name: '小黑子',
age: 18,
say() {
console.log('深圳');
}
}
};
type IAddresskeys = '上海' | '北京' | '广州' | '深圳';
interface Ikun {
name: string;
age: number;
say?: () => void;
}
/**
* Keys 必须 是联合类型,且联合类型的值必须是 'string' | 'number' | 'symbol' 的子集
* 1. keyof any => 'string' | 'number' | 'symbol',代表的是所有可以作为属性名的类型
* 2. Keys extends keyof any => 代表的是 Keys 必须是 'string' | 'number' | 'symbol' 的子集
* 3. [P in Keys]: T => 代表的是遍历 Keys,将 Keys 中的属性名作为新对象的属性名,属性值为 T
*/
type MyRecord = {
[P in Keys]: T;
};
/**
* 结果如下:
* type IBlack = {
* 上海: Ikun;
* 北京: Ikun;
* 广州: Ikun;
* 深圳: Ikun;
* }
*/
type IBlack = MyRecord;
const black: IBlack = {
上海: { name: '小红', age: 18 },
北京: { name: '小蓝', age: 18 },
广州: { name: '小紫', age: 18 },
深圳: {
name: '小黑子',
age: 18,
say() {
console.log('深圳');
}
}
};
用于构造一个类型,它是从Type类型里面挑了一些属性Keys
interface Ikun {
name: string;
age: number;
say?: () => void;
address?: string;
}
/**
* Pick 从某个类型中挑选出指定的属性
* 结果如下:
* type IkunPick = {
* name: string;
* address?: string | undefined;
* }
*/
type IkunPick = Pick;
interface Ikun {
name: string;
age: number;
say?: () => void;
address?: string;
}
/**
* K extends keyof T 表示 K 是 T 的属性 => 约束 K 的类型
* [P in K]: [P 是 K 的属性]
*/
type MyPick = {
[P in K]: T[P];
};
/**
* 结果如下:
* type IkunPick = {
* name: string;
* address?: string | undefined;
* }
*/
type IkunPick = MyPick;
用于构造一个类型,它是从Type类型里面过滤了一些属性Keys
interface Ikun {
name: string;
age: number;
say?: () => void;
address?: string;
}
/**
* Omit 表示忽略 T 中的 K 属性
* 获取 Ikun 中除了 name 属性之外的所有属性
* 结果如下:
* type IkunOmit = {
* age: number;
* say?: (() => void) | undefined;
* address?: string | undefined;
* }
*/
type IkunOmit = Omit;
interface Ikun {
name: string;
age: number;
say?: () => void;
address?: string;
}
/**
* 从T中排除K
* 使用了分发条件类型
* P in keyof T => 表示遍历T中的所有属性
* as P => 表示将遍历的结果赋值给P
* P extends K ? never : P => 表示如果P是K的子集,则返回never,否则返回P
*/
type MyOmit = {
[P in keyof T as P extends K ? never : P]: T[P];
};
/**
* 结果如下:
* type IkunOmit = {
* age: number;
* address?: string | undefined;
* }
*/
type IkunOmit = MyOmit;
用于构造一个类型,它是从UnionType联合类型里面排除了所有可以赋给ExcludedMembers的类型
interface Ikun {
name: string;
age: number;
say?: () => void;
address?: string;
}
/**
* Exclude
* 从UnionType中排除ExcludedMembers
* UnionType: 联合类型
* ExcludedMembers: 要排除的成员, 可以是单个成员,也可以是多个成员组成的联合类型
*
* 结果如下:
* type IkunExclude = "address" | "say"
*/
type IkunExclude = Exclude;
可以使用它来实现MyOmit
interface Ikun {
name: string;
age: number;
say?: () => void;
address?: string;
}
/**
* 从T中排除U
* Exclude => keyof T中排除U,把交集排除掉
* [K in Exclude]: T[K] => 从T中排除U后,再遍历T中的key,然后取出T中的value
*/
type MyOmit = {
[K in Exclude]: T[K];
}
/**
* 结果如下:
* type IkunExclude = "address" | "say"
*/
type IkunExclude = MyOmit;
interface Ikun {
name: string;
age: number;
say?: () => void;
address?: string;
}
/**
* 从T中排除U
* 使用了分发条件类型
* T extends U => 表示如果T是U的子集,那么返回never,否则返回T
*/
type MyExclude = T extends U ? never : T;
/**
* 结果如下:
* type IkunExclude = "address" | "say"
*/
type IkunExclude = MyExclude;
用于构造一个类型,它是从Type类型里面提取了所有可以赋给Union的类型,和Exclude相反
interface Ikun {
name: string;
age: number;
say?: () => void;
address?: string;
}
/**
* Extract
* 从Type中提取出可以赋值给Union的类型
* Type: 联合类型
* Union: 要提取的类型,单个类型或者联合类型
* 结果如下:
* type IkunExclude = "name" | "age"
*/
type IkunExtract = Extract;
interface Ikun {
name: string;
age: number;
say?: () => void;
address?: string;
}
/**
* 方式一
* @param T => Ikun
* @param U => 'name' | 'age'
* K in keyof T => k是Ikun的key, 也就是name, age, say, address
* as K => 表示将遍历的结果赋值给K
* K extends U ? K : never => 如果K是U的子集,那么返回K, 否则返回never
*/
type MyExtract = {
[K in keyof T as K extends U ? K : never]: T[K];
};
/**
* 结果如下
* type IkunExtract{
* name: string;
* age: number;
* }
*/
type IkunExtract = MyExtract;
console.log('------------------------------------');
/**
* 方式二
* @param T => Ikun的所有key, 也就是name | age | say | address
* @param U => 'name' | 'age'
* T extends U ? T : never => 如果T是U的子集,那么返回T, 否则返回never
*/
type MyExtract2 = T extends U ? T : never;
/**
* 结果如下
* type IkunExtract{
* name: string;
* age: number;
* }
*/
type IkunExtract2 = MyExtract2;
用于构造一个类型,这个类型从Type中排除了所有的null、undefined的类型
type IKun = string | number | undefined | null | boolean;
/**
* NonNullable
* 从类型T中排除null和undefined
* 结果如下
* type Ikuns = string | number | boolean
*/
type Ikuns = NonNullable;
type IKun = string | number | undefined | null | boolean;
/**
* T extends null | undefined => 如果T是null或者undefined,返回never,否则返回T
*/
type MyNonNullable = T extends null | undefined ? never : T;
/**
* 结果如下
* type Ikuns = string | number | boolean
*/
type Ikuns = MyNonNullable;
用于构造一个由所有Type的构造函数的实例类型组成的类型
class Person {}
const p1: Person = new Person();
/**
* typeof Person => Person构造函数的类型
* InstanceType => Person构造函数构建出的实例的类型
*/
type MyPerson = InstanceType;
const p2: MyPerson = new Person();
class Person {}
class Studen {}
/**
* 可以使用infer关键字来推断出函数的返回值类型
* 推断出p的类型为Person
* 推断出s的类型为Studen
* 在返回值处重新写一遍 any>
* 是为了让函数的返回值类型和传入的构造函数类型保持一致
* 如果不写的话,会推断出p和s的类型为any
*/
function fectory any>(
ctor: T
): T extends new (...args: any[]) => infer R ? R : any {
return new ctor();
}
const p = fectory(Person);
const s = fectory(Studen);
console.log('-------------------');
/**
* 可以使用InstanceType来获取构造函数的实例类型
* 推断出p2的类型为Person
* 推断出s2的类型为Studen
*/
function fectory2 any>(ctor: T): InstanceType {
return new ctor();
}
const p2 = fectory2(Person);
const s2 = fectory2(Studen);