1. TS的数据类型
TS常用类型
类型 | 描述 |
---|---|
String | 字符串类型 |
Nubmer | 数字类型 |
Boolean | 布尔值类型 |
Object | 对象类型 |
Array | 数组类型 |
any | 任意类型(不推荐) |
unknown | 类型安全的any类型 |
void | 空值(undefined) |
never | 不能是任何值 |
tuple | 元组,TS新增类型,固定长度数组 |
enum | 枚举,TS新增类型 |
1.1 基础类型声明
// 1.字符串类型
let sayHi: string = '你好'
// 2.数值类型
let money: number = 0
// 3.布尔类型
let isLogin: boolean = false
// 4.symbol类型
const sym1 = Symbol("user")
const sym2 = Symbol("user")
const userList = {
[sym1]: "Admin",
[sym2]: "用户"
}
// 5.数组类型
let userNameList: string[] = ["张三", "李四", "王五"] // 写法一
let userNameList2: Array<string> = ["张三", "李四", "王五"] // 写法二
// 6. 对象类型 一般不用
let adminInfo: object = {
name: "法外狂徒",
age: "189"
}
//我们不能获取对象单个数据,也不能设置数据
// adminInfo.user = "哈哈哈" //报错
// adminInfo[name] = "张三" //报错
// 在TypeScript中,它们各自的类型也是undefined和null,也就意味着它们既是实际的值,也是自己的类型
// 7.undefined
let nnd: undefined = undefined
// 8. null
let nll: null = null
1.2 any和unknown类型
any是类型类型,默认不声明时为any类型
//可以对any类型的变量进行任何的操作,包括获取不存在的属性、方法
//any类型的变量赋值任何的值,比如数字、字符串的值
let say : any = "hi"
say = 2
say = false
unknown 是类型安全的any类型
作用场景:定义类型不确定的变量
//unknown 是类型安全的any类型
let user:unknown ;
let name:unknown ;
user= "憨八龟";
// 注意:unknown类型的变量不能直接赋值给其他变量
例如: let name = user 则直接报错
//此时我们可以使用类型断言
//类型断言,可以用来告诉解析器的变量的实际类型
name = user as String
//或者
name = <string> user
function demo(): string {
return "demo"
}
function demo2(): number {
return 23
}
const flag = true
let known: unknown
if (flag) {
known = demo()
} else {
known = demo2()
}
if (typeof known === 'string') {
console.log(known.length);
}
1.3 void类型
void类型一般表示一个空值。
常用场景:当方法没有返回值的时候
//没有返回值的时候 默认void类型 可不写
function sum (num1:number,num2:number):void{
console.log(num1)
}
1.4 never类型
never 表示永远不会发生值的类型。
常用场景:抛出异常
function fn():never{
throw new Error ("报错了")//JS报错后无法继续向下执行
}
function getType(getType: number | string) {
switch (typeof getType) {
case "string":
console.log("执行参数为string类型时的函数");
break
case "number":
console.log("执行参数为number类型时的函数");
break
default:
const check: never = getType //永远不会产生值
}
}
1.5 tuple类型
元组,元组就是固定长度的数组。元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型。
在开发中,数组建议存放相同类型的元素,不同类型的元素最好放在对象或者元组中。
const user: [string, number, number] = ["张三", 8888, 2321]
const name = user[0] //张三 类型string
元组也常用于作为返回值
来自公众号codewhy笔记
function useState<T>(state: T): [T, (newState: T) => void] {
let currentState = state
const changeState = (newState: T) => {
currentState = newState
}
return [currentState, changeState]
}
const [counter, setCounter] = useState(19)
2. TS类型补充
2.1 函数参数类型
// 在开发中,通常情况下可以不写返回值的类型(自动推导)
function sunPrice(price1: number, price2: number): number {
return price1 + price2
}
sunPrice(20, 30)
上下文中的函数: 可以不添加类型注解:
const user= ["张山","李四","王五"]
user.forEach((item)=>{
console.log(item)
})
2.2 可选类型–?:
//Z是可选类型 格式 ?:
function printPoint(point: {x: number, y: number, z?: number}) {
console.log(point.x)
console.log(point.y)
console.log(point.z)
}
printPoint({x: 123, y: 321})
2.3 联合类型 --|
当变量或参数不止类型有多种时使用:
function getUser(openId: number | string) {
if (typeof openId == "number") {
// xxxx
} else {
// xxx
}
}
getUser(20)
2.4 类型别名–type
ts通过type定义类型别名,提升代码阅读性。
//未定义类型别名时
function getCord(cord: { x: number ,y: number ,z?: number
}) {
}
//定义类型别名后
type cord = {
x: number
y: number
z?: number
}
function getCord(cord: cord) {
}
2.7 可选链操作符–!
可选链操作符是ES11中增加的特性。它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行。在TS中同样支持:
type Person = {
name: string,
price?: {
subsidy?: number,
privateAccount: number
}
}
const userInfo: Person = {
name: "你好",
// price: {
// subsidy: 200, //补贴
// privateAccount: 399,//个人账户
// }
}
//未使用可选链
console.log(userInfo.name)
console.log(userInfo.price.subsidy)//报错
//使用可选链
console.log(userInfo.name)//你好
console.log(userInfo?.price?.subsidy)//undefined
2.8 !!和??
??和!!都是es6+的特性
1.!!操作符:
将一个其他类型转换成boolean类型
//!!操作符等同于Boolean()
const message = ""
let flag = Boolean(message)
let flag1 = !!(message)
console.log(flag); //false
console.log(flag1);//false
2.??(空值合并操作符)操作符:
当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数, 否则返回左侧操作数。
??操作符和||语法有点类似
但是??操作符的返回值不是左边就是右边
而||语法可以有多个选择例如 type= demo||emo||" 未知"
||语法常用于if判断
const message="你好啊"
const result =message?? "我好啊" //如果message为false 返回右侧
2.9 字面量类型
type selectorType = "radio" | "checkbox" | "input"
// 字面量类型 selector的类型只能是"radio" | "checkbox" | "input"三种
function getSelector(selector: selectorType) {
if (selector === "input") {
console.log("选择器", selector)
} else {
}
}
getSelector("input")
2.10 类型缩小
类型缩小就是类型保护。通过判断缩小范围。
常见的类型保护有以下几种:
- typeof
- 平等缩小(、=、!==)等等
- instanceof
- in
1. typeof
在 TypeScript 中,检查返回的值typeof是一种类型保护:因为 TypeScript 对如何typeof操作不同的值进行编码
type Car = number | string
function carType(car: Car) {
if (typeof car == 'number') {
}
}
carType(213)
2.平等缩小
我们可以使用Switch或者相等的一些运算符来表达相等性(比如===, !==, ==, and != )。
type selectorType = "radio" | "checkbox" | "input"
// 字面量类型 selector的类型只能是"radio" | "checkbox" | "input"三种
function getSelector(selector: selectorType) {
if (selector === "input") {
console.log("选择器", selector)
} else {
}
}
getSelector("input")
3. instanceof
JavaScript 有一个运算符来检查一个值是否是另一个值的“实例”,也可以判断复杂类型的具体类型。
class student1 {
}
class student2 {
}
function work(p: student1 | student2) {
if (p instanceof student1) {
console.log("执行student1")
} else {
}
}
let sdt = new student1()
work(sdt)
4. in
如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。
type Fish = {
swimming: () => void
}
type Dog = {
running: () => void
}
function walk(animal: Fish | Dog) {
if ('swimming' in animal) {
animal.swimming()
} else {
animal.running()
}
}
const fish: Fish = {
swimming() {
console.log("swimming")
}
}
walk(fish)
3. 函数
3.1 函数的类型
//1.函数的返回值定义
//:number 代表该函数的返回值
function sum(x: number, y: number): number {
return x + y;
}
//2.函数表达式的类型声明
//注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>
// 定义常量时, 编写函数的类型
type AddFnType = (num1: number, num2: number) => number
const add: AddFnType = (a1: number, a2: number) => {
return a1 + a2
}
案例:
3.2 参数的可选类型
// b?:number 代表b参数是可选的
// 注意:可选类型必须在必传类型的后面
function logNbr(a: number, b?: number) {
console.log(a, b); //20 undefined
}
logNbr(20)
3.3 默认参数
在函数中我们可以给一个参数赋予默认值,当我们传递的参数值为undefined时,使用默认值。
在开发中 我们传递的参数有时候我们获取的,有可能获取的值为undefined
function logNbr(a: number, b: number = 20) {
console.log(a, b); //20 20
}
logNbr(20, undefined)
export { }
3.4 剩余参数
剩余参数 是ES6+提出的,TS也同样支持。
function sum(defalut: number, ...nbrList: number[]): number {
let total = defalut
nbrList.map(item => {
total += item
})
return total
}
console.log(sum(30, 40, 60)) //130
console.log(sum(30, 40, 50, 60)) //180
3.5 函数的this
TS中对this是有默认推导的:
const info = {
name: "hello",
say() {
//this指向info==>作用域链
console.log(this.name)
}
}
info.say()
但是当我们的this指向不明确时,TS就会报错
function say() {
//error:'this' implicitly has type 'any' because it does not have a type annotation.
console.log(this.name)
}
const info = {
name: "hello",
say
}
info.say()
//因为ts检测时不确定你会以什么形式调用say方法 ,所以检测不通过
//有可能是通过info.say()||say()..
//say()调用时 this指向没有name就报错
当我们指定this类型后:
// 指定this
function say(this: { name: string }) {
console.log(this.name)
}
const info = {
name: "hello",
say
}
info.say() //hello
//需要通过call绑定this
say.call({name:"刘德华"}) //刘德华
3.6 函数的重载
函数的重载又称为重载签名,表示函数可以使用不同的方式进行调用。
JS的话不会实现重载而是直接覆盖。
如果可以使用联合类型则优先使用联合类型。
// 案例: 做一个通用累加 如果是数字则相加 如果是字符则字符长度相加
// 两个参数类型相同
function sum(op1: number, op2: number): number
function sum(op1: string, op2: string): number
function sum(op1: any, op2: any): any {
if (typeof op1 === "string") {
return op1.length + op2.length
}
return op1 + op2
}
console.log(sum(20, 20)) //40
console.log(sum("abcd", "efg")) //7
4. class类
类的基础知识—ES6+ 类
4.1 constructor构造函数
类通过class关键字来定义一个类。
类可以有自己的构造函数constructor,当我们通过new关键字创建一个 实例时,构造函数会被调用。
构造函数不需要返回任何值,默认返回当前创建出来的实例。
class father {
// 在默认的strictPropertyInitialization模式下面我们的属性是必须初始
// 化的,如果没有初始化,那么编译时就会报错
name: string
money: number
//构造器
constructor(name: string, money: number = 200) {
this.name = name
this.money = money
}
getMoney() {
console.log("我家的钱", this.money)
}
}
const demo = new father("你好")
console.log(demo.money) //200
demo.getMoney()//我家的钱 200
4.2 类的继承
类通过关键字extends
关键字实现继承。子类中可以使用super
关键字来访问父类。
class father {
type: string
value: string
constructor(type: string, value: string) {
this.type = type;
this.value = value
}
show() {
console.log('我显示了');
return "显示"
}
}
class son extends father { //继承了father的实例成员
name: string
age: number
constructor(type: string, value: string, name: string, age: number) {
super(type, value) //调用父元素的type,value属性
this.name = name
this.age = age
}
text() {
console.log(this.type);
console.log(this.value);
console.log(this.name);
console.log(this.age);
super.show() //super调用父类方法
}
}
let demo = new son("文字", "值为0", "憨八龟", 19)
demo.text()
export { }
4.3 类的修饰符
在TypeScript中,类的属性和方法支持三种修饰符: public、private、protected。
- public: 默认值,公共属性,谁都可以访问。
- private:私有属性,只能在类内部进行访问 编程规范一般私有属性都是 _ 下划线开头
- protected:受保护的属性,只能在当前类和当前类的子类中使用和修改。
1.private
class User {
private name: string
constructor(name: string) {
this.name = name
}
}
let demo = new User("张三")
console.log(demo.name) //报错 name为私有属性
2. protected
class User {
protected name: string
constructor(name: string) {
this.name = name
}
}
class Student extends User {
constructor(name: string) {
super(name)
}
logUser() {
console.log(this.name)
}
}
let demo = new Student("张三")
demo.logUser() //张三
4.4 只读属性readonly
class User {
readonly name: string
constructor(name: string) {
this.name = name
}
}
let demo = new User("张三")
console.log(demo.name) //张三
demo.name = "李四" //报错 只读属性无法修改
4.5 setters/getters
前面一些私有属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程, 这个时候我们可以使用存取器。
class User {
private _name: string
constructor(name: string) {
this._name = name
}
set name(newName) {
this._name = newName
}
get name() {
return this._name
}
}
const demo = new User("张三")
console.log(demo.name); //张三
demo.name = "李四"
console.log(demo.name);
4.6 静态成员 static
使用static
开头的属性/方法成为静态属性(类属性),只能通过类去调用。
class User {
static admin: string = "张三"
static getAdmin() {
console.log("管理员是", this.admin)
}
}
//静态成员通过类名调用
console.log(User.admin) //张三
User.getAdmin()//管理员是张三
4.7 抽象类
抽象就是指不具体的,所以抽象类就是指不具体的类。所以抽象类自身没有什么功能,通常作为父类类使用。
抽象类是使用abstract声明的类。
抽象类是不能被实例的话(也就是不能通过new创建)。
抽象类规定了所有继承自它的非抽象子类必须实现它的所规定的功能和相关操作,否则会报错。
function CurrencyConverter(Fun: transfer) {
return Fun.converter()
}
// 抽象类规定了所有继承自它的**非抽象子类**必须实现它的所规定的功能和相关操作,否则会报错。
abstract class transfer {
abstract converter(): number
}
class usd extends transfer {
private money: number
constructor(money: number) {
super()
this.money = money
}
converter() {
return this.money * 6.3
}
}
const usdDemo = new usd(20)
console.log(CurrencyConverter(usdDemo)); //126
4.8 类的类型
类的本身也是可以作为一种数据类型。
class father {
name?: string = "张三"
constructor(name?: string) {
this.name = name
}
getMoney() {
console.log("我没钱")
}
}
function demo(f: father) {
console.log(f.name)
f.getMoney()
}
demo(new father("李四"))
demo({ name: "王五", getMoney: function () { console.log("我明明看见你有钱") } })
打印结果:
李四
我没钱
王五
我明明看见你有钱
5. 接口
接口的声明规范一般规定以I开头。
5.1 声明对象类型
接口的类型通过interface
关键字声明。注意接口中的所有属性都不能有实际的值,接口一般用于定义对象的结构。
interface IUser{
name:string
age?:number //可选
readonly money:number
readonly info:{
ss:number
}
fun:()=>void //接口中的所有方法都是抽象方法
}
let demo:IUser={
name:"张三",
money:200,
info:{
ss:200
},
fun(){
console.log("测试")
}
}
console.log(demo.name)//张三
console.log(demo.money)//200
console.log(demo.info.ss=300) //300
// console.log(demo.info={})//报错 因为是只读类型 可以修改值不能修改指向
在接口中我们可以定义对象中键值对的类型
5.2 函数类型
接口也可以用于定义函数类型,但是除了对象类型外,其他不推荐使用接口定义,最好使用type
。
interface count{
(nbr1:number,nbr2:number):number//定义函数类型
}
function getSum(n1:number,n2:number,countSum:count){
return countSum(n1,n2)
}
const add:count =(n1,n2)=>{
return n1+n2
}
console.log(getSum(20,30,add)) //50
5.3 接口的继承
接口与类有些不同,接口可以实现多继承。
interface inter1{
fun1:()=>number
}
interface inter2{
fun2:()=>void
}
interface collect extends inter1,inter2{ //多个继承用逗号隔开
}
const demo :collect={
fun1(){
return 20
},
fun2(){}
}
5.4 交叉类型
前面学习的联合类型|
等同于逻辑或,交叉类型&
等同于逻辑与。
interface color{
color:string
}
interface action{
change:()=>void
}
type colorType =color&action
const obj:colorType={
color:"red",
change(){
console.log("red")
}
}
obj.change()
5.5 接口的实现
接口的实现就是指类满足接口的所有需求。 通过implements
关键字实现接口。
interface IAdmin{
getPirce:()=>void
}
interface IUser{
getAge:()=>number
}
class role {
}
//类只能单继承 但是接口可以实现多个
class roleType extends role implements IAdmin,IUser{
getPirce(){
console.log(2000)
}
getAge(){
return 200
}
}
//案例2 公共API编写
function priceAction(price:IAdmin){
price.getPirce()
}
// 所有实现了接口的类所对应的对象, 都是可以传入
//1.直接传方法
priceAction({getPirce:function(){console.log("没钱了")}}) //没钱了
//2.传递实现了接口的类
priceAction(new roleType())//2000
5.6 接口与类型别名的不同
不同之处:
1.接口interface
允许命名重复,重复的对某个接口来定义属性和方法,ts内部会进行合并操作。
2.类型别名type
定义的是别名,别名是不能重复的
使用场景:
如果是定义非对象类型,通常推荐使用type,比如Direction、Alignment、一些Function。
5.7 字面量赋值
字面量赋值解决以下问题:(原则上字面量赋值仅仅是解决报错)
作用场景:后端有时候返回我们的数据有许多是我们封装的接口不需要的,如果直接赋值给接口会导致报错。这时候可以使用字面量赋值。
通过字面量赋值跳过错误:
interface IUser{
name:string
action:()=>void
}
const instance ={
name:"张三",
age:18,
action(){
console.log("运动")
}
}
/**
* 不报错原理:freshness擦除
* 1.首先将Instance的类型取出
* 2.擦除与IUser接口无关的声明 即age
* 3.检测instance对应的name和action类型是否相同
*/
const instance1:IUser = instance
console.log(instance1.name) //张三
console.log(instance1.age)//报错 类型“IUser”上不存在属性“age”。
函数使用:
interface IUser{
name:string
action:()=>void
}
function getUser(info:IUser){
console.log(info.action)
}
const instance ={
name:"张三",
age:18,
action(){
console.log("运动")
}
}
getUser(instance)
6. 枚举enum
枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型。
enum Money {
RMB = "RMB",
MY = "MY",
YB = "YB",
}
function moneyType(money: Money) {
switch (money) {
case Money.RMB:
console.log("人民币")
break
case Money.MY:
console.log("美元")
break
case Money.YB:
console.log("英镑")
break
default:
const foo: never = money
break
}
}
moneyType(Money.RMB) //人民币
6.1 枚举类型的值
枚举类型默认是有值的,值为索引:
例如:
enum Money {
RMB, //0
MY ,//1
YB ,//2
}
当我们赋值如果是数字后面的值会依次+1
enum Money {
RMB, //0
MY=2 ,//2
YB ,//3
}
7. 泛型
泛型的作用场景:当我们定义函数或类时,如果遇到类型不明确的就可以使用泛型,本质上来说就是参数的类型化。
参数的类型化:在定义这个函数时, 我不决定这些参数的类型,而是让调用者以参数的形式告知,我这里的函数参数应该是什么类型。(不定义为any的原因是会丢失类型信息)
function demo<T>(nbr: T): T {
return nbr
}
// 调用方式1: 明确传入类型
demo<number>(20)
demo<string>("你好")
//2.利用TS的类型推导 推出类型
demo(30)
demo("你好")
7.1 泛型的使用
//多类型传递
function demo<T, S, B>(nbr: T, str: S, bool: B) {
}
demo<number, string, boolean>(20, "你好", false)
7.2 泛型在接口的使用
interface Idemo<T = string, N = number> {
name: T
age: N
}
const demo: Idemo = {
name: "张三",
age: 12
}
7.3泛型在类中使用
class Demo<T>{
nbr: T
nbr2: T
constructor(x: T, y: T) {
this.nbr = x
this.nbr2 = y
}
fun() {
console.log(this.nbr)
}
}
const demo1 = new Demo(10, 20)
demo1.fun()
const demo2 = new Demo<number>(10, 30)
7.4 泛型约束
有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中:
比如string和array都是有length的,或者某些对象也是会有length属性的;
interface ILength {
length: number
}
function getLength<T extends ILength>(arg: T) {
return arg.length
}
getLength("abc")
getLength(["cba", "nba"])
getLength({length: 2})
8. 命名空间namespace
TS通过两种方式来声明作用域:
- 模块化:每一个文件就是一个模块 支持ES Module和CommonJS
- 命名空间:通过namespace来声明一个命名空间
命名空间也成为内部模块,是TS在ES6模块化出现前的方案。主要目的是将一个模块内部再进行作用域的划分,防止一些命名 冲突的问题。
//相同的方法名
export namespace demo1 {
export function demo(nbr: number, nbr2: number) {
return nbr + nbr2
}
}
export namespace demo2 {
export function demo(str: string, str2: number) {
return str + "的答案是:" + str2
}
}
9. TS类型查找
.d.ts文件:它是用来做类型的声明(declare)。 它仅仅用来做类型检测,告知typescript我们有哪些类型
TS通常会在三个场景中查找类型声明:
- 内置类型声明 ts包安装时自带的。比如
BOM API
,DOM API
等等。 - 外部定义的类型声明:通常引用库时,库自带的。例如
axios
。 - 自己定义类型声明。
本章主要讲自定义类型声明: