TypeScript 基础小记

本文引用《vue3+typescript组件开发 typescript vue3.0》,仅以作自己工作查阅使用

基础类型

字符串类型 string

// 普通字符串
let name = ref<string>("jeasu");

// 带变量的字符串
let msg = ref<string>(`my name is ${name.value}`);

// 拼接的字符串
let sentence = ref<string>("Hello, my name is " + name + ".\n\n" +  "I'll be " + (age + 1) + " years old next month.");

布尔类型 boolean

// 布尔类型 
let isAdult = ref<boolean>(false);

数字类型 number

// 数值类型
let age = ref<number>(17);

任意值类型 any

如果是一个普通类型,在赋值过程中改变类型是不被允许的,任意值(Any)用来表示允许赋值为任意类型。
let a1 = ref<string>('seven'); 
a1 = 7; // error 

// 但如果是 any 类型,则允许被赋值为任意类型。 
let sim = ref<any>("seven");
sim = 7;
console.log(sim, "任意类型"); // 7

// 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型 
let a3; 
a3 = 'seven'; 
a3 = 7; 
// 相当于 
let a3:any; 
a3 = 'seven'; 
a3 = 7; 

// any 定义多元数组
let arr = reactive<any[]>([1, true, "hellow"]);
arr[1] = 100;
console.log(arr, "any定义多元数组");   // [1, 100, 'hellow']

枚举类型 enum

//枚举类型
enum Color {red,blue,green} 

// 取索引
let c11 = ref<Color>(Color.blue); // 1
// 取内容
let c21 = ref<string>(Color[2]); // green 

// 枚举默认值,默认值可以为小数
enum Color2 {red = 1.5,blue = 10,green = 20}
let c111 = ref<Color2>(Color2.blue); // 10
let c222 = ref<string>(Color2[10]); // blue 根据默认值取对应内容
// 由此可见:
enum Color {red,blue,green} // => {red=0, blue=1, green=2}

// 若是只有一个添加默认值如下
enum Color3 {red,blue = 5,green}
let c333 = ref<Color3>(Color3.red); // 0 未加默认值,默认从 0 开始
let c444 = ref<Color3>(Color3.blue); // 5  // 默认值 5
let c555 = ref<Color3>(Color3.green); // 6  // 从默认值开始 +1
let c666 = ref<string>(Color3[6]); // green
// 取内容,根据默认值
let c555: string = Color3[6]; // green 

// 任意值,任意值后面不能加别的类型(未赋予默认值)
 enum Color4 {
    red,
    blue = 5,
    green = <any>"abccc",
    // green = <any>4,  // 4
    // green = <any>true, // true
    // yellow, // undefined
    yellow = "BALUE".length,
 }
 let c777 = ref<Color4>(Color4.blue); // 5
 let c888 = ref<Color4>(Color4.green); // abccc
 let c999 = ref<Color4>(Color4.yellow); // 5
 let c100 = ref<string>(Color4[<any>"abccc"]); // green

数组类型 array

// 第一种方式:元素类型后面加上[ ]
let list1 = ref<number[]>([1, 2, 3]);
let list2 = ref<string[]>(["1", "2", "a"]);
let list3 = ref<any[]>([1, true, 'abc', {id:2}])

// 第二种方式是使用数组泛型,Array<元素类型>:
let hobbies = ref<Array<string>>(["历史", "地理", "生物"]);
let list4 = ref<Array<number | string>>(['dasahk', 10])

联合类型

一个变量定义可能的多种类型

// 联合类型
let collection1 = ref<number | string | boolean>(6);
let collection2 = ref<number | string | boolean>("good");
let collection3 = ref<number | string | boolean>(true);

// 联合类型数组
let collection4 = ref<(number | string | boolean)[]>(["hasd", true, 36]);

// 联合类型的注意点:对联合类型的数据的操作
// const fn = (str: string | number): number => {
//   return str.length;  // 红波浪线报错,原因是 number 没有 length 属性
// };
// fn("hello");

元组类型 Tuple

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同,且数组各项类型与定义一一对应。元组的长度和类型不可变:

let arr1 = ref<[number, string]>([8, "haode"]);
// let arr1 = ref<[number, string]>(["haode", 10]); // ERROR

Void 类型

它表示没有任何类型

// 声明一个 void 类型的变量没什么大用,只能赋予 undefined 和 null 两种值
let unusable: void = undefined;

// 但是我们通常在定义函数的时候用它,当一个函数没有返回值时,你通常会见到其返回值类型是 void
const warnUser =(x:number,y:number): void=> {     
    console.log("This is my warning message"); 
    if (x > y) {
        console.log("x>y");
    } else {
        console.log("x<y");
    }
}

Null 和 Undefined

默认情况下 Null 和 Undefined 是所有类型的子类型。 就是说你可以把 Null 和 Undefined 赋值给 number 类型的变量

// undefind 类型
let u: undefined = undefined; 

// null 类型
let n: null = null;

Never 类型

表示永远不会存在值的类型,常用来定义抛出异常或根本就不会有返回值的函数
never 类型是任何类型的子类型,也可以赋值给任何类型;没有类型是 never 的子类型或者可以赋值给类型(除了 never 本身)
never 和 void 之间的区别是void 意味着至少要返回一个 undefined 或者 null ,而 never 意味着不会正常执行到 函数的终点

// 返回 never 的函数必须存在无法达到的终点
 const error = (message: string): never => {
    throw new Error(message);
 };

const infiniteLoop = (): never=> {    
    while (true) {     } 
}
// 推断的返回值类型为 never
const fail = ()=> {     
    return error("Something failed"); 
}

Object 类型

object 表示非原始类型,也就是除 number,string,boolean,symbol,null 或者 undefined 之外的类型,使用 object 类型,就可以更好的表示想 Object.create 这样的 API

const create = (o: object | null): void => {};

create({ prop: 0 }); // OK
create(null); // OK

// create(42); // Error
// create("string"); // Error
// create(false); // Error
// create(undefined); // Error

类型断言

类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用

// 类型断言有两种形式。 其一是“尖括号”语法:
let someValue: any = "this is a string";  
let strLength: number = (<string>someValue).length;

// 另一个为 as 语法:
let someValue: any = "this is a string";  
let strLength: number = (someValue as string).length;

类型别名 type

type abc = string | number[];
type n = number;

let n1: abc;
n1 = "4";
n1 = [1];

const fn1 = (str: abc): n => {
    return str.length;
};
fn1("a");

对象类型(引入概念:接口 Interfaces)

在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象。接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述
注意:顺序可以乱,但是定义的对象要受到接口的约束

// 定义对象属性
interface defineObj {
    readonly name: string; // 只读属性
    age: number; // 必填
    sex?: string; // 选填
    call(): string; // 有返回值,类型为 string
    action(): void; // 必填、无返回值
    [propName: string]: any; //任意属性
    // 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性
}
const obj = reactive<defineObj>({
    name: "abc", // 只读,不可修改
    age: 18, // sex: '男',
    a: "a",
    b: 9,
    c: true,
    call: () => "call",
    action: function () {
        console.log("void 接口");
    },
    o: { id: 1 },
});
console.log(obj.call()); // call
obj.action(); // void 接口
return {
    obj,
};

函数类型

函数类型的定义有两点:参数类型、返回值类型

// 有返回值
const f1 = (x: number, y: number): number => {
  return x + y;
};
console.log(f1(1, 3)); // 4

// 无返回值
const f2 = (n1: number, n2: number): void => {
  if (n1 > n2) {
    // 处理逻辑
    console.log(n1 + ">" + n2);
  } else {
    // 处理逻辑
    console.log(n1 + "<" + n2);
  }
};
f2(9, 7); //9>7

// 有返回值,比较数据
const f3 = (n1: number, n2: number): boolean => {
  if (n1 > n2) {
    return true;
  } else {
    return false;
  }
};
console.log(f3(1, 2)); // false

// 组合数据
const f4 = (n1: string, n2: string): string[] => {
  return [n1, n2];
};
console.log(f4("a", "b")); // ['a','b']

// 数据判断校验
const f5 = (s1: string, s2: string): boolean => {
  let i = s1.search(s2);
  return i > -1;
};
console.log(f5("abc", "a")); // true

// 函数默认值
const f6 = (x: number = 1, y: number = 2): number => {
  return x + y;
};
console.log(f6()); //3

// 可选参数,y 可以传,也可以不传,可选参数后面是不允许出现必填参数,只能放在最后
const f7 = (x: number = 1, y?: number, z?: number): number => {
  //   return x + y; // 报错,y 为可选参数,不能加减或拼接
  return x + (y! ?? 0) + (z! ?? 0);
};
console.log(f7());  // 1

接口 Interfaces

接口在函数中的运用

当传入的参数是对象类型的时候:

// 不用接口定义时
const fn = (obj: { id: number; name: string }): string => {
  return obj.name;
};
const oh = reactive({ id: 3, name: "jeasu" });
console.log(fn(oh)); // jeasu

// 仅对函数入参做约束
interface params {
  id: number;
  name: string;
  age: number;
}
const fn1 = (o: params): number => {
  return o.age;
};
const oh1 = reactive({ id: 3, name: "jeasu", age: 18 });
console.log(fn1(oh1)); // 18

// 对整个函数的类型检查,建议对返回值类型也要定义
iinterface SearchFun {
  (a: string, b: string): boolean;
}
const fn2: SearchFun = (s1, s2) => {
  let i = s1.search(s2);
  return i !== -1;
  // return s1;
};
console.log(fn2("dsdahjk", "jk")); // true

接口继承接口

// 二维坐标接口
interface TwoDPoint{
    x: number,
    y: number
}
// 三维坐标中的 z 坐标接口
interface ThreeDPoint{
    z: number
}

// 四维坐标接口继承了二维坐标接口的 x,y 坐标和三维接口的z坐标
interface FourDPoint extends ThreeDPoint, TwoDPoint{
    // 内还定义了四维坐标独有的时间坐标
    time: Date
}
// 实例化四维坐标接口
const poi2 = reactive<FourDPoint>({
  z: 100,
  x: 200,
  y: 300,
  time: new Date(),
});
console.log(poi2, "poi2"); // Proxy 对象{{z: 100,x: 200,y: 300,time: Mon Oct 11 2021 15:29:15 GMT+0800 (中国标准时间)}}

泛型 Generics

补充理解:《TS中的泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
写法是 <变量类型>
泛型的产生条件:定义相同结构的函数时,如下

// 如果根据业务需要传入不同类型的数据,就需要一个类型一个类型的写
// 这样容易造成代码冗余
function fnn(x:number,y:number):number[]{
    return [x,y]
}

function fnn(x:string,y:string):string[]{
    return [x,y]
}

function fnn(x:boolean,y:boolean):boolean[]{
    return [x,y]
}

因此我们可以将类型用一个变量替代,在使用的时候,将变量类型传过去

泛型变量

泛型变量 T ,T 表示任何类型,也可以用其他字母代替
因此我们上面的代码可以写成

// 函数声明方式
function declaration<T>(x: T, y: T): T[] {
    return [x, y];
}

// 函数表达式方式
const expression = <T>(n1: T, n2: T): T[] => {
    return [n1, n2];
};
// 单类型
console.log(declaration<string>("1", "2")); // ['1', '2']
console.log(expression<boolean>(true, false)); // [true, false]
console.log(expression<number>(6, 7)); // [6, 7]

// 联合类型
 console.log(expression<number | string>(6, "a")); // [6, "a"]

// 当我们不给传类型的时候,类型推断编译器会自动的帮我们判断传入的是什么类型,此时传入的数据只能为单一类型
console.log(expression(1, 23)); // [1, 23]

// 泛型的约束
// 错误示范 求变量的长度
// let variable1 = <T>(str: T): number => {
//   return str.length; // T是变量,未指定类型,未必会有 length 这个属性
// };
// 修改:
// 给参数限制类型
let limit1 = <T>(str: string | T[]): number => {
    return str.length;
};
console.log(limit1<number>([1, 3])); // 2

// 或者给泛型变量添加约束 extends
let limit2 = <T extends String>(arr: T): number => {
   return arr.length;
};
console.log(limit2<string>("one")); // 3

// 泛型的接口约束:就是让这个变量必须有 length 属性,此时就限定了变量类型有 length 属性,只能为 string ,或者数组类型
interface ILengthNum {
  length: number;
}
const limit3 = <T extends ILengthNum>(str: T): number => {
  return str.length;
};
console.log(limit3<string>("oneworld")); // 8
console.log(limit3<string[]>(["dasjd", "dhksah", "dahskdha"])); // 3
console.log(limit3<number[]>([12, 456, 79, 465])); // 4

// 多个类型参数
const multi = <N, S>(sum: [N, S]): [S, N] => {
  return [sum[1], sum[0]]; // 实现的是交换数组内两项的位置
};
console.log(multi<number, string>([1, "one"])); // ["one", 1]

泛型接口

// 泛型接口
interface genface1 {
   <T>(a: T, b: T): boolean;
}
const func1: genface1 = (x, y) => {
   return x == y;
};
console.log(func1<number>(1111, 5)); // false
console.log(func1<string>("abc", "abc")); // true

// 另一种 把泛型参数提前放在接口名上
interface genface2<T> {
  (a: T, b: T): boolean;
}
const func2: genface2<number> = (x, y) => {
  return x == y;
};
console.log(func2(7, 5)); // false

// 另一种写法,先定义类型,再赋值函数
let func3: genface2<string>;
func3 = (x, y) => {
  return x == y;
};
console.log(func3("abc", "abc")); // true

// 多类型泛型接口
interface createA3<N, T> {
  (a: N, b: T): Array<T>;
}
let func4: createA3<number, string>;
func4 = function (i, s) {
  let arr: string[] = [];
  arr[i] = s;
  return arr;
};
func4(1, "dqwy");

// 泛型约束
interface Length4 {
  length: number;
}
interface createA4<N, T extends Length4> {
  (a: N, b: T): string;
}
let func5: createA4<number, string>;
func5 = function (i, s) {
  // var arr:string[] = []
  // return arr
  return s;
};
func5(2, "dqwy");

用泛型变量和 any 的区别

// 使用泛型变量 T
function fun6<T>(x: T, y: T): T[] {
  return [x, y];
}
fun6<string>("a", "b");

// 使用 any:缺点就是传入的类型和返回的类型不确定
function fun7(x: any, y: any): any[] {
  return [x, y];
}
fun7("a", "b");

Class类

ES5:构造函数方式

传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件,但对于熟悉使用面向对象方式的程序员来讲就有些棘手,因为他们用的是基于类的继承并且对象是由类构建出来的

function Cat(name,color){
    this.name = name;
    this.color = color;
}
Cat.prototype.type = '动物';
Cat.prototype.eat = function(){
    console.log('吃');
}
var c1 = new Cat('tom', 'blue')

ES6:Class 类定义

从ECMAScript 2015,也就是ECMAScript 6开始,JavaScript程序员将能够使用基于类的面向对象的方式

class Cat1 {
  name: string;
  color: string; // 属性
  constructor(name: string, color: string) {
    // 构造函数
    this.name = name;
    this.color = color;
  }
  eat() {
    // 方法
    console.log("吃");
  }
}
const cat1 = new Cat1("tom", "blue");
console.log(cat1.name); // tom
console.log(cat1.color); // blue
cat1.eat(); // 吃

类的继承

class Animal {
  type: string;
  constructor(type: string) {
    this.type = type;
  }
  eat() {
    return "吃";
  }
  say() {
    return "叫";
  }
}
class Dog extends Animal {
  name: string;
  age: number; // 属性
  constructor(type: string, name: string, age: number) {
    // 构造函数
    // Animal.prototype.constructor.call(this)
    super(type); // 继承父类的属性和方法
    this.name = name;
    this.age = age;
  }
  action() {
    console.log("hsgdajsd");
  }
}
// 实例化
let d = new Dog("犬", "tom", 2);
console.log(d.type); // 犬
console.log(d.name); // tom
console.log(d.age); // 2
// d.eat();
// d.say();
d.action(); // hsgdajsd

修饰符

static 静态方法

不需要通过实例化处理,可以直接通过类来调用

class Obj {
  static str: string = "abc";
  // constructor可以不写,因为上面已经给变量赋值
  // constructor(str){
  //    this.str = str;
  // };
  static action() {
    console.log("static 静态方法");
  }
}
// 不需要实例化,直接调用Obj
console.log(Obj.str); // abc
Obj.action(); // static 静态方法

public 修饰公共属性或方法

public 修饰的属性或方法都是共有的,可以在任何地方被访问到,默认所有属性和方法都是public。用下面的方式来重写 Cat2

class Cat2 {
  public name: string;
  public color: string; // 属性
  public constructor(name: string, color: string) {
    // 构造函数
    this.name = name;
    this.color = color;
  }
  public action() {
    console.log("public 修饰公共属性或方法");
  }
}
// 实例化对象再取值、调用
let cat2 = new Cat2("John", "white");
console.log(cat2.name); // John
console.log(cat2.color); // white
cat2.action(); // public 修饰公共属性或方法

private 修饰私有属性或方法

修饰的属性或方法是私有的,不能在声明它的类外部访问。我们将上例中的 action 方法修饰符改为 private

class Cat3 {
  public name: string;
  private color: string; // 属性
  public constructor(name: string, color: string) {
    // 构造函数
    this.name = name;
    this.color = color;
  }
  private action() {
    console.log("private  修饰私有属性或方法");
    console.log(this.name); // jack ,在内部能访问到该类中的属性
  }
}
// 实例化对象再取值、调用
let cat3 = new Cat3("jack", "black");
console.log(cat3.name); //jack
// 下面能打印值,但是有红波浪线报错
// console.log(cat3.color); // 报错,说明访问不到 private 修饰的属性
// cat3.action(); // 报错,说明访问不到 private 修饰的方法

protected 修饰受保护的属性或方法

修饰的属性或方法是受保护的,它和private类似,区别是它在子类中也是允许被访问的。我们将上例中的 action 方法修饰符改为 protected

class Cat4 {
  protected name: string;
  protected color: string; // 属性
  public constructor(name: string, color: string) {
    // 构造函数
    this.name = name;
    this.color = color;
  }
  protected action() {
    console.log("protected 修饰受保护的属性或方法");
  }
}
// 实例化对象再取值、调用
let cat4 = new Cat4("tom", "yellow");
// 能打印出来,但是页面书写红波浪报错
// console.log(cat4.name); // 说明访问不到 protected 修饰的变量
// console.log(cat4.color); // 说明访问不到 protected 修饰的变量
// cat4.action(); // 说明访问不到 private 修饰的方法

// 创建一个子类继承Cat3
class littleCat extends Cat4 {
  constructor(name: string, color: string) {
    super(name, color);
    this.name = name;
    this.color = color;
  }
  say() {
    // return this.name // tom 可以访问父类里面 protected 修饰的属性
    console.log(this.name);
    console.log(this.color);
    return this.action();
  }
}
const dog2 = new littleCat("tom", "hua");
dog2.say(); // tom  // hua  // protected 修饰受保护的属性或方法

类实现接口 implements实现

接口和类的区别

接口,对属性和方法类型的约束,抽象的约束

interface ObjType{
    name:string;
    action():string;
}

类,对属性和方法的定义,具体的内容

class Obj5{
    name:string;
    constructor(name){
        this.name = name
    }
    action(){console.log('执行')}
}

回顾之前的接口定义对象:

interface ObjType{
    name:string;
    action():string;
}
let o6:ObjType = { // 强制指定类型,添加或减少属性或方法会报错
    name:'abc',
    action(){return '123'}
}

类 实现 接口 (implements 实现)

interface ClassType {
 name: string;
  action(): void;
}
class Obj implements ClassType {
  // 类Obj受到接口ClassType的约束
  // 类中的属性和方法要受到接口的约束,但是添加属性或方法不会报错
  // 属性和方法只能比接口里定义的多,不能少
  name: string;
  age: number; // 新增属性,接口里未定义
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  action() {
    console.log("执行");
  }
  a() {
    return "sah";
  } // 新增方法,接口未定义
}
const obj1 = new Obj("hahaha", 18);
console.log(obj1.name); // hahaha
console.log(obj1.age); // 18
console.log(obj1.a()); // sah
obj1.action(); // 执行

不同类实现同一个接口

不同类之间有共同的特性,我们把这个特性提出来,就可以定义成一个接口,然后不同类中再去用 implements 去实现这个接口:

// 警报功能
interface Alam {
  name: string;
  ring(): void;
}
// 公共类 门
class Door implements Alam {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  ring() {
    console.log("我是门这个类");
  }
}
const door = new Door("door");
door.ring(); // 我是门这个类
// 门的子类防盗门 拥有报警功能
class SecurityDoor extends Door implements Alam {
  // 里面不写任何东西,完全继承父类
  // 若添加,则属性和方法是这个子类自己的
  price: number;
  constructor(name: string, price: number) {
    super(name);
    this.name = name;
    this.price = price;
  }
  ring() {
    console.log("我是安全门,门的一个字类");
  }
}
const securityDoor = new SecurityDoor("door", 2889);
console.log(securityDoor.price); // 2889
securityDoor.ring(); // 我是安全门,门的一个字类
// 车 这个类 拥有报警功能
class Car implements Alam {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  ring() {
    console.log("我是车这个类");
  }
}
const car = new Car("door");
car.ring(); // 我是车这个类

一个类实现多个接口

// 警报功能
interface Alam1 {
  ring(): void;
}
// 开灯关灯
interface Light {
  lightOn(): boolean;
  lightOff(): boolean;
}
// 车实现这两个功能
class Car1 implements Alam1, Light {
  // 用逗号链接接口名
  ring() {
    console.log("车的报警功能");
  }
  lightOn() {
    return true;
  }
  lightOff() {
    return false;
  }
  a() {} // 只能多不能少
}
const car1 = new Car1();
car1.ring(); // 车的报警功能
console.log(car1.lightOn()); // true
console.log(car1.lightOff()); // false

接口继承类

// 定义了一个 Bird 类
class Control {
 private state: any; // state 是 Control 的私有属性,只能在 Control 中访问该变量
  constructor(state: any) {
    this.state = state;
  }
  print() {
    console.log("这是 Control 类的打印操作");
    console.log(this.state, "打印 Control 的私有属性 state");
  }
}
const control = new Control(false);
control.print();

interface SelectableControl extends Control {
  select(): void;
}

class Button extends Control implements SelectableControl {
  // state: any;
  name: string;
  constructor(name: string, state: any) {
    super(state);
    this.name = name;
  }
  select() {
    console.log("这是 Button 类的操作");
    // console.log(this.state); // Control 中 state 修饰符改为 protected 这个才对
  }
}
const button = new Button("button", true);
console.log(button); // button 中有 state,但是无法访问 state
// console.log(button.state); //Control 中 state 修饰符改为 public 这个才对
button.select();

class TextBox extends Control {
  constructor(state: any) {
    super(state);
  }
  select() {
    console.log("这是 textbox 的 select");
  }
}
const textbox = new TextBox("true");
textbox.select(); // 这是 textbox 的 select

泛型在类中的运用

class A2<T> {
  n: T;
  constructor(num: T) {
    this.n = num;
  }
  add(x: T): T {
    return x;
  }
}
var a2 = new A2<number>(1);
console.log(a2.n); // 1
console.log(a2.add(3)); // 3

One thought on “TypeScript 基础小记

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注