官方文档
Atwood定律
Stack Overflow 的创立者之一 Jeff Atwood 在 2007年提出了著名的 Atwood 定律
any application that can be written in JavaScript , will eventually be written in JavaScript.
任何可以使用JavaScript来实现的应用都最终会使用 JavaScript 实现
类型检测缺失, 很容易造成代码崩溃等等
为了弥补 JavaScript 类型约束上的缺陷,增加类型约束,很多公司推出了自己的方案
2014年,Facebook 推出了 flow 来对JavaScript进行类型检查
同年,Microsoft 微软,也推出了 typeScript 1.0 版本
vue2.x 采用的就是 flow 来做的类型检查
vue3.x 采用的是 TS
TS 是 拥有类型的 js 超集,它可以编译成 普通、干净、完整的 js 代码
始于JavaScript,归于 JavaScript
TS 从今天数以百万计的js开发者所熟悉的语法和语义开始。使用现有的JS代码,包括流行的js库,并从JS代码中调用TS代码
TS 代码可以编译出 纯净、简洁 的js 代码,并且可以运行再任何浏览器上、node.js 环境中、任何支持ECMAScript 3 (或更高的版本) 的 JS 引擎中
TS是一个强大的工具,用于构建大型项目
拥有先进的JS
众多项目采用TS
在前面我们提到过,TypeScript最终会被编译成JavaScript来运行,所以我们需要搭建对应的环境:我们需要在电脑上安装TypeScript,这样就可以通过TypeScript的Compiler将其编译成JavaScript;
# 安装命令
npm install typescript -g
# 查看版本号 2022年1月21,版本号是 Version 4.5.5
tsc --version
方案一
方案二
通过webpack,配置本地的TypeScript编译环境和开启一个本地服务,可以直接运行在浏览器上;
部署文章链接
方案三
通过ts-node库,为TypeScript的运行提供执行环境;
# 安装ts-node
npm install ts-node -g
# 另外 ts-node 还需要安装 tslib 喝 @types/node 两个包
npm install tslib @types/node -g
# 现在就可以直接通过 ts-node 运行 TS后缀的文件
ts-node math.ts
小提示: 在写TS 的时候,默认情况下,所有的TS 文件,都是在一个作用域下编译的,所以可能会冲突,如果要把每一个文件,看成一个单独的作用域, 需要在每个文件里面最后加上 export {} 。还有其他解决方案
声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解
var/let/const 标识符: 数据类型 = 赋值;
注意:这里的string是小写的,和String是有区别的,string是TypeScript中定义的字符串类型,String是ECMAScript中定义的一个类
类型推导:在开发中,有时候为了方便起见我们并不会在声明每一个变量时都写上对应的数据类型,我们更希望可以通过TypeScript本身的特性帮助我们推断出对应的变量类型:
let message = "ddg" message = 123 // 这里其实会报错的
这是因为在一个变量第一次赋值时,会根据后面的赋值内容的类型,来推断出变量的类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OAIldHYF-1643279385255)(coderwhy-vue3-组件化.assets/image-20220121145723489.png)]
TS 是 JS 的超集
数字类型是我们开发中经常使用的类型,TypeScript和JavaScript一样,不区分整数类型(int)和浮点型(double),统一为number类型。
ES6新增了二进制和八进制的表示方法,而TypeScript也是支持二进制、八进制、十六进制的表示:
let num: number = 123
num = 333
let num1: number = 100 // 十进制
let num2: number = 0b111 // 二进制,以 0b 开头
let num3: number = 0o456 // 八进制 ,以 0o 开头
let num4: number = 0x123abc // 16进制 ,以 0x 开头
let num: boolean = true
let message1: string = "hello world"
let message2: string = "ddg"
const name = "ddg"
const age = 18
const height = 1.88
let message3 = `name:${name} age:${age} height${height}`
console.log(message3);
// 确定一个事实,names 虽然是一个数组类型,但是数组中存放的是什么类型的元素呢?
// 一个数组中,在TS 开发中,最好存放的数据类型是固定的(string)
// 在数组中存放不同的类型 是不好的习惯
// 类型注解: type annotation
const names: Array = [] // 第一种写法, 但是 不推荐 (在 react jsx 、vue jsx 中是有冲突的 )
const names2: string[] = [] // 推荐
names.push('abc')
const info = {
name: "呆呆狗",
age: 21
}
// 如果没有写 类型注解, 他会 自动的 推导出来
let n1: null = null
let n2: undefined = undefined
jses6 的新知识。可以通过 symbol来定义相同的名称,因为 symbol 函数返回的是不同的值
const t1 = Symbol("title")
const t2 = Symbol("title")
const info = {
[t1]: "呆呆狗",
[t2]: "学生"
}
export { }
在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型
let message: any = "hello world"
// 类型 设置为 any 后,可以给它赋任何值
/*
当进行一些 类型断言 as any
在不想给某些JS 添加具体的数据类型的时候,用 any 不会出错, 其实就当于 原生JS
*/
message = 123
message = true
unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。
function foo() {
return "abc"
}
function bar() {
return 123
}
let flag = true
let result: any
// let result: unknown
// unknown类型只能赋值给 any 和 unknown类型
// any类型 可以赋值给任意类型
if (flag) {
result = foo()
} else {
result = bar()
}
// 如果 result 设置的是 any 类型 这样赋值是没问题的
let message: string = result
let num: number = result
console.log(result);
console.log(message);
// 如果 result 设置的是 unknown类型,然后给 message 和num 赋值 是不行的
export { }
viod 通常用来指定一个函数,是没有返回值的,那么它的返回值就是 void 类型
- 我们可以将 null 和 undefined 赋值给 void 类型,也就是函数可以返回 null 或者 undefined
function sum(num1: number, num2: number) {
console.log(num1 + num2);
// 如果没有返回值,默认会返回一个 undefined
// 其实,这个函数,默认返回值的类型 就是 void
// 我们可以将 null 和 undefined 赋值给 void 类型,也就是函数可以返回 null 或者 undefined
}
// sum(123, 456)
console.log(sum(123, 456));
export { }
如果一个函数中是一个死循环或者抛出一个异常,这个函数是不会有返回值的。 那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型
function foo(): never {
// 死循环
while (true) {
// never 就表示 这个函数永远不会有返回值
}
}
function bar(): never {
throw new Error('返回错误')
}
// 这里的 message 是一个联合类型,可以是 字符串,可以是 数字
// 防止别人再增加了 message的类型注解后,没有写 相应的 case
function handleMessage(message: string | number | boolean) {
switch (typeof message) {
case 'string':
console.log('string 处理方式');
break
case 'number':
console.log('number 处理方式');
break
case 'boolean':
console.log('boolean 处理方式');
break
default:
// 永远不可能来到这里,所以可以给它赋值
const check: never = message
}
}
handleMessage(true)
// 如果 想要给 handleMessage 函数,传递一个布尔类型的值呢?
// 可以在 message 的类型注解 哪里,再加一个 Boolean类型
// 但是这样,还需要 再加 一个 case 'boolean'
基本使用
// tuple 元组,多种元素组合
// 元组类型,可以确定数组每一项的类型,
// 如果直接给数组写个any 类型, 不太安全
// ddg 19 180
const info: [string, number, number] = ["ddg", 18, 180]
const name = info[0]
console.log(name.length);
const age = info[1]
// console.log(age.length);
export { }
应用场景
// hooks : useState
// const [counter,setCounter] = useState(10)
// 如果要是 useState 里面要是一个数组的话,太不安全了
function useState(state: any) {
// state 可能是一个 数组,字符串,数字,布尔,等等
let currentState = state
const changeState = (newState: any) => {
currentState = newState
}
// 这样以后,我们在外面调用这个函数,并且解构, 两个值都是 any 类型
// const arr: any[] = [currentState, changeState]
const arrTuple: [any, (newState: any) => void] = [currentState, changeState]
// return arr
return arrTuple
}
// 这个时候, counter 还是 any 类型,而 setCounter 是一个函数类型
const [counter, setCounter] = useState(10)
export { }
应用场景优化
// hooks : useState
// const [counter,setCounter] = useState(10)
// 如果要是 useState 里面要是一个数组的话,太不安全了
// 如果要优化,就要用到 泛型
function useState(state: T) {
// state 可能是一个 数组,字符串,数字,布尔,等等
let currentState = state
const changeState = (newState: T) => {
currentState = newState
}
// 这样以后,我们在外面调用这个函数,并且解构, 两个值都是 any 类型
// const arr: any[] = [currentState, changeState]
const arrTuple: [T, (newState: T) => void] = [currentState, changeState]
// return arr
return arrTuple
}
// 这个时候就没问题了
const [counter, setCounter] = useState(10)
const [flag, setFlag] = useState(true)
export { }
// 给参数 加上类型注解
// 给返回值加上类型注解,没有返回值的时候 就是 viod , 给返回值加上类型注解,一般写在函数的参数 括号的后面
// 在开发中,通常情况下,可以不写返回值的类型(自动推导)
function sum(num1: number, num2: number): number {
// 上面这两个参数 是必须要传递的, 传一个就会报错
return num1 + num2
}
function foo(message: string) {
// 通常情况下,在定义一个函数时,都会给参数加上类型注释的
}
const names = ["abc", "c", "a"]
names.forEach((item) => {
// 可以不给 item 加类型
// item 的类型是根据上下文的环境推导出来的
// 当我们把我们的一个函数作为参数传递给另外一个函数的时候,某些情况下,它会自动推导出参数的类型,这个时候可以不添加的类型注解
})
// 参数是一个对象
function printPoint(point: { x: number, y: number }) {
// point: { x: number, y: number } 这个对象 里面的 每个键 可以用逗号或者分号进行隔开
console.log(point.x);
console.log(point.y);
}
printPoint({ x: 20, y: 30 })
// 参数是一个字符串
function printString(message: string) { }
// 参数是一个对象
// 如果需要用户传 x,y,z,z是可选的呢? ,z?:number , 在z 后面加个问号就可以了
function printPoint(point: { x: number, y: number, z?: number }) {
// point: { x: number, y: number } 这个对象 里面的 每个键 可以用逗号或者分号进行隔开
console.log(point.x);
console.log(point.y);
}
printPoint({ x: 20, y: 30 })
printPoint({ x: 20, y: 30, z: 1111 })
// 参数是一个字符串
function printString(message: string) { }
export { }
TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型。
// number | string 联合类型
function printID(id: number | string) {
// 使用联合类型的值的时候,要特别的小心
// narrow : 缩小
if (typeof id === 'string') {
// ts 帮助确定 id 一定是 string 类型
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
printID(123)
printID('abc')
export { }
// type 定义类型别名
type IDType = string | number | boolean
type PointType = {
x: number,
y: number,
z: number
}
function printId(id: IDType) {
}
function Point(id: PointType) {
}
export { }
有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)
比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它
具体的类型
TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换
不太具体,指的就是 any 或者 unknown
//
const el = document.getElementById("ddg") as HTMLImageElement
el.src = "url地址" // 会报错: 类型“HTMLElement”上不存在属性“src”。
// document.getElementById("ddg") as HTMLImageElement 这样修改就不会报错了
// 把普遍的类型 转成 具体的类型
class Person { }
class Student extends Person {
studying() { }
}
function sayHello(p: Person) {
// p.studying() // 直接访问是 访问不到的
(p as Student).studying()
}
const stu = new Student()
sayHello(stu)
// 3. 建议不要这样做
const message = "hello world"
// const num: number = message
// const num: number = (message as any) as number 这个可以
// const num: number = (message as unknown) as number 这个也可以
export { }
// message ? => undefined | string
function printMessage(message?: string) {
// 别人有可能传,也有可能不传
// 如果不传,这样会报错
// console.log(message.length);
// 但是,我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言
// 非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测
// ! 表示的含义就是 message 一定有值
console.log(message!.length);
}
printMessage("hello world")
// printMessage()
虽然逃避了检查,但是代码还是不够严谨
可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性
type Person = {
name: string,
friend?: {
name: string,
age?: number,
}
}
const info: Person = {
name: "呆呆狗",
// friend: {
// name: "kobe"
// }
}
// 另外一个文件中,
console.log(info.name);
//console.log(info.friend); // info.friend 有可能取到,有可能取不到。friend 是可选的
console.log(info.friend?.name);
// info 的 friend 可能有值 也可能没有, 如果没有friend,后面的代码不再执行,整个表达式返回 undefined
!!
??
const message = "hello wolrd"
// const flag = Boolean(message)
// console.log(flag);
// !! 本身就是 js 的特性
const flag = !!message
console.log(flag);
let message: string | null = null
// 如果 message 为 null 给他一个默认值
const content = message ?? "你好啊"
console.log(content);
export { }
字面量更常见的是 联合类型,
// 如果是 let 定义,那它的类型是 string
let message = "hello world"
// 如果是 const 定义的,那它的类型是 const message2: "hello world"
// 也就是说 hello world 就是一个类型,叫做 字面量类型
const message2 = "hello world"
let num: 123 = 123
// num = 321 修改字面类的值,是不可能的, 类型必须和 值 相等
// 字面量类型的意义,就是必须结合 联合类型
let align: 'left' | 'right' | 'center' = 'left'
align = "right" // 这样赋值没问题, 赋的值 必须是 类型的其中一个
export { }
// 没有写类型注解,他会自动推导
const info = {
name: "ddg",
age: 20
}
info.name = "kobe"
// 例子
type Method = 'GET' | 'POST'
function request(url: string, method: Method) {
}
// 方案1: 写死 类型注解 .. 建议方案1
// type Request = {
// url: string,
// method: Method
// }
// const options: Request = {
// url: "http://www.daidaigou.top",
// method: "POST"
// }
// 方案3
const options = {
url: "http://www.daidaigou.top",
method: "POST"
} as const
// 在传递的时候,第二个参数会报错,因为 options.method 这个属性的类型会 自动推导出是 string类型 , 而函数的第二个参数是 Method
// request(options.url, options.method)
// request(options.url, options.method)
// 方案2 用类型断言
request(options.url, options.method as Method)
常见的类型保护有以下几种
// 1.typeof 的类型缩小
function printID(id: number | string) {
// id 是一个联合类型,不能随便进行处理
// 整个语句的过程就叫做: 类型缩小
if (typeof id === 'string') {
// 在这里 拿到的 id 一定是一个 string 类型
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
// 2.平等的类型缩小 (=== == !== != switch)
function printDireaction(direction: 'left' | 'right' | 'top' | 'bottom') {
// if (direction === "left") {
// }else if () {
// }
// switch(direction){
// case 'left': 等等
// }
}
// 3.instanceof
function printTime(time: string | Date) {
if (time instanceof Date) {
// 如果是 date 类型
} else {
console.log('xxx');
}
}
class Student {
studying() { }
}
class Teacher {
teaching() { }
}
function work(p: Student | Teacher) {
if (p instanceof Student) {
p.studying()
} else {
p.teaching()
}
}
const stu = new Student()
work(stu)
// work 传的 是一个 Student 或 Teacher的实例
// 4. in
type Fish = {
swimming: () => void
}
type Dog = {
running: () => void
}
function walk(animal: Fish | Dog) {
if ('swimming' in animal) {
// 就是判断属性,这里的 Fish Dog 不是类,只是 字面量
animal.swimming()
} else {
animal.running()
}
}
const fish: Fish = {
swimming() {
console.log("swimming");
}
}
walk(fish)
// 1.函数作为参数时,在参数中如何编写类型
function foo() { }
function bar(fn: () => void) {
fn()
}
bar(foo)
// 2,定义常量时,编写函数的类型
const add: (num1: number, num2: number) => number = (num1: number, num2: number) => {
return num1 + num2
}
// y -> undefined | number
function foo(x: number, y?: number) {
}
// 第一个参数,必须是 必选的 !
foo(20, 30)
foo(40)
// 先写 必选参数 - 有默认值的参数 - 可选参数
function foo(x: number = 20, y: number = 100) {
console.log(x, y);
}
foo(undefined, 40)
function sum(...nums: number[]) {
console.log(nums);
}
sum(10, 20, 30, 40, 50)
// this 是可以被 推导出来, info对象(TS推导出来的)
const info = {
name: "呆呆狗",
eating() {
console.log(this.name + " eating");
}
}
info.eating()
// 在TS中 this 是不能乱用的
// 独立函数 推导不出来 this
// 解决方案:传一个参数,并且放在第一位
function eating(this: { name: string }) {
console.log(this.name + " eating");
}
const info = {
name: "呆呆狗",
eating: eating,
}
// 隐式绑定
info.eating()
// 显示绑定
// 如果 传递了 this ,就不能直接写 eating()
eating.call({ name: "2222" })
export { }
在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢?
// 函数的重载:函数名称相同,但是参数不同的几个函数,就是函数的重载
// TS 的函数重载,重上往下匹配,函数体 写在最后
// 别的语言写法
// function add(num1: number, num2: number) { }
// function add(num1: number, num2: number, num3: number) { }
// TS 函数重载写法
function add(num1: number, num2: number): number; // 没有函数体
function add(num1: string, num2: string,): string;
// 实现函数, 这个函数 就是 实现函数
function add(num1: any, num2: any): any {
// 函数体
if (typeof num1 === 'string' && typeof num2 === 'string') {
return num1.length + num2.length
}
return num1 + num2
}
console.log(add(20, 30));
console.log(add('123', '456'));
// 在函数的重载中,实现函数是不能直接被调用的。必须要 重上往下 进行匹配
// add({name:"呆呆狗"},{age:29})
// 如果能通过联合类型 简单实现,那就用联合类型
// 实现方式一:联合类型 实现
// function getLength(args: string | any[]) {
// return args.length
// }
// console.log(getLength("abc"));
// console.log(getLength([12, 23, 11, 23]));
// 实现方式二:函数重载 实现
function getLength(args: string): number;
function getLength(args: any[]): number;
function getLength(args: any): number {
return args.length
}
console.log(getLength("abc"));
console.log(getLength([12, 23, 11, 23]));
// 类的属性必须初始化,要么直接在定义的时候进行初始化,要么用constructor 初始化
class Person {
// 属性和方法
// 可以在定义属性后面,直接进行赋值,初始化
// name: string = ""
// age: number = 0
name: string
age: number
// 也可以用 constructor 来进行初始化
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eating() {
console.log(this.name + " eating");
}
}
const p = new Person("ddg", 21)
export { }
class Person {
name: string = ""
age: number = 0
eating() {
console.log("eating");
}
}
class Student extends Person {
sno: number = 0
studying() {
console.log("studying");
}
}
class Teacher extends Person {
titlle: string = ""
teaching() {
console.log("Teachering");
}
}
const stu = new Student()
console.log(stu);
stu.eating()
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: number
constructor(name: string, age: number, sno: number) {
super(name, age) // 调用父类的构造方法
this.sno = sno
}
// 子类对父类的方法,不满意 可以进行重写
eating() {
console.log("Student eating");
// 如果 在 子类里面,非得 调用 父类的 方法呢?
super.eating()
}
studying() {
console.log("studying");
}
}
const stu = new Student("ddg", 21, 201813212)
console.log(stu);
stu.eating()
export { }
在TypeScript中,类的属性和方法支持三种修饰符: public、private、protected
// protected: 在类内部和子类中可以访问,
class Person {
protected name: string = ""
getName() {
return this.name
}
}
const p = new Person()
console.log(p.name); // 这样是不可以访问的 会报错
export { }
class Person {
// 只读属性,可以在构造器中赋值,赋值完毕后,就不可以修改了
// 属性本身不能进行修改,但是如果它是对象类型,对象中的属性是可以修改的。类似于 const
readonly name: string
age?: number
readonly friend?: Person
constructor(name: string, friend?: Person) {
this.name = name
this.friend = friend
}
}
const p = new Person("ddg", new Person("boke"))
// 此时的 name 是 只能访问,不能修改
// p.name = "123"
console.log(p.name);
console.log(p.friend);
// 不可以直接修改 friend
// p.friend = new Person("hames")
if (p.friend) {
p.friend.age = 30
}
class Person {
private _name: string
constructor(name: string) {
this._name = name
}
// 访问器 setter / getter
// 建议:私有属性,下划线开头
set name(newName) {
this._name = newName
}
get name() {
return this._name
}
}
const p = new Person("呆呆狗")
// console.log(p.name);
p.name = "呆呆狗------"
console.log(p.name);
class Student {
static time: string = "20:00"
static attdendClass() {
console.log('去学校');
}
}
// 可以通过 类,直接进行访问
console.log(Student.time);
console.log(Student.attdendClass());
export { }
function makeArea(shape: Shape) {
return shape.getArea()
}
// 父类
abstract class Shape {
// 抽象函数,是可以没有 函数体的, 抽象函数必须在抽象类中
// 抽象类 不能被实例化
// 抽象类的方法,必须在子类中实现
abstract getArea(): number
}
class Rectangle extends Shape {
private width: number
private height: number
constructor(width: number, height: number) {
// 这里是子类,必须调用 父类的 super()
super()
this.width = width
this.height = height
}
getArea() {
return this.width * this.height
}
}
class Circle extends Shape {
private r: number
constructor(r: number) {
super()
this.r = r
}
getArea() {
return this.r * this.r * 3.14
}
}
const rectangle = new Rectangle(20, 30)
const circle = new Circle(10)
console.log(makeArea(rectangle))
console.log(makeArea(circle))
// makeArea(new Shape())
// makeArea(123)
// makeArea("123")
class Person {
name: string = "123"
eating() { }
}
const p = new Person()
// 类 本身,也是可以作为一个类型的
const p1: Person = {
name: "呆呆狗",
eating() { }
}
function printPerson(p: Person) {
console.log(p.name);
}
printPerson(new Person())
printPerson({ name: "打", eating: function () { } })
// 第一种 通过类型(type)别名来声明对象类型
// type InfoType = { name:string,age:number}
// 第二种,通过 接口 interface
// interface 类型名称
// 接口后面的 类型名称,一般前面都会加一个大写的 I
interface InfoType {
readonly name: string,
age: number
}
const info: InfoType = {
name: "ddg",
age: 12
}
// 因为加了只读属性,所以会报错
// info.name = "@22"
// interface 来定义索引类型
interface IndexLanguage {
// index 是一个形参
// [index: number]: string | boolean
[index: number]: string
}
const frontLanguage: IndexLanguage = {
0: "html",
1: "css",
2: "js",
3: "vuejs",
// 4: true
}
interface ILanguageYear {
// name 就是一个形参
[name: string]: number
}
const languageYear = {
"c": 1972,
"java": 1995,
"js": 1996,
"ts": 2014
}
interface CalcFn {
// 接口定义 函数类型
(n1: number, n2: number): number
}
// 还是推荐用 type 来定义函数类型
// type CalcFn = (n1: number, n2: number) => number
function calc(num1: number, num2: number, calcFn: CalcFn
) {
return calcFn(num1, num2)
}
const add: CalcFn = (num1, num2) => {
return num1 + num2
}
calc(20, 30, add)
吧多个接口结合在一起,有两种方式
- 接口继承
- 交叉类型
interface ISwim {
swmming: () => void
}
interface IFly {
flying: () => void
}
interface IAaction extends ISwim, IFly {
// 接口的继承,支持多继承
}
const action: IAaction = {
swmming() { },
flying() { }
}
// 一种组合类型的方式 : 联合类型
type DdgType = number | string
type Direaction = "left" | "right" | "center"
// 另一种组合类型的方式:交叉类型
// 这里的 & 表示的是:既符合 前面的类型,也得符合 后面的类型
type oneType = number & string
// &:它的意义
interface ISwim {
swmming: () => void
}
interface IFly {
flying: () => void
}
type mytype1 = ISwim | IFly
type mytype2 = ISwim & IFly
const obj: mytype1 = {
// swmming 和 flying二选一即可
swmming() {}
}
const obj2: mytype2 = {
// 两个都写才是可以
swmming() {},
flying() { }
}
export { }
interface IEat {
eating: () => void
}
interface ISwim {
swmming: () => void
}
// 类实现接口
class Animal {
}
// 类的继承:只能实现单继承
// 实现:实现接口,可以实现多个接口
class Fish extends Animal implements ISwim, IEat {
eating() {
console.log('fish eating');
}
swmming() {
console.log('fish swmming');
}
}
class Person implements ISwim {
swmming() {
console.log('person swimming');
}
}
// 编写一些公共的api : 面向接口编程
function swimAction(swimable: ISwim) {
swimable.swmming()
}
// 1.所有实现了接口的类 对应的对象,都可以传入
swimAction(new Fish())
swimAction(new Person())
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FiR6veYA-1643279385256)(coderwhy-vue3-组件化.assets/image-20220127120910909.png)]
interface IFoo {
name: string
}
interface IFoo {
age: number
}
// TS 允许定义 两个名字一样的 接口。TS内部会把这两个接口合并的
const foo: IFoo = {
age: 18,
name: "sting"
}
// type 不允许两个 名称一样的 别名, 就是别名不能重复
// type IBar = {
// name: string
// }
// type IBar = {
// age: number
// }
interface IPerson {
name: string,
age: number,
height: number
}
const info = {
name: "ddg",
age: 19,
height: 2.0,
address: "北京市"
}
// 在类型检查的时候,info 赋值给 IPerson ,两者对比,先擦除 address,然后比较如何相同,则允许赋值,不相同则不允许
// freshness 擦除
const p: IPerson = info
// 它的意义
function p2(obj: IPerson) { }
// p2({ name: "ddh", age: 20, height: 1.8, address: "北京" }) // 会报错,因为多传了一个 address 属性
const obj2 = { name: "ddh", age: 20, height: 1.8, address: "北京" }
p2(obj2)
// type Direction = "left" | "Right" | "Top" | "Bottom"
enum Direction {
LEFT,
RIGHT,
TOP,
BOTTOM
}
function turnDirection(direction: Direction) {
switch (direction) {
case Direction.LEFT:
console.log("改变角色的方向向左")
break;
case Direction.RIGHT:
console.log("改变角色的方向向右")
break;
case Direction.TOP:
console.log("改变角色的方向向上")
break;
case Direction.BOTTOM:
console.log("改变角色的方向向下")
break;
default:
// 只有把 上面的类型 穷举 完,这里才不会报错
const foo: never = direction;
break;
}
}
turnDirection(Direction.LEFT)
turnDirection(Direction.RIGHT)
turnDirection(Direction.TOP)
turnDirection(Direction.BOTTOM)
enum Direction {
// 这四个属性的默认值 都是0 ,也可以修改值
// 如果只修改第一个,第一个改成100, 那后面三个 会递增的
// 如果只修改第四个,第一个还是0,第二个还是1,第三个还是2
// 它的值,也可以是字符串,一般是字符串或者数字
LEFT = 100,
RIGHT = 200,
TOP = 300,
BOTTOM = 400
}
function turnDirection(direction: Direction) {
// console.log(direction); 枚举类型 其实是有值的
switch (direction) {
case Direction.LEFT:
console.log("改变角色的方向向左")
break;
case Direction.RIGHT:
console.log("改变角色的方向向右")
break;
case Direction.TOP:
console.log("改变角色的方向向上")
break;
case Direction.BOTTOM:
console.log("改变角色的方向向下")
break;
default:
// 只有把 上面的类型 穷举 完,这里才不会报错
const foo: never = direction;
break;
}
}
turnDirection(Direction.LEFT)
turnDirection(Direction.RIGHT)
turnDirection(Direction.TOP)
turnDirection(Direction.BOTTOM)
export { }
// 类型的参数化
// 在定义这个函数时, 我不决定这些参数的类型
// 而是让调用者以参数的形式告知,我这里的函数参数应该是什么类型
function sum(num: Type): Type {
return num
}
// 1.调用方式一: 明确的传入类型
sum(20)
sum<{ name: string }>({ name: "why" })
sum(["abc"])
// 2.调用方式二: 类型推到
sum(50)
sum("abc")
T:Type的缩写,类型
K、V:key和value的缩写,键值对
E:Element的缩写,元素
O:Object的缩写,对象
function foo(arg1: T, arg2: E) { }
foo(10, '你好啊~~~')
interface IPerson {
name: T1,
age: T2
}
// 这里必须要写对应的类型,不会自动推导
const p: IPerson = {
name: "ddg",
age: 20
}
const p2: IPerson = {
name: "ddg1",
age: 30
}
class Point {
x: T
y: T
z: T
constructor(x: T, y: T, z: T) {
this.x = x
this.y = y
this.z = y
}
}
const p1 = new Point("1.33.2", "2.22.3", "4.22.1")
const p2 = new Point("1.33.2", "2.22.3", "4.22.1")
const p3: Point = new Point("1.33.2", "2.22.3", "4.22.1")
const names1: string[] = ["abc", "cba", "nba"]
const names2: Array = ["abc", "cba", "nba"] // 不推荐(react jsx <>)
export { }
比如string和array都是有length的,或者某些对象也是会有length属性的
那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢?
interface ILength {
length: number
// 表示:如果是一个对象类型,则必须有 length 属性
}
// extends 继承某个类型,
// 对这个泛型 进行一个约束
function getLength(arg: T) {
return arg.length // 得有length 这个属性,传过来以后才不会报错
}
// getLength(123) // number类型是没有 length 属性的
getLength("abc") // 因为字符串本身就是有 length属性
getLength(["abc", "cba"])
getLength({ length: 100 })
console.log(getLength({ length: 100 }));
console.log(getLength(["abc", "cba"]));
TypeScript支持两种方式来控制我们的作用域
命名空间namespace
命名空间在TypeScript早期时,称之为内部模块,主要目的是将一个模块内部再进行作用域的划分,防止一些命名冲突的问题
export namespace time {
// 在 这个 time 花括号外,想要拿到 ,就必须要 export 导出
export function format(time: string) {
return "2022-22-02"
}
export function foo() { }
export let name: string = "呆呆狗"
}
export namespace price {
export function format(price: number) {
return "99.9999"
}
}
time.format
// time.foo
// main.ts
import { time, price } from './utils/format';
console.log(time.format('xxx'));
之前我们所有的typescript中的类型,几乎都是我们自己编写的,但是我们也有用到一些其他的类型:
const imgId = document.getElementById('image') as HTMLImageElement
大家是否会奇怪,我们的HTMLImageElement类型来自哪里呢?甚至是document为什么可以有getElementById的方法呢?
其实这里就涉及到typescript对类型的管理和查找规则了
那么typescript会在哪里查找我们的类型声明呢?
内置类型声明通常在我们安装typescript的环境中会带有的 官网地址
比如 axios 就是外部定义类型声明
node_modules => axios =>index.d.ts 。 axios已经帮我们弄好了
npm i react ,npm i @types/react --save-dev
一般在 src 下 新建一个 .d.ts 结尾的文件
// declare 就是声明的
declare module 'lodash' {
export function join(arr: any[]): void { }
}
// main.ts , 前提没有执行过 npm i @types/lodash --save-dev
import lodash from 'lodash'
console.log(lodash.join(["1"]));
声明变量
在根文件的 index.html 文件中
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js
">script>
<script>
let whyName = "coderwhy"
let whyAge = 18
let whyHeight = 1.88
function whyFoo() {
console.log("whyFoo")
}
function Person(name, age) {
this.name = name
this.age = age
}
script>
// main.ts
import { add, sub } from './utils/math';
import { time, price } from './utils/format';
// import lodash from 'lodash'
// console.log(lodash.join(["1"]));
// TS 里面,引入一个图片 也会报错
import nhltImg from './img/nhlt.jpg'
console.log(add(20, 30));
console.log(sub(20, 30));
console.log(time.format('xxx'));
console.log(whyName);
console.log(whyAge);
console.log(whyHeight);
console.log(whyFoo());
const p = new Person("ddg", 20)
console.log(p);
$.ajax({})
// ddg.d.ts
// .d.ts 文件不需要写实现,只定义即可
// declare 就是声明的
// 这是声明的模块
declare module 'lodash' {
export function join(arr: any[]): void { }
}
// 也可以声明 变量 / 函数 / 类
declare let whyName: string
declare let whyAge: number
declare let whyHeight: number
declare function whyFoo(): void
declare class Person {
name: string
age: number
constructor(name: string, age: number)
}
// 声明文件
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.png'
declare module '*.svg'
declare module '*.gif'
// 声明命名空间
// 举个例子,在 index.html 中引入了 jq 的 cdn地址
declare namespace $ {
export function ajax(settings: any): any
}