TypeScript基础(一)

216 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

TypeScript

  • TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。
  • TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。

安装

  • 安装完之后,会提供tsc命令。
npm install -g typescript
  • 执行命令,编译ts文件会在同级目录下生成对应的js文件
tsc hello.ts
  • 编译选项

--outDir 编译输出的目录 target ES版本,默认ES3 --watch 监听模式,文件发生改变自动编译

  • 每次编译都输入这么多选项很麻烦,所以可以使用tsconfig.json配置文件。
tsc --init

执行上面的命令自动在当前目录下生成文件。

修改配置文件之后,可以直接使用tsc命令进行编译了。

tsconfig.json的配置:

{
    "compilerOptions": {
        "outDir": "./dist",
        "target": "es2016"
    },
    // 解决提示的问题
    "include": [ "./src/**/*" ]
}

类型系统

  • 类型系统检测的是类型,而不是具体值。

  • 类型标注,相当于把API文档集成到代码当中。为没有ts的框架或库提供类型系统的支持,更方便的代码提示。例如:百度地图的baidumap-web-sdk

类型标注

基础类型

  • string
  • number
  • boolean
let title: string = "哈哈哈";
let age: number = 18;
let isShow: boolean = true;

null 和 undefined

  • 声明为nullundefined类型后不可再修改。
let nul: null = null;
let undef: null = undefined;

// nul = 1; // 报错: 不能将1分配给类型null
  • 默认情况下,nullundefined是所有类型的子类型,所以下面的变量修改不会报错:
let a: string = "哈哈";
a = null;
a = undefined;

tsconfig.jsonstrictNullChecks设置为true,可以检查代码中可能存在的有风险的null值。但如果有这个配置的话,上面的代码就会提示错误。

对象类型

  • 下面的代码找不到x,只能找到Object上的原型方法。因为这样声明是把obj1声明成了对象但并没有指定它的类型,所以在编译会报错,但在js中的话,也是可以找到x的。
let obj1: Object = {
  x: 1,
  y: 2
}
obj1.x; // 报错
obj1.toString();

对象字面量式类型标注

  • 正确的声明:
let obj2: {x: number, y: number} = {
  x: 1,
  y: 2
}
obj2.x;
obj2.y;

内置对象类型

let d1: Date = new Date();

包装对象

  • String Boolean Number
  • 字符串string是字符串对象 new String()的一个子类。
let str1: string;
str1 = "哈哈哈";
str1 = new String("嘿嘿嘿"); // 报错

let str2: String;
str2 = new String("哈哈哈");
str2 = "嘿嘿嘿";

数组

  • 两种声明方式:
let arr1: string[] = [];
arr1.push(100); // 错误
arr1.push("哈哈哈");
let arr2: Array<number> = [];
arr2.push(100); 
arr2.push("哈哈哈"); // 错误

元组

  • 与数组相似,但储存的数据类型多样
  • 初始化个数与标注类型必须一致
  • 后续添加的越界数据必须与标注的类型中任意一个一致,不校验顺序
let data1: [string, number] = ["哈哈", 1000];

data1.push(10);
data1.push("嘿嘿");
data1.push(true); // 错误

枚举

  • 值只能是数字或字符串
enum HTTP_CODE {
  OK = 200,
  NOT_FOUND = 404
}

HTTP_CODE.OK; // 200

其他类型

无值类型 void

function fn(): void{
  // return 1; // 错误
}
fn()

任意类型 any

  • 任何值都可以赋值给any类型
  • any类型也可以赋值给任何类型
  • any类型有任意属性和方法
  • 相当于放弃了类型检测,也放弃了IDE的类型提示
let a: any = 1;
a = [1, 2, 4];
a = "haha";
a = 12;
a = true;

tsconfig配置:"noImplicitAny": true, 出现隐式的any时会报错。

未知类型 unknown

  • 更安全的any
  • 只能赋值给unknown any
  • 没有任何属性和方法

函数类型

function foreach(data: string[], callback: (k: number, v: string) => void) {
  for (let i: number = 0; i < data.length; i++) {
    callback(i, data[i])
  }
}
let arr = ["a", "b", "c"];
foreach(arr, function (k, v) {

})

接口 interface

  • 接口只能作为类型使用,不能作为值去使用。
interface Point {
  x: number,
  y: number
}

let p: Point = {
  x: 1,
  y: 2
}

可选属性

  • ? 标识
interface Point {
  x: number,
  y: number,
  color?: string
}

只读属性

  • readonly 标识。
  • 除了初始化以外,是不能被再次赋值的,只能读取。
interface Point2 {
  readonly x: number
}
let p2: Point2 = {
  x: 1000
}
p2.x; // 可访问
// p2.x = 2000; // 错误,不可修改

任意属性

  • 表达式: [prop: 索引类型]: 索引值类型
  • [prop: string]: number表示可以向目标添加键为string类型、值为number类型的对象。
interface Point3 {
  x: 1,
  y: 2,
  [prop: string]: number
}
let p3: Point3 = {
  x: 1,
  y: 2,
  z: 3
}
p3.count = 10;
  • 添加代码 p3[1] = 20; 这样也不会出现错误,因为1最终会被转换为string类型。

注意事项

  • 索引类型只能是stringnumber之一。
  • 但是两者同时出现时,要注意下面的问题。
interface Point4 {
  [prop: string]: string,
  // [prop1: number]: number, // 错误
  [prop2: number]: string, // 正确
}
interface Point5 {
  [prop: string]: Object,
  // [prop1: number]: Date, // 正确
  [prop2: number]: number, // 正确
}
  • 如上:可以将索引的number类型理解为索引string类型的子类型(仅仅在索引这里可以这样理解),则索引number对应的值的类型必须和索引string对应的值的类型一致或者是其类型的子类型

类型深入

联合类型

  • 用符号 | 表示
  let a: number|string = 1;
  a = "哈哈"

交叉类型

  • 或者叫合并类型,用符号 & 表示。
  • 用于把多种类型合并在一起成为新的类型。
  interface o1 {
    x: 1,
    y: 2
  }
  interface o2 {
    z: 3
  }
  let obj1: o1 = {
    x: 1,
    y: 2
  }
  let obj2: o2 = {
    z: 3
  }
  let obj3: o1 & o2 = Object.assign({}, obj1, obj2);
  obj3.z;

字面量类型

  let direction: 'left' | 'right' | 'top' | 'bottom';
  // direction = "center"; // 错误
  direction = 'left'; // 正确
  direction = 'right'; // 正确
  direction = 'top'; // 正确
  direction = 'bottom'; // 正确

类型别名

  • type关键字定义
  • 对象类型可以用上面说到的interface,基本类型就可以用type
{
  type dir = 'left' | 'right' | 'top' | 'bottom';
  let direction: dir;
  // direction = "center"; // 错误
  direction = 'left'; // 正确
  direction = 'right'; // 正确
  direction = 'top'; // 正确
  direction = 'bottom'; // 正确
}

类型推导

  • 每次都显示标注类型比较麻烦,TypeScript提供了一种更加方便的特性:类型推导
  • TypeScript编译器会根据当前上下文自动的推导出对应的类型标注,这个过程发生在:
    • 初始化变量
    • 设置函数默认参数值
    • 函数返回值
  let num = 1; // 被类型推导识别为是number类型
  // num = "哈哈"; // 错误,所以不能赋值给string类型
  num = 100; // 正确

类型断言

  • 断言表达式 1 :< 类型 >
  • 断言表达式 2 :as 类型
  let img1 = <HTMLImageElement>document.querySelector("#img");
  let img2 = document.querySelector("#img") as HTMLImageElement;

  img1.src;
  img2.src;

类型断言只是一种预判,不会对数据本身产生实际的作用。类似类型转换,但不会真实的转换类型。

类型操作符

typeof

  • 操作值,抽取值的类型,后面只能接值,不能接类型。
  • typeof拿到的类型可以赋值给变量,也可以赋值给类型
  • 赋值给变量和类型,typeof抽取出来的结果是不一样的
  let colors = {
    color1: "red",
    color2: "blue",
  }
  // 赋值给变量
  let a = typeof colors; // a 是联合类型 object | string | number | ...
  // 赋值给类型
  type b = typeof colors;  // b -> { color1: string, color2: string }
  let data: b;  // data -> { color1: string, color2: string }

keyof

  • 操作类型,获取类型所对应的类型key的集合,返回是key的联合类型
  interface Person {
    name: string;
    age: number
  }
  type t1 = keyof Person; // 相当于 type a = name | age
  let data: t1;
  data = "name";
  data = "age";
  // data = "a" // 报错

例子:

  function css(ele: Element, attr: keyof CSSStyleDeclaration) {
    return getComputedStyle(ele)[attr]
  }
  let box = document.querySelector(".box");
  if (box) {
    css(box, "width");
  }

CSSStyleDeclaration接口 存在于 lib.dom.d.ts 类型声明文件中。

in

  • in 操作符对值和类型都可以使用
  // 对值的操作
  console.log("name" in { name: "tom", age: 35 }); // boolean
  // 对类型的操作
  interface Person {
    name: string,
    age: number,
    sex: boolean
  }
  type personKeys = keyof Person;
  /**
   * newPerson = {
   *  name: string,
   *  age: string,
   *  sex: string,
   * }
   */
  type newPerson = {
    [k in personKeys]: string
  }
  • 原理: 内部使用for ... in 对类型进行遍历
  • in遍历完的因为要作为key使用,所以后面只能接 number string Symbol

extends

  • 类型继承
  interface t1 {
    x: number,
    y: number,
  }
  interface t2 extends t1 {
    z: string
  }
  let a: t2 = {
    x: 1,
    y: 2,
    z: "kongcodes"
  }

类型保护

  • 下面代码,ts推断出arg的值可能是stringarray
  • 此处若使用 arg.push(); 会提示错误,因为push不是stringarray的共有方法
  • arg.length 就不会报错,因为length是共有的方法
  function toUpperCase(arg: string | string[]) {
    arg.push(); // 错误
    arg.length; // 正确
  }
  • 可以使用类型保护解决类似的问题,ts提供了下面几个方法可以进行类型保护

typeof

  • 使用了typeof后会影响后续的类型推断,不会再报错
  function toUpperCase(arg: string | string[]) {
    if (typeof arg === "string") {
      arg.toUpperCase();
    } else {
      arg.push();
    }
  }

instanceof

  • 针对对象进行保护。
  function toUpperCase(arg: string | string[]) {
    if (arg instanceof Array) {
      arg.push()
    }
  }

自定义类型保护函数

  • 上面两种情况无法满足条件时
  • 操作符 is
    // 自定义类型保护
    function canEach(data: Element | Element[] | NodeList): data is NodeList | Element[] {
      return (<NodeList>data).forEach !== undefined;
    }

    function fn(elements: Element | Element[] | NodeList) {
      if (canEach(elements)) {
        elements.forEach(() => {});
      }
    }

    // 举例调用
    let eles = document.querySelectorAll(".box");
    fn(eles);

上述代码:

  • (<NodeList>data)中的断言仅仅是为了解决提示错误的问题
  • canEach返回布尔值为真的时候,说明data is NodeList | Element[]成立