Skip to content

TS

数据类型

1,TS 变量的声明

格式:var/let/const 标识符: 数据类型 = 赋值;

// 没有写类型,ts会自动推导出来
let a = 110;
// a = "hello"; // 不ok  不能把string类型的数据赋值给number类型

// var/let/const 变量名:数据类型 = 赋值
// java中:   int a = 110;
let b: number = 666;
b = 888;

let c: string = "hello ts";
c = 'hi ts'

console.log(a,b,c);

自动类型推导:

let a = 110;   // a的类型就已经确定了,是number类型
let b = "hi";   // b的类型就已经确定了,是string类型

a = 'ok';  // 不ok
b = 888;    // 不ok

2,number 类型

// ts和js一样,不分小数和整数,都是number
// 在java中,分小数和整数,小数是float和double,整数是int
let num = 100;  // 10进制
num = 200;
num = 300;

num = 0b110; // 2进制
num = 0o567; // 8进制
num = 0xf123; // 16进制

3,boolean 类型

// boolean类型只有两个值:true,false
let b: boolean = true;
b = false;

// 1 > 2 结果是boolean类型
let flag = 1 > 2; // flag的类型会自动推导

4,string 类型

// string要小写
let str: string = 'hi ts';
str = "hello ts";

let userName = "malu";
let userAge = 18;
let height = 1.88;

// ts中写代码,提示给js中给力的多
let info = `my name is ${userName},age is ${userAge},height is ${height}`;

console.log(info);

5,Array 类型

// names是一个数组,里面只能放string类型
// 也就是说在ts或java中,说到数组,它里面一般都是放同一种类型
// let names:string[] = ["a",1,true];  // 不ok

// 在真实开发中,一般情况下,数组中都是放同一种数据类型
// 不要放不同的类型的数据
let names: string[] = ["a", "b", "c"];
console.log(names);
names.push("d")
// names.push(666);  //不ok
console.log(names);

// 定义数组的另一种方式:需要我们讲到泛型后你才能理解
let nums: Array<number> = [1, 2, 3];
nums.push(666);
console.log(nums);

6,Object 类型

// 你感觉可能是这样写的,其实这样写不对
let obj1: object = {
  a: 1,
  b: 2,
  c: 3
}

console.log(obj1.a);  // 报红  获取数据时,报错了
obj1.d = 4;
console.log(obj1.d);  // 报红

// { x: number, y: number } 整体是一个类型
let obj2: { x: number, y: number } = {
  x: 1,
  y: 2
}
console.log(obj2.x);
obj2.y = 22;
console.log(obj2.y);

// 可以给一个类型起别名  type
type MyType = { m: string, n: number };
let obj3: MyType = {
  m: 'hi',
  n: 123
}

7,Symbol 类型

JS 中的 Symbol 可以创建一个唯一值:

// 在js中,如果键同名了,后面的会覆盖掉前面
// const person = {
//   identity: 'coding',
//   identity: 'teacher',
// }
// console.log(person);  // { identity: 'teacher' }

const person = {
    [Symbol()]: 'coding',
    [Symbol()]: "teacher"
}
console.log(person); // { [Symbol()]: 'coding', [Symbol()]: 'teacher' }

TS 中使用之:

let s1:symbol = Symbol('id')
let s2:symbol = Symbol('id')

const person = {
  [s1]: 'coding',
  [s2]: "teacher"
}
console.log(person);

8,null 和 undefined 类型

// null类型只有一个值是null
// undefined类型只有一个值是undefined
let x: null = null;
let y: undefined = undefined;

9,函数的参数类型

function sum(x:number,y:number,z:number) {
  return x + y + z;
}

let res = sum(1, 2, 3);  // res的类型会自动推导
console.log(res);  // 6

// let res2 = sum("1", "2", "3"); // 报红

10,函数的返回值类型

// 指定函数的返回值类型
function sum(x: number, y: number, z: number): number {
  // return x + y + z;

  // return "hello";// 报红
}

let res = sum(1, 2, 3);  // res的类型会自动推导
console.log(res);  // 6

// let res2 = sum("1", "2", "3"); // 报红

看一个小案例:

// type是给一个类型起别名
// 别名是TodoType
type TodoType = {
  text: string,
  done: boolean
}

function getTodos(txt:string): TodoType[] {
  let todos: TodoType[] = [];
  todos.push({ text: txt, done: false })
  return todos;
}

console.log(getTodos('学习ts'));  // [ { text: '学习ts', done: false } ]

11,匿名函数参数

let names: string[] = ["malu", "wc", "xq"];

// forEach中写的函数是匿名函数
// 匿名函数中形参的类型会根据上下文推导出它的类型
names.forEach(function (item,index,arr) {
  console.log(item,index,arr);
})

// 匿名函数的形参类型,最好不要加类型,就靠它自动推导就OK
names.forEach(function (item:string, index:number, arr:string[]) {
  console.log(item, index, arr);
})

names.forEach(item=>console.log(item))

12,对象类型

// let malu: { name: string, age: number, height: number } = { name: "ml", age: 18, height: 1.88 };

type MaluType = { name: string, age: number, height: number };
let malu: MaluType = { name: "ml", age: 18, height: 1.88 };
console.log(malu);

// ?表示属性可以有可以没有
type PointType = {
  x: number,
  y: number,
  z?: number,
}

function print(point: PointType) {
  console.log("x坐标是:", point.x);
  console.log("y坐标是:", point.y);
  console.log("z坐标是:", point.z);
}

print({ x: 1, y: 2, z: 3 })

13,可选类型

type PointType = {
  x: number,
  y: number,
  z?: number,  // 可选属性
}

function print(point: PointType) {
  console.log("x坐标是:", point.x);
  console.log("y坐标是:", point.y);
  // 由于z是可选的,使用z时,作一个判断
  if (point.z) {
    console.log("z坐标是:", point.z);
  }
}

print({ x: 1, y: 2, z: 3 })
print({ x: 1, y: 2 })

14,any 类型

一个数据如果是 any 类型,它可以赋值任何类型的数据,和 JS 就一样了,所以一般不会使用 any。当我们无法确定一个变量的类型时,并且它的类型可能会发生变化,此时也是可以使用 any 类型。

// 如果一个变量是any类型,可以赋值任何值,和JS一样。
let a: any = 1;
a = "hi";
a = true;

a = { x: 1, y: 2 }
console.log(a.x);
console.log(a.y);

// 数组names中可以放任意类型的数据,和JS一样了
let names: any[] = [1, true, "hi", undefined]
console.log(names);

15,unknown 类型

unknown 类型是 TS 中的一种特殊的数据类型,用于描述一个不确定的变量。和 any 有点类似,但是在 unknown 类型上做任何事情都是不合法的。

function foo(): string {
  return "hi ts"
}

function bar(): number {
  return 123;
}

let flag = true;
// res的数据类型是unknown类型
let res: unknown;
if (flag) {
  res = foo();
} else {
  res = bar();
}
// 类型缩小
if (typeof res === 'string') {
  console.log(res.length);
}

console.log(res);

console.log("---------------");

let xx: unknown;
xx = 123;
xx = "hi vue";
xx = { a: 1, b: 2 }
xx.c = 456;  // 报红了
console.log(xx);

16,void 类型

void 是用来指定函数返回值类型的,如果函数没有返回值,返回值类型就是 void,和 java 一样。

// void用于指定函数没有返回值
function sum(x: number, y: number): void {
  console.log(x + y);

  // return 123; // 报错
}
let res = sum(1, 2);
console.log(res);

let names: string[] = ["malu", "wc", "xq"];

// 匿名函数的形参item会自动推导
// 匿名函数的返回值类型是什么? 鼠标摸一下,发现是void
names.forEach(function (item) {
  console.log(item);
  return 123; // 没有报红  它会根据上下文推导出你到底返回值是什么类型
})

// fn是一个函数,函数也有类型
let fn: () => void = () => { }

// () => void 是一个类型  要求没有形参 没有返回值
type FnType = () => void;
// let fn2: FnType = (a)=>{}  // 报红
let fn2: FnType = () => { return 123; }  // 没报红
console.log(fn2());

17,never 类型

never 表示永远不会发生类型,如一个函数是死循环,这个函数永远不可能有返回值。或者函数中抛出了一个错误,也是永远不可能有返回值。

// 写项目中,never基本上用不到
// 在封装一些框架或工具库时,可能会用到

function fn(): never {
  throw new Error();

  // while(true){}  // 死循环
}

console.log(fn());

18,tuple 类型

在 JS 中没有元组类型,但是在 Python,Swift 等语言中有元组类型,在 JAVA 中也是没有元组类型。

// [number, string, boolean] 整体是一个元组类型
// 每一个元素的数据类型都定死了
let info1: [number, string, boolean] = [1, 'hi', true]

type MyTupleType = [number, number, number];
let info2: MyTupleType = [1, 2, 3];

// 要保存一个人的信息:wc, 18, 1.88
let person = {  // 通过对象来保存,用的最多
  name: "wc",
  age: 18,
  height: 1.88
}

// person2是一个数组
let person2: any[] = ["wc", 18, 1.88];

let person3: [string, number, number] = ["wc", 18, 1.88];
console.log(person3[0]);  // wc
console.log(person3[1]);  // 18

元组和数组的区别?

  • 数组中放相同类型的元素,不同类型的元素不建议放在数组中,当然也可以放在对象中。
  • 元素中每一个元素可以有自己特有类型,根据索引就可以获取对应的值。

元组的应用:

//  [number, (newValue: number) => void] 整体是元组类型
function useState(initialState: number): [number, (newValue: number) => void] {
  let stateVlaue = initialState;
  function setValue(newValue: number) {
    stateVlaue = newValue;
  }
  return [stateVlaue, setValue];
}

// 期望函数返回值的类型是元组
// counter 是状态   setCounter 是修改状态的方法
let [counter, setCounter] = useState(10);
setCounter(20)

19,联合类型

把两个或多个其它类型组合成一种新的类型,就是联合类型。

// number | string 联合类型
let a: number | string = 110;
a = 'hi';

if (typeof a === 'string') {
  console.log(a.length);
}

// 如果一个数据是联合类型,赋值时可以赋值给其中的一种类型
function fn(msg: number | string) {
  // 类型缩小
  if (typeof msg === 'string') {
    console.log(msg.length);
  } else {
    console.log(msg);
  }
}

fn(110)
fn('hi ts')

20,类型别名

通过 type 可以给一个类型起别名,通常是给一个对象类型起别名。

// type就是给某个类型起别名
type MyNumber = number;
let age: MyNumber = 110;

// 给一个联合头型起别名
type MsgType = number | string;

// 给一个对象类型起别名
type PointType = {
  x: number,
  y: number
}

// 后面这个别名就可以是一种类型了
function printPoint(point: PointType) {
  console.log(point.x,point.y);
}

printPoint({x:1,y:2})

21,接口声明(难)

接口和类型别名比较像,在定义对象类型时,使用类型别名实现的,使用接口也可以使用。我是这样记的:

  • type 是给某个类型起别名
  • interface 是发明一种新的类型
// 声明一个接口
// interface相当于发明了一种数据类型,也是用于约束对象的
// type是用来起别名
interface PointType {
  x: number,
  y: number,
  z?: number
}

function printPoint(point: PointType) {
  console.log(point.x, point.y);
  if (point.z) {
    console.log(point.z);
  }
}

printPoint({ x: 1, y: 2, z: 3 })

interface 和 type 的区别?

  • interface 是发明一种新的类型,只能约束对象类型,type 除了约束对象类型之外,还可以约束其它类型。说白了就是 type 的使用范围更广,接口只能约束对象类型。
  • interface 可以重复对某个接口定义属性和方法,type 不能重复。
  • interface 可以继承。interface 可以被类实现(和 java 一样)。

写代码演示他们之间的区别:

// type的使用范围更广,接口只能约束对象类型
type MyNumber = number;
type Xxx = number | string | boolean;

// -------------------------

// type 别名不能重名
// type PointType1 = {
//   x: number,
//   y: number,
// }
// type PointType1 = {
//   z?: number
// }

// -------------------------

// interface发明的类型是可以重名的
// interface只能约束对象类型
interface PointType1 {
  x: number,
  y: number,
}
interface PointType1 {
  z?: number
}

function printPoint(point: PointType1) {
  console.log(point.x, point.y);
  if (point.z) {
    console.log(point.z);
  }
}

printPoint({ x: 1, y: 2, z: 3 })

// -------------------------

// interface是可以继承的
interface IPerson {
  name: string,
  age: number
}
interface IMalu extends IPerson {
  height?: number
}
let ml: IMalu = {
  name: "ml",
  age: 18,
  height: 1.88
}

// 什么时候使用interface,什么时候使用type类型别名?
//    只约束一个对象中有什么属性时,推荐使用interface
//    如果不是约束一个对象,仅仅是给一个类型起别名,使用type

22,交叉类型

前面说过联合类型,如下:

// 'malu' 是一个字面量类型
let ml: 'malu' = 'malu';

// 'left'  就是一个类型,叫字面量类型
// 'left' | 'right' | 'top' | 'botton'  整体是一个联合类型
type MyType = 'left' | 'right' | 'top' | 'botton';

let a:MyType = 'right'

还有一种类型,叫交叉类型,交叉类型是所的条件都要满足:

interface ITeach {
  name: string,
  age: number
}

interface ICode {
  name: string,
  coding: () => void;
}

// ITeach & ICode 整体是一个交叉类型
type InfoType = ITeach & ICode;

let info: InfoType = {
  name: "ml",
  age: 18,
  coding() {
    console.log("coding...");
  }
}

23,类型断言

有时候 ts 并不能获取一个数据的具体类型,此时我们就可以使用类型断言,类似于强制类型转化。

// document.getElementById("logo")  得到这个dom元素,ts也不知道它是什么类型,那才可以断言
// HTMLImageElement 这是一种数据类型
let ele = document.getElementById("logo") as HTMLImageElement

if (ele !== null) {
  ele.src = "xxx";

  console.log(ele);
}

let age = 110;
// age 断言成 string类型
// let age2 = age as string;  // 报红

// let age3 = age as any;

24,非空类型断言

interface IPerson {
  name: string
  age: number
  friend?: {
    name: string
  }
}

let info: IPerson = {
  name: "ml",
  age: 18,
  friend: {
    name:"xq"
  }
}
// ?. 是一个新的运算符
console.log(info.friend?.name);

// 类型缩小
if (info.friend) {
  console.log(info.friend);
}

// 非空类型断言(有点危险,一定要确保有friend时再使用)
// ! 表示非空断言
console.log(info.friend!.name);

25,字面量类型

// malu就是一种数据类型
let myname: 'malu' = 'malu';
// 18也是一种数据类型,都是字面量类型
let age: 18 = 18;

// "left" | "right" | "up" | "down"  整体是一个联合类型
type Direction = "left" | "right" | "up" | "down"
const d1: Direction = "left"

type MethodType = "get" | "post"
function request(url: string, method: MethodType) { }
request("http:xxx", 'get')

let obj = { url: "xxx", method: "post" }
// obj.method 得到的是字符串类型
// request(obj.url,obj.method) // 报红
request(obj.url, obj.method as 'post') // 报红

26,类型缩小

前面说过联合类型,所谓的联合类型就是把 N 个其它类型联合成一个大的类型,如下:

// number | string 就是一个联合类型
type MyType1 = number | string;

type MyType2 = "left" | "right" | "up" | "down";

类型缩小的实现方式:

  • typeof 用的最多的
  • === !==
  • instanceof
  • in
  • ...

上代码:

type MyType1 = number | string;

// 使用typeof实现类型缩小
function fn(msg: MyType1) {
  if (typeof msg === 'string') {
    console.log(msg.length,msg.split(" "));
  } else {
    console.log(msg.toFixed(2));
  }
}
fn(110)
fn('hi ts')

// -------------------------------

type Direction = "left" | "right" | "up" | "down";

// 使用 === 实现类型缩小
function switchDirection(dir: Direction) {
  if (dir === 'left') {
    console.log("向左移动");
  } else if (dir === 'right') {
    console.log("向右移动");
  } else if (dir === 'up') {
    console.log("向上移动");
  } else if (dir === 'down') {
    console.log("向下移动");
  }
}

switchDirection("left")
switchDirection("right")
switchDirection("up")

// -------------------------------

// instanceof 用来判断是否是某个类的实例
function fn(date: string | Date) {
  if (date instanceof Date) {
    console.log(date.getFullYear());
  } else {
    console.log(date);
  }
}
fn('2024,01,01')

let d = new Date();
fn(d)

// -------------------------------

// in 实现类型缩小
// 发明了一个类型,主要是用来约束对象
interface ISwim {
  swim: () => void;
}

interface IRun {
  run: () => void;
}

// in是一个运算符,是用来判断一个对象中是否有某个属性
function fn(animal: ISwim | IRun) {
  if ("swim" in animal) {
    animal.swim();
  } else if ('run' in animal) {
    animal.run();
  }
}

let fish: ISwim = { swim() { } }
let dog: IRun = { run() { } }

fn(fish)
fn(dog)

27,函数类型

函数也是一种特殊的数据,不管是在 JS 中,还是在 TS 中,只要是数据,都有类型。所以函数也是有类型。

// 函数声明
function f(msg: string): number {
  return 666
}
console.log(f("hi ts"));

// (msg: string) => number; 整体就是函数类型
// 函数类型格式:(参数列表)=>返回值类型
type GType = (msg: string) => number;

// 函数表达式
let g: GType = (msg: string): number => {
  return 123
}

console.log(g("hi ts"));

对于函数类型,如果要求两个参数,传递了一个,不会报红,如果传递了三个,就不行,看如下代码:

// 定义函数类型
type FnType = (num1: number, num2: number) => number

function calc(fn: FnType) {
  let num1 = 1;
  let num2 = 2;
  let res = fn(num1, num2);
  return res;
}

function add(num1: number, num2: number) {
  return num1 + num2
}

function mul(num1: number, num2: number) {
  return num1 * num2
}

console.log(calc(add));
console.log(calc(mul));

function k(num1: number) {
  return num1 * 10
}

console.log(calc(k));  // ok

function q(num1: number,num2:number,num3:number) {
  return num1 + num2 + num3
}
console.log(calc(q));  // 报红

再看一次 forEach,如下:

let names = ["ml","wc","xq"];

// forEach中的匿名函数,它的形参可以是1个,也可以是2个,也可以是3个
names.forEach(function (item,index,arr) {
  console.log(item,index,arr);
})

再去说一个概念,叫调用签名。在 JS 中,一个切都是对象,函数也是对象,先写一点 JS 代码:

// fn自动推导出它是函数类型
let fn = () => {
    console.log("fn..");
}

fn(); // 函数类型去调用没有问题

// 去给函数类型加属性,就会报红
fn.a = 1;
fn.b = 2;
console.log(fn.a); // 报红
console.log(fn.b); // 报红

去定义一个类型,去约束上面的 fn 函数,fn 又是对象,又是函数,如何约束呢?

type FnType = () => void;

interface FnType2 {
  a: number
  b: number
  c: string
  // 函数调用签名  写: 不能写=>
  (num: number): number
}

// fn自动推导出它是函数类型
// let fn:FnType = () => {  // 不行  fn就是函数类型
const fn: FnType2 = (num: number): number => {
  return 123;
}

fn.a = 1;
fn.b = 2;
fn.c = 'hi'
console.log(fn.a);
console.log(fn.b);

fn(123);  // 函数类型去调用没有问题

除了函数的调用签名之外,还有一个叫构造签名,如下:

class Person { }

// let p1 = new Person(); // new一个类,得到一个对象

// 定义一个接口,约束形参
interface IFacory {
  // 构造签名
  // 要求形参,是可以new对象,并且返回类型是Person
  new(): Person
}

// 把一个类传递给工厂,工厂就可以返回对象
function factory(cls: IFacory) {
  let p1 = new cls();
  return p1
}

factory(Person)

函数参数的可选参数:

// b ?: number  表示b参数可传可不传
function fn(a: number, b?: number) {
  // 类型缩小
  if (b !== undefined) {
    console.log(a, b);
  }
}

fn(1)
fn(1, 2)

函数参数的默认值:

// c=110  表示参数的默认值  不需要指定类型
function fn(a: number, b: number, c = 110) {
  console.log(a + b + c);
}

fn(1, 2)    // 113
fn(1, 2, 3)  // 6

剩余参数:

// ...nums:number[]  剩余参数  需要写在最后  类型是数组
function fn(a: number,...nums:number[]) {
  console.log(a,nums);
}

fn(1, 2)
fn(1, 2, 3)
fn(1, 2, 3, 4)
fn(1, 2, 3, 4, 5)
fn(1, 2, 3, 4, 5, 6)

28,函数重载

定义一个函数,可以实现数字相加,也可以实现字符串拼接,你可能会写出如下的代码:

// function sum(a: number | string, b: number | string) {
//   return a + b; // 报红
// }

// 解决办法:类型缩小
function sum(a: number | string, b: number | string) {
  if (typeof a == 'number' && typeof b == 'number') {
    return a + b;
  } else if (typeof a == 'string' && typeof b == 'string') {
    return a + b;
  }

}

console.log(sum(1, 2));
console.log(sum("hi", " ts"));

除了使用上面的解决办法之外,还可以使用函数重载如下:

// 下面的写法,叫函数的重载
function sum(a: number, b: number): number;
function sum(a: string, b: string): string;
// 下面的是固定死法,不会调用
function sum(a: any, b: any): any {
  return a + b;
}

console.log(sum(1, 2));
console.log(sum("hi", " ts"));
// console.log(sum(1,'hi'));  // 报红

对于同一个需求来说,可以使用函数重载,也可以使用联合类型都可以实现如下:

// arg报红
// function getLen(arg) {
//   return arg.length
// }

// 函数重载  ok
// function getLen(arg: string): number
// function getLen(arg:any[]):number
// function getLen(arg:any) {
//   return arg.length
// }

// 使用联合类型也是OK    联合类型是最简单的
// function getLen(arg: string | any[]) {
//   return arg.length
// }

// { length: number } 整体是一个类型
// 对象类型也可以解决
function getLen(arg: { length: number }) {
  return arg.length
}

console.log(getLen(["a", "b", "c"]));
console.log(getLen("hello ts"));
console.log(getLen({ length: 110 }));

29,This

在 vue2 中会大量使用到 this,在 vue3 中很少使用 this 了。在 ts 中对 this 的处理,也需要研究一下,看如下代码:

// 默认this是any类型,需要手动指定this的类型
// function fn() {
//   console.log(this);  // 报红了
// }

// fn();

// ----------------------------

// 需要明确this的类型,在函数的第1个参数位置指定this的类型
function fn(this: {name:string},a:number) {
  console.log(this);  // 报红了
  console.log(a);
}

fn.call({name:"wc"},110)

再看下面的代码:

let obj = {
  name: "wc",
  // (this: {})   手动明确this的类型
  sayHello(this: {}) {
    // 这里的this会根据上下文推导出,是obj
    console.log(this);
  }
}
// obj.sayHello();
obj.sayHello.call({})

对于 this,ts 提供了一些内置工具,如下:

  • ThisParameterType
  • OmitThisParameter
  • ThisType

然后说一下 ThisParameterType 这个工具的使用:

// this: { name: string } 手动指定this的类型
function fn(this: { name: string }, info: { name: string }) {
  console.log(this, info);
}

fn.call({ name: 'ml' }, { name: "ok" })

// ThisParameterType 可以获取到函数类型中this的类型
type FnThisType = ThisParameterType<typeof fn>

let a: FnThisType = { name: "xx" };

如果 this 没有类型,如下:

// this没有手动指定类型
function fn( info: { name: string }) {
  console.log(this, info); // this报红了
}

fn.call({ name: 'ml' }, { name: "ok" })

// ThisParameterType工具获取this对应的类型,得到unknow
type FnThisType = ThisParameterType<typeof fn>

然后说一下 OmitThisParameter 这个工具的使用:

1700018548095

最后说一下 ThisType 工具的使用:

interface IState {
  name: string,
  age: number
}
interface IStore {
  state: IState,
  fn: () => void,
  gn: () => void
}
// ThisType  用于绑定一个上下文中的this
let store: IStore & ThisType<IState> = {
  state: {
    name: "ml",
    age: 18
  },
  fn() {
    console.log(this.name);
  },
  gn() {
    console.log(this.age);
  }
}

store.fn.call(store.state)
store.gn.call(store.state)

OOP

1,类的定义

定义一个类,通过 new 这个类,就可以得到一个对象,和 JS 中是一样的,如下:

// 封装:把数据和操作数据的方法封装到一个类中
class Animal {
  // 数据
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  run() {
    console.log(this.name + " run...");
  }
  jump() {
    console.log(this.name + " jump...");
  }
}

let a1 = new Animal('wc', 1);
console.log(a1.name);
console.log(a1.age);
a1.run();
a1.jump();

2,类的继承

一个子类,可以继承一个父类,如下:

// 父类
class Animal {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  run() {
    console.log(this.name + " run...");
  }
}

// 子类  子可以继承父中的数据和方法
class Cat extends Animal{

}

let cat = new Cat('xq', 10);
console.log(cat.name); // xq
console.log(cat.age);  // 10
cat.run();  // xq run...

子可以有自己的数据和方法,如下:

// 父类
class Animal {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  run() {
    console.log(this.name + " run...");
  }
}

// 子类  子可以继承父中的数据和方法
class Cat extends Animal {
  weight: number  // weight是子独有的数据
  constructor(name: string, age: number, weight: number) {
    super(name, age); // 表示调用父中的constructor
    this.weight = weight;
  }
  eat() {
    console.log(this.name + " eat....");
  }
}

let cat = new Cat('xq', 10, 5);
console.log(cat.name); // xq
console.log(cat.age);  // 10
console.log(cat.weight);
cat.run();  // xq run...
cat.eat();

3,成员的修饰符

类中的数据和方法,都叫类的成员,可以通过三个修饰符来修饰成员:

  • public 公开的,在任何地方都可以访问成员,默认创建的都是 public
  • private 私有的,只能在类中访问成员
  • protected 受保护,只能在类中或子类中访问成员

public 演示:

class Animal {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  run() {
    console.log(this.name + " run...");
  }
}

let a = new Animal("wc", 10);
// 在类外是可以直接访问成员的,因为默认这些成员是公开的
console.log(a.name);
console.log(a.age);
a.run();

private 演示:

class Animal {
  // name和age就是私有的,只能在类中访问,不能在类外访问
  private name: string
  private age: number
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  run() {
    //this.name 表示在类中访问
    console.log(this.name + " run...");
  }
}

let a = new Animal("wc", 10);
console.log(a.name); // 报红
console.log(a.age);  // 报红
a.run();  // run是公开的

protected 演示:

// 父类
class Animal {
  protected name: string  // protected修饰成员,表示这个成员是受保护的,只能在本类中 或 子类中访问
  protected age: number
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  run() {
    console.log(this.name + " run...");
  }
}

class Cat extends Animal {
  weight: number
  constructor(name: string, age: number, weight: number) {
    super(name, age);
    this.weight = weight;
  }
  eat() {
    // this.name 为什么能访问? 因为name是protected的,在子类中可以访问的
    console.log(this.name + " eat....");
  }
}

let cat = new Cat('xq', 10, 5);
console.log(cat.name); // 报红了
console.log(cat.age);  // 报红了
console.log(cat.weight);
cat.run();  // xq run...
cat.eat();

4,只读属性

通过 readonly 表示一个数据是只读的。

class A{
  // readonly 表示只读
  readonly name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

let a = new A('wc', 10);
console.log(a.name);  // 读
console.log(a.age);  // 读

a.name = "ml";  // 写  修改  报红
console.log(a.name);

5,getter 和 setter

类中可以有私有成员,私有成员类外是不能访问的,可以提供 getter 和 setter 来进行读写,如下:

class A {
  // 数据私有了,类外不能读写
  // 私有数据,建议以_打头
  private _name: string
  private _age: number

  constructor(name: string, age: number) {
    this._name = name;
    this._age = age;
  }

  // a.name 就会走getter  访问器
  get name() {
    return this._name
  }
  set name(newVal: string) {
    this._name = newVal;
  }
}

let a = new A('wc', 10);
console.log(a.name);  // 读    报红
a.name = "xq";
console.log(a.name);

使用 getter 和 setter 好处是,可以驿数据访问进行拦截操作,如下:

class A {
  private _name: string
  private _age: number

  constructor(name: string, age: number) {
    this._name = name;
    this._age = age;
  }
  get age() {
    return this._age
  }
  set age(newVal: number) {
    if (newVal >= 0 && newVal < 150) {
      this._age = newVal;
    }
  }
}

let a = new A('wc', 10);
console.log(a.age);  // 10

a.age = 200
console.log(a.age);  // 10

6,参数属性

说白了,就是对成员数据可以省略,如下:

// class A {
//   name: string
//   age: number
//   constructor(name: string, age: number) {
//     this.name = name;
//     this.age = age;
//   }
// }

// let a = new A('wc', 10);
// console.log(a.name);
// console.log(a.age);

// ---------  上面的代码是可以简化的

class A {
  constructor(public name: string, public age: number) { }
}

let a = new A('wc', 10);
console.log(a.name);  // wc
console.log(a.age);  // 10

7,抽象类

使用 abstract 定义的类就是抽象类,在抽象类中可以声明抽象方法,所谓的抽象方法就是没有函数体的方法。为什么要有抽象类,抽象类可以定义出一套规则,如下面的三个类,都有所谓计算面积的方法,可以把计算面积的规则放到抽象类,让其它子类去继承这个抽象类。

class Rectangle {
  width: number
  height: number
  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }
  getArea() {
    return this.width * this.height;
  }
}
class Circular {
  radius: number
  constructor(radius: number) {
    this.radius = radius;
  }
  getArea() {
    return this.radius ** 2 * Math.PI
  }
}
class Triangle {
  getArea() {
    return 100;
  }
}

定义一个抽象类,让面的三个类去继承抽象类,如下:

// 就是抽象类  规则
abstract class Shape {
  // 在抽象类中可以定义抽象方法
  // 抽象方法就是为了让子类去实现
  abstract getArea(): number // 抽象方法必须出现在抽象类中, 类前面也需要加abstract
}

// let s1 = new Shape(); // 抽象类是不能new,只能让子类去继承

class Rectangle extends Shape {
  width: number
  height: number
  constructor(width: number, height: number) {
    super();
    this.width = width;
    this.height = height;
  }
  getArea() {
    return this.width * this.height;
  }
}
class Circular extends Shape {
  radius: number
  constructor(radius: number) {
    super();
    this.radius = radius;
  }
  getArea() {
    return this.radius ** 2 * Math.PI
  }
}
class Triangle extends Shape {
  getArea() {
    return 100;
  }
}

let r1 = new Rectangle(2, 2)
console.log(r1.getArea());

let c1 = new Circular(2);
console.log(c1.getArea());

let t1 = new Triangle();
console.log(t1.getArea());

// shape: Shape 父类引用  它的值是子类对象
// 在java中,叫多态    js天生就是多态
function calcArea(shape: Shape) {
  return shape.getArea();
}
console.log(calcArea(r1));
console.log(calcArea(c1));
console.log(calcArea(t1));

鸭子类型:

// 鸭子类型:如果一只鸟, 走起来像鸭子, 游起来像鸭子, 看起来像鸭子, 那么你可以认为它就是一只鸭子
//   只关心数据(属性)和方法(行为),不关心你到底是什么样的类型
class Person {
  constructor(public name: string, public age: number) { }
  running() { }
}

class Dog {
  constructor(public name: string, public age: number) { }
  running() { }
}

function printPerson(p: Person) {
  console.log(p.name, p.age)
}

let p1 = new Person("wc", 19);
printPerson(p1)

printPerson(new Person("xq", 18))

printPerson(new Dog("xx", 20))

printPerson({ name: "yy", age: 20, running() { } })

let p2: Person = new Dog("mm", 10);

8,对象类型

之前都说过了,可以使用接口或类型别名约束一个对象:

interface IPerson{
  name: string,
  age?: number,
  readonly score:number
}

let p1: IPerson = {
  name: "wc",
  age: 18,
  score:88
}
console.log(p1.name);
console.log(p1.age);
console.log(p1.score);
p1.name = "xq";
console.log(p1.name);
// p1.score = 88; // 报红

// ------------------------------

type TPerson = {
  name: string,
  age:number
}

let p2: TPerson = {
  name: "xq",
  age:18
}

// ------------------------------

class Person{
  constructor(public name: string, public age: number) { }
}

let p3:Person = new Person("xx",11)

// ------------------------------

function printPerson(p: Person) { }
printPerson(p3)

9,接口继承与实现

一个接口是可以继承另一个接口的,如下:

interface IPerson {
  name: string
  age: number
}

interface IXxx extends IPerson {
  score: number
}

let p1: IXxx = {
  name: "wc",
  age: 19,
  score: 88
}

console.log(p1);

一个类可以实现多个接口,此时接口也是一套规则,如下:

// 一个接口中也可以定义函数,函数不能有函数体
interface IAaa {
  name: string,
  eat: () => void  // 更加抽象的方法
}

interface IBbb {
  age: number,
  run: () => void  // 更加抽象的方法
}

// 一个类可以实现多个接口,此时接口就是规则
class Person implements IAaa, IBbb {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  eat() { console.log("eat..."); }
  run() { console.log("run..."); }
}

let p1 = new Person("wc", 18);
console.log(p1.name);
console.log(p1.age);
p1.run();
p1.eat();

10,一个特殊现象

把一个字面量对象赋值给一个变量时,第 1 次检测严格一些,后面再赋值就会松一些。

interface IPerson {
  name: string
  age: number
}

// 第一次把一个字面量对象,赋值给p1,会进行检测
// let p1: IPerson = {
//   name: "wc",
//   age: 18,
//   score:88  // 报红
// }
// console.log(p1);

// 第1次赋值给了xx
let xx = {
  name: "wc",
  age: 18,
  score: 88  // 不报红了
};

// 第2次才赋值给了p1,检测就比较松了
let p1: IPerson = xx
console.log(p1);

11,枚举类型

枚举就是把一组可能出现的值,一个个列出来,定义在一个类型中,这个类型就是枚举类型。

enum Direction{
  LEFT,   // 0
  RIGHT    // 1
}

let a: Direction = Direction.LEFT;
console.log(a);  // 0

let b: Direction = Direction.RIGHT;
console.log(b);   // 1

enum Aaa{
  TOP = 100,
  LEFT,
  BOTTOM,
  RIGHT
}
let x: Aaa = Aaa.BOTTOM;
console.log(x);  // 102

泛型

1,泛型基本使用

所谓的泛型就是把类型参数化,看如下代码:

// function fn(arg: number | string | {name:string}) {
function fn<T>(arg:T):T {
  // T不指一个具体的类型,只能在调用函数时才能确定,这个T就是泛型
  return arg
}

// 在调用函数时,可以把实参赋值给形参。
// 我们也可以把一个类型传递给函数,说白了类型也可以传递
let res1 = fn<number>(123);
let res2 = fn<string>('hi ts');
let res3 = fn<{ name: string }>({ name: "wc" });

// 类型参数化   不传递类型,ts会自动推导
let res4 = fn(123);
let res5 = fn('hi ts');
let res6 = fn({ name: "wc" });

泛型可以有多个:

// 一般情况下,泛型会使用如下字母表示:
//   T: Type  类型
//   K, V    key  value  键值对
//   E:  Element 元素
//   O:  Object 对象
function fn<T, E>(arg: T, arg1: E): void {
  console.log(arg, arg1);
}

let res1 = fn<number, string>(123, 'hi ts');
let res2 = fn(123, 'hi ts');

2,泛型接口与泛型类

泛型接口:

// 使用接口,就是给T赋值
// T的类型不确定,传递了什么类型,它就是什么类型
interface IPerson<T> {
  name: T,
  age: T,
  score: T
}

let p1: IPerson<string> = {
  name: "xx",
  age: "11",
  score: "99"
}

let p2: IPerson = {  // 报红了
  name: "xx",
  age: "11",
  score: "99"
}

泛型类:

class Point<T>{
  x: T
  y: T

  constructor(x: T, y: T) {
    this.x = x;
    this.y = y;
  }
}

let p1 = new Point<number>(1, 2);
console.log(p1.x);
console.log(p1.y);

let p2 = new Point("1", "2");
console.log(p2.x);
console.log(p2.y);

3,泛型的约束

可以对泛型做一些约束,如下:

interface ILength {
  length: number
}

function getLength(arg: ILength) {
  return arg.length
}

let length1 = getLength("aaaa")
let length2 = getLength(["a","b","c"])
let length3 = getLength({length:100})

// 泛型约束  <T extends ILength>
function getInfo<T extends ILength>(arg:T):T {
  return arg;
}
let info1 = getInfo<string>("aaaa")
let info2 = getInfo(["a","b","c"])
let info3 = getInfo({length:100})
console.log(info1);
console.log(info2);
console.log(info3);

getInfo(123) // 报红
getInfo({})  // 报红

keyof 的使用:

interface IPerson {
  name: string,
  age:number
}
// keyof IPerson;  得到  name | age
type Xxx =  keyof IPerson;
let a:Xxx = 'age'

// O object K key
// ,K extends keyof O  也是泛型约束
// K = 'name' | 'age' | 'height'
function fn<O,K extends keyof O>(obj:O,key:K) {
   return obj[key]
}
let info = {
  name: "wc",
  age: 18,
  height:1.88
}

console.log(fn(info, "name"));
console.log(fn(info, "age"));
console.log(fn(info, "height"));
console.log(fn(info, "score")); // 报红了

类型声明文件

1,ts 中的两种文件类型

ts 中的类型文件有两种:

  • .ts 文件 里面业务代码 在 ts 文件中,你写的代码是 ts 代码,数据都是有类型的。ts 代码浏览器不能识别,需要通过一个工具转化成 js,最终运行的 js 代码。在 ts 文件中,也可以声明类型。
  • .d.ts 文件 类型声明文件,不会生成 js,仅仅是用于提供类型信息的。

在 ts 文件中就可以写 ts 代码:

1700097647804

在 d.ts 文件中,只能声明一些类型,如下:

1700097717508

2,类型声明文件分三种

类型主要分三种:

  • 内置类型声明文件
    • 可以通过 npm i typescript -g 这样就可以把类型声明文件安装到你电脑上了
    • 通过脚手架创建的项目,内置的类型声明文件就安装到项目中了
  • 外部定义的类型声明文件
  • 自定义的类型声明文件

3,内置的类型声明

TS 内置了 JS 运行时一些标准化 API 的声明文件 ,如:Date, Math, String, Function....。在创建 vue3 项目时,如果选择了 ts,它会把这些 ts 的类型声明全部下载下来。

内置类型声明:

1700098018796

在创建项目时,脚手架就把这些类型声明好了:

1700037472504

我们在写 ts 代码时,它的类型声明查询顺序是这样的:

  • 先在人家内置的类型声明文件中找
  • 再去外部定义的类型声明文件中找(第三方的)
  • 最后去自定义的类型声明文件中找

4,第三方库对应的类型声明文件

写项目时,需要用到很多的第三方模块,有的第三方模块内置好了类型声明文件,有的是没有。可以通过 npm 官网查看:

1700098235867

1700098274673

目前几乎所有常用的第三方模块都有相应的类型声明文件,只不过有的类型声明文件内置到了对应的模块中,如:axios。有的需要自己去下载,如:react。类型声明文件命名通常是这样:@types/*,如下:

1700098496144

在下载一些第三方库时,一些库自带类型声明文件,如下:axios,如下:

1700038043202

看一个 axios 这个包:

1700038099084

使用 axios,如下:

1700098691291

有些第三方包没有类型声明文件,需要你自己去下载,如:react,lodash

1700098768643

1700098806705

先去下载 lodash,如下:

1700098839850

在 main.ts 中,引入,如下:

1700098945138

安装类型声明文件,如下:

1700098988497

查看之,如下:

1700099035236

使用之,如下:

1700099066202

5,自己创建的类型声明文件

在项目中,如果多个 ts 文件中都用到了同一个类型,如果你都在 ts 中声明类型,就有很多的重复代码,那么此时,就可以把类型声明,声明到了.d.ts 中,实现共享。如下:

1700099407562

在 ts 项目中,可以使用 js 文件,在导入 js 文件时,ts 会自动加载与.js 同名的.d.ts 文件,以提供类型声明,可以使用 declare 关键字为已存在的变量声明类型。对于 type,interface 等这些明确就是 ts 类型,可以省略 declare 关键字。

1700101239776

创建 utils.d.ts 文件,如下:

1700101437618

在 main.ts 中,引入 js 文件,如下:

1700101494544

利用 declare 也可以声明一些模块,如下 :

1700101594435

在.d.ts 中声明之,如下:

1700101670995

Vue3 中使用 TS

1,ref 和 ts

直接上代码 App.vue:

<script setup lang="ts">
import { ref } from "vue";
const count = ref<number>(0);

type ToDoItem = {
  id: number;
  name: string;
  done: boolean;
};
const list = ref<ToDoItem[]>([]);

setTimeout(() => {
  list.value = [
    { id: 100, name: "吃饭", done: false },
    { id: 101, name: "睡觉", done: false },
  ];
}, 1000);
</script>

<template>
  <div>
    <h1>{{ count }}</h1>
    <ul>
      <li v-for="item in list" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

2,reactive 和 ts

直接上代码 App.vue:

<script setup lang="ts">
import { reactive } from "vue";

// 自动推导
// const book = reactive({
//   title: "学习vue3+ts",
// });
// book.year = 2021 // 报红了

// 手动指定book类型
type Book = {
  title: string;
  year?: number;
};
const book: Book = reactive({
  title: "学习vue3+ts",
});
book.year = 2024;
</script>

<template>
  <div>
    <h1>{{ book.title }} -- {{ book.year }}</h1>
  </div>
</template>

3,计算属性和 ts

直接上代码 App.vue:

<script setup lang="ts">
import { ref, computed } from "vue";

const count = ref(200);

// 类型推导,根据计算的结果推断类型(这种使用较多)
// const doubleCount = computed(() => (count.value * 2).toFixed(2));

// 指定计算属性的类型
const doubleCount = computed<string>(() => (count.value * 2).toFixed(2));
</script>

<template>
  <div>
    <h1>
      {{ count }} --
      {{ doubleCount }}
    </h1>
  </div>
</template>

4,props 和 ts

子组件:

<script setup lang="ts">
// vue3基本props写法
// defineProps({
//   money: {
//     type: Number,
//     required: true,
//   },
// });

// vue3和ts的写法
// defineProps<{
//   money: number;
//   car?: string;
// }>();

// 这种写法比较笨拙,默认值写法
withDefaults(
  defineProps<{
    money: number;
    car?: string;
  }>(),
  {
    money: 666,
    car: "bc",
  }
);
</script>

<template>
  <div>Child组件</div>
  <p>{{ money }}</p>
  <p>{{ car }}</p>
</template>

父组件 App.vue:

<script setup lang="ts">
import { ref, computed } from "vue";
import Child from "./components/Child.vue";

const money = ref(1000);
const car = ref("bm");
</script>

<template>
  <div>
    <Child :money="money" :car="car"></Child>
    <button @click="money++">改money</button>
    <hr />
    <Child></Child>
  </div>
</template>

5,自定义事件和 ts

子组件:

<script setup lang="ts">
defineProps<{
  money: number;
  car?: string;
}>();

// 基本写法  和  ts没有关系
// const emit = defineEmits(["changeMoney", "changeCar"]);

const emit = defineEmits<{
  (e: "changeMoney", money: number): void;
  (e: "changeCar", car: string): void;
}>();
</script>

<template>
  <div>Child组件</div>
  {{ money }}
  <button @click="emit('changeMoney', 800)">改money</button>
  <hr />
  {{ car }}
  <button @click="emit('changeCar', '黄包车')">改car</button>
</template>

<style scoped>
</style>

父组件:

<script setup lang="ts">
import { ref, computed } from "vue";
import Child from "./components/Child.vue";

const money = ref(1000);
const car = ref("bm");
</script>

<template>
  <div>
    <Child
      :money="money"
      :car="car"
      @change-money="money = $event"
      @change-car="car = $event"
    ></Child>
    <hr />
    <button @click="money++">改money</button>
  </div>
</template>

<style scoped>
</style>

6,事件处理和 ts

<script setup lang="ts">
// 不处理类型
// const handleChange = (e) => {
//   console.log(e.target.value);
// }

// 处理类型
const handleChange = (e: Event) => {
  // as 叫类型断言
  console.log((e.target as HTMLInputElement).value);
};
</script>

<template>
  <div>
    <input type="text" @change="handleChange" value="hi vue3" />
  </div>
</template>

<style scoped>
</style>

7,常见的工具类型

typescript
type Required<T> = {
  [P in keyof T]-?: T[P];
};

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Exclude<T, U> = T extends U ? never : T;

type Extract<T, U> = T extends U ? T : never;

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

type Partial<T> = {
  [P in keyof T]?: T[P];
};

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

// is、keyof、in、infer

8,其它

看文档:https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props

最后更新于:

基于 MIT 许可发布