TypeScript的神奇之处 大总结

141 阅读3分钟

我始终相信:任何一种新的技术的出现是为了解决原有的技术的缺陷

JavaScript 是一门非常优秀的编程语言

他比java的(面向对象/面向过程更加的灵活) 并且在很长的时间内并不会被另外一种语言所取代

Stack Overflow的创立者之一的 Jeff Atwood 在2007年提出了著名的 Atwood定律:任何使用JavaScrit实现的应用最终都会被JavaScript实现 现在看来 这句话正在被一步一步的被应验

  • Web端一直用的都是javaScript
  • 移动端使用的ReactNative Weex Uniapp等框架实现的跨平台开发 其内部也是使用的javascript
  • 小程序端也离不开javaScript
  • 桌面端应用程序我们可以借助于Electron来开发
  • 服务器端开发可以借助于Node环境使用JavaScript来开发

但是javaScript本身也存在了很多缺点

  • ES5以及之前使用的var关键字。关于作用域的问题
  • 最初javaScript设计的数组类型 也并不是连续的内存空间
  • 直到今天javaScript 也没有假如类型检测这一机制

为什么类型检测会在代码中有很大的重要性呢?

因为我们都知道 一个错误越早发现越好

  • 能在编写代码时候被编译器发现问题 那就不要到编译期间暴露
  • 能在编译期间发现的问题 那就不要在运行期间暴露
  • 能在运行期间发现的问题 那就不要在上线了客户手里暴露出来

这个错误很大的原因就是在javascript中我们对传入的参数没有一个类型的限制 导致在运行期间才会被暴露 这就会造成了早期开发人员关于类型思维的缺失 导致我们写出来的代码 将会有不安全性

早期为了解决javaScript 类型约束上的缺陷 很多公司都推出了自己的增加类型的方案

  1. 2014年 Facebook退出了flow来对于javaScript的类型检测
  2. 同年 微软也推出了TypeScript 1.0 版本
  3. 结果显然而知。TypeScript战胜了
  4. vue2.0当时也采用的是flow的类型检测
  5. vue3.0版本全栈开始转向TypeScript了 基本上98.3% 使用了TypeScript进行了重构
  6. Angular在很早期就使用TypeScript进行了项目重构并且需要使用TypeScript来进行开发;
  7. 甚至Facebook公司一些自己的产品也在使用TypeScript;

认识TypeScript

  • GitHub说法:TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
  • TypeScript官网:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. 解释一下:TypeScript是javaScript的超集,它可以变编译成普通的 干净的 完整的javascript代码
  1. 超集: TypeScript 是一个加强版的javascript 。javascript拥有的特性TypeScript也全部拥有 并且TypeScript它是紧跟随着ECMA规范的 所以ES6-7-8.。。所有方法它也是支持的
  2. 在语言方面 不仅仅增加了类型约束 还增加了语法的扩展 还有枚举类型(Enum元组类型(Tuple)接口 (Interface) 泛型 等等
  3. 并且TypeScript最后会变异成javascript 所以不用去担心浏览器兼容的问题 并且编译的时候并不需要babel这样的工具

搭建TypeScript环境

因为TypeScript它是最终是会转换成javaScript的所以他有两种方式

  1. 通过webpack/vite 搭建一个ts环境 首先需要通过ts-loader 在webpack中配置tsloader 在需要一个tsconfig.json 来约束配置ts具体
npm install ts-loader typescript -d
在 webpack 配置 tsloader
module: {
    rules: [
        {
            test: /\.ts$/,
            loader: 'ts-loader',
        },
    ],
},
需要一个 tsconfig.json 来配置 ts
tsc --init
  1. 通过node里面一个ts-node库来进行编译ts代码 并且跑到node环境下面 npm install ts-node -g npm install tslib @types/node -g ts-node math.ts 其内部原理 也是帮你把ts代码转换成了js
正常默认情况下 所有 ts 代码和 js 代码都在同一个作用域 会有名称重复定义方法一解决 使用 export{}当成一个模块 (模块有自己的作用域)

TypeScript中类型

  1. any类型 我们无法确认一个变量的类型 并且可能会发生一些变化
// 可以给any类型进行任何任何操作和赋值
let message: any = '222';
  1. unknown类型 用于描述类型不确定的
// anyunknown
//unknown类型只能赋值给anyunknown类型  防止拿到该值 不可以在其他地方再去给新创建的变量赋值
// any类型可以赋予给任何类型

// 错误
// let message1: string = result;

3.(函数)void类型 指一个函数如果没有返回值 那么他的返回值默认是void

// 3.void
function sum(): void {}

4.(函数)never类型 表示永远不会发生值/表示函数是一个死循环或者抛出异常 应用场景:在封装核心库的时候可以使用 逻辑判断最后使用never 这样的话 就可以检查逻辑判断是否少了定义逻辑 因为never是永远不会有值的 所以在逻辑里面永远不会走到这逻辑 如果这个变量接受了三种类型 所对应的逻辑就应该会有三种

// message:string|number联合类型
function handleMessage(message: string | number | boolean) {
  switch (typeof message) {
    case 'string':
      console.log('string');
      break;

    case 'number':
      console.log('number');
      break;
    default:
      const check: never = message;
  }
}

5.(数组)tuple类型:元祖类型 多种数组元素的组合[string,number,number]

const inf1o: [string, number, number] = ['tring', 11, 22];
console.log(inf1o);
// 可以从泛型的地方把any改编成泛型 T   他们默认吧传入的类型赋值给T
function useState(state: any) {
  let currentState = state;

  const changeState = (newState: any) => {
    currentState = newState;
  };
  const tuple: [any, (newState: any) => void] = [currentState, changeState];
  return tuple;
}

const [counter, setChange] = useState(10);

setChange(110);

6.函数的参数类型和返回值

// 给函数添加类型注解() => void

type MyFunction = () => void;
const foo2: () => void = () => {};

const foo3: MyFunction = () => {};

/***
 * 6.函数的参数类型和返回值类型
 * 也可以给返回值加上类型注解
 * 在开发中一般都默认不写  因为他会类型推导的
 * */

function su1m(num: number, num2: number): number {
  return num + num2;
}

7.匿名函数的参数类型:如果函数可以在上下文环境中推导出来参数类型,这个时候可以不加该参数的类型注解

/***
 * 匿名参数的参数类型
 * 通常情况下 定一个函数的时候 都会给函数的参数加上一个类型注解
 * 如果函数可以在上下文环境中推导出来参数类型 这个时候可以不加该参数的类型注解
 * 比如item 他的上下文环境就是names1 names1里面的元素都是已经确认的类型注解 所以在itme就可以添加类型注解
 * */

const names1 = ['abc', 'cba', 'nba'];
names1.forEach(item => {});

8.函数参数的对象类型

// 8.函数参数对象类型
// { x: number; y: number,z?:number } z就是可选类型在参数后面加入一个? z?

function messtage(parms: { x: number; y: number; z?: number }) {
  console.log(parms.x);
  console.log(parms.y);
}

message({ x1: 111, y2: 222 });

9.联合类型 允许我们使用多种运算符 从现有类型中构建出新的类型

// 09 联合类型

function parintId(id: string | number) {
  // 这种判断为narrow :缩小
  if (typeof id === 'string') {
    // ts在判断里面确认了id为string
    console.log(id.toLocaleUpperCase);
  } else {
  }
}

10.可选类型和联合类型的关系 当我们一个参数是一个可选类型的时候 它其实类似表示的是这个参数是 类型|undefined 的联合类型


// 10 可选类型和联合类型的关系
// 当我们一个参数的是一个可选类型的时候 它其实类似上表示的是在这个参数是 类型|undefined 的联合类型
function foooo(message?: string) {}

function n1ame(params: string | undefined) {}

11.类型别名

11.类型别名 type用于定义类型别名
type uniontype = string | number | boolean;
function foowi(parent: uniontype) {}

12.类型断言 (as):有时候ts无法获取到具体的类型信息 这个时候就需要类型断言 相对而言把广泛的类型 具体到指定的类型

// 类型断言
// 1.
const el = document.getElementById('xsh') as HTMLImageElement;
el.src;

// 2.
class Person {}

class student extends Person {
  studding() {}
}

function foooo1(p: Person) {
  // 可以通过断言 来获取到子类的方法
  (p as student).studding();
}

const stu = new student();

foooo1(stu);

// 3; 这种方式可以转换类型 但是会造成类型混乱 不推荐
const shdag = '213';
const dhdh: number = shdag as unknown as number;

13.非空类型断言:当我们确认传入的参数肯定有值 就要使用非空类型断言 14.可选链 ?. :当对象属性不存在的话 会短路(不执行了)返回一个undefined 如果存在 会继续执行 !! 把一个其它类型的转换成boolean

// 非空类型断言   当使用可选类型参数的时候 如果使用这个变量 编译期间会进行自动检测是否使用这个变量  跳过ts编译的检查
function message031(message?: string) {
  //1. 通过编译的话 确认有值
  if (message) {
    console.log(message.length);
  }
  // 2.非空断言
  console.log(message!.length);
}
// 可选链
type Person01 = {
  name: string;
  friend?: {
    name: string;
    age?: number;
  };
};
const info1: Person01 = {
  name: '3123',
  friend: {
    name: '3123',
    age: 23,
  },
};
console.log(info1.friend?.name);

15.子面量类型 字符串也可以是类型注解 一般都是配合联合类型一起使用

子面量类型 配合联合类型
let align: 'left' | 'right' | 'center' = 'right';
type alignA = 'left' | 'right' | 'center';
let align1: alignA = 'left';

16.子面量推导 正常情况下没法把一个字符串类型传给一个子面量类型 这样就需要三种方式

  • 第一种 给创建的对象设置类型注解
  • 第二种 给传入的methd参数一个断言 确认该参数的类型
  • 第三种 给创建的对象增加一个断言 为const。代表着里面的属性为只读
type Method = 'POST' | 'GET';
function request(url: string, method: Method) {}
// 第一种情况 给创建的对象设置到类型注解
type infoREq = {
  url: string;
  method: Method;
};
const infoREq: infoREq = {
  url: '2222',
  method: 'POST',
};
request(infoREq.url, infoREq.method);
// 第二种办法 给传入的method一个断言 确认类型注解
request(infoREq.url, infoREq.method as Method);
// 第三种办法 给创建的对象增加一个断言 为const 里面属性为只读
const infoREq1 = {
  url: '2222',
  method: 'POST',
} as const;
request(infoREq1.url, infoREq1.method);

17.类型缩小 在指定的执行路径中 我们可以缩小比声明时更小范围的类型 这个过程称之为类型缩小 常见的类型保护/类型缩小有以下几种

  • typeof
  • ===/!==
  • instanceof
  • in
  • 等等等
// 1. typeof  检查返回类型 ts可以在typeof里面明确拿到该变量的类型(唯一性)
// 2.平等/ (===/!==/swich)
// 3.instanceof 来检查一个值是否另一个类的实例 (这个要创建对应的实例的)
function foowuy(params: 'string' | Date) {
  if (params instanceof Date) {
  }
}
function fooclass(p: student | Person) {
  if (p instanceof student) {
  }
}
// 4.in 指定属性是否在对象或其原型链中   (判断子面量的)
// 定义一种函数的类型注解
type Fish = {
  swimming: () => void;
};
type Dog = {
  running: () => void;
};
function walk(animal: Fish | Dog) {
  if ('swimming' in animal) {
    animal.swimming();
  }
}

18.函数类型 函数作为参数 给它类型

函数类型
// 1.函数作为参数类型
function bars(fn: () => void) {}

// 2.定义常量时候 编写函数的类型
type funs = (a1: number, b1: number) => void;
const funsct: funs = (ai: number, b1: number) => {};
  1. this的推导 在明确的对象(对象内部使用this) this是默认能推导出来的
// 这是在对象中使用this ts可以默认推导出来this的指向 可以拿到name
const info0101 = {
  name: 'xsh',
  eatch() {
    console.log(this.name);
  },
};

// 如果函数放在全局里面 他是推导不出来this指向谁

function eatch1(this: { name1: string }) {}

const infoso = {
  name1: 'xsh',
  eatch: eatch1,
};
// 这会编译报错的因为它无法推导this 有可能this还会复制操作 call apply 需要给函数一个this的类型注解 这样还可以通过call/apply调用
infoso.eatch();
infoso.eatch.call({ name1: 'shh' });

总结:因为vue2中使用的optionsAPI 在里面的this用的地方很多 但是ts里面对this的支持很不友好 ,因为现在vue3中的CompositionAPI使用的是setup函数 该函数中已经没有了this的定义了 所以ts更适合在vue3中使用

20.函数的重载(解决联合类型不能解决的问题 比如无法确定返回值的类型) 函数的名称相同 但是参数不同的几个参数类型注解 就是函数的重载


// 函数的声明
function add(n1: number, n2: number): number;
function add(n1: string, n2: string): string;

//函数的执行 不能直接调用这个  因为他只是找到声明函数 只执行函数体而已
function add(n1: any, n2: any): any {
  if (typeof n1 === 'string' && typeof n2 === 'string') {
    return n1.length + n2.length;
  } else {
    return n1 + n2;
  }
}

// 它会根据参数依次去函数声明去寻找对应的类型  然后去执行函数的执行体
add(10, 20);
add('xsd', 'jfh');

21.类的使用 类的成员修饰符

  • publice 修饰的是任意都可以访问到的 共有的属性或方法 一般都是默认的
  • private 仅仅在当前类内部才可以访问到的私有属性 通过我们方法
  • protected 修饰的仅仅是在类本身以及子类中可见的 受保护的属性和方法
  • readonly 只可以访问 不可以修改 一般情况下都在constructor 构造器中赋值 但是赋值之后不可以修改了 属性本身不可以修改 但是它是对象类型 可以修改对象中的属性值
  • static 静态属性/方法 可以不用new创造实例 直接。调用 面对对象的三大特征
  1. 封装:把共有的属性封装到一起
  2. 继承:继承是多态的前提 子类里面使用父类方法/属性
  3. 多态:父类引用(类型)指向子类对象 在某一个方法里面 相同的类型但是返回的不是一个结果 目的:写出更加通用的代码

22.抽象类 abstract 我们可能会给一个函数的参数 也就是调用者传入一个父类的类型注解 然后通过多态来实现更加灵活的调用 但是父类并不会对某些方法进行实现 所以定义在父类中的方法 加上abstract的方法就是抽象方法

  • 抽象方法必须放在抽象类中 也就是用abstract声明的类
  • 继承父类的子类 必须要有这个方法的定义以及函数体 或者该类也是一个抽象类
  • 抽象类不能被new 实例化
抽象类
function makeArea(parms: Shap1) {
  return parms.getArea();
}

abstract class Shap1 {
  abstract getArea(): number;
}

class Rectangle extends Shap1 {
  private width: number = 0;
  private height: number = 0;
  constructor(width: number, height: number) {
    super();
    this.width = width;
    this.height = height;
  }
  getArea() {
    return this.width * this.height;
  }
}

class aaa extends Shap1 {
  private width: number = 0;
  private height: number = 0;
  constructor(width: number, height: number) {
    super();
    this.width = width;
    this.height = height;
  }
  getArea() {
    return this.width + this.height;
  }
}
const circle = new Rectangle(10, 20);
const aaa111 = new aaa(10, 20);
makeArea(circle);
makeArea(aaa111);

typeScript中的接口(interface)

  1. 接口的声明
  • 可以使用类型(type)别名来声明一个对象类型
  • 也可以使用接口(interface)别名来声明
//23。 接口
type obj1Type = {
  name: string;
  age: number;
};

const inf111o: obj1Type = {
  name: '222',
  age: 29,
};

interface IIobj1Type {
  readonly name: string;
  age: number;
  friden?: {
    na1me: string;
  };
}
const infosua: IIobj1Type = {
  name: '111',
  age: 298,
  friden: {
    na1me: '222',
  },
};
  1. 索引类型 可以定义索引类型
  2. 接口定义函数类型(定义函数类型时候推荐使用type 定义对象类型的时候使用接口)
索引类型
interface IIndexLang {
  [index: string]: string;
}
const InfoLang: IIndexLang = {
  0: '11',
  2: '444',
  '222': '222',
};
// 定义函数类型
interface IcalcFn {
  (n1: number, n2: number): number;
}
function addsa(fns: IcalcFn) {}

4.接口的继承(支撑多继承)通过extends关键字

接口的继承(支持多继承)
interface aaba {
  name: string;
  eat: (n1: number) => number;
}
interface a11aba {
  nam1e: string;
  aaa?: (n21: number) => number;
}
interface functs extends a11aba, aaba {
  sno: string;
}
const infshhdj: functs = {
  name: '222',
  nam1e: '4444',
  sno: 'string',
  eat: function nam111e(n1: number): number {
    return 111;
  },
};

5.交叉类型(&) 组合一个多类型的 可以用交叉类型 也可以用接口的继承

type Mytype1 = aaba | a11aba;

type Mytype2 = aaba & a11aba;

6.可以通过class实现接口(继承只能实现单继承类 一个类可以实现多个接口)

// 类实现接口
class fish extends Shap1 implements aaba, a11aba {
  name: string = '2';
  nam1e: string = '3';
  eat(n1: number) {
    return 111;
  }
  aaa(n2: number) {
    return n2;
  }
  getArea(): number {
    return 11;
  }
}

function swim(wsimer: aaba) {}

swim(new fish());
// 编写一些公共api :面向接口编程
// 所有实现了接口都可以传入进去 只要有包括这个接口即可

总结 type和interface的区别

  • type:非对象类型的 并且别名不可以重复
  • interface:可以定义重复名的接口 是把接口属性进行合并
  1. 枚举类型 枚举其实就是将一组可能出现的值 一个一个列举出来 定义在一个类型中 枚举允许开发者定义一组命名常量 常量可以是数字或者字符串
enum Direction {
  LEFT = 100,
  RIGHT,
  TOP,
  BOTTOM,
}

function rurnDirection(direction: Direction) {
  switch (direction) {
    case Direction.LEFT:
      break;

    default:
      break;
  }
}

rurnDirection(Direction.LEFT);

typeScript 中的泛型

  1. 类型参数化(外界可以决定调用的参数用什么类型) 定义函数时 函数不决定这些参数类型 而是通过者以参数的形式告知 我这里应该使用什么类型
// 里面的Type就是 调用参数的时候决定的
function su22m<Type>(n1: Type, n2: Type): Type {
  return n1;
}
// 调用方式1 明确的传入类型
su22m<number>(20, 30);
// 调用方式2 明确的传入类型
su22m(39, 30);
  1. 泛型的多个定义
function sum0203<T1, E1>(n1: T1, n2: E1) {}
sum0203<string, number>('sjdjd', 11);

3.泛型接口 定义接口和泛型结合起来 并且可以给泛型一个默认值

// 33.泛型接口
interface Iperson<T = string, T1 = number> {
  name: T;
  age: T1;
}

const p111: Iperson<string, number> = {
  name: 'xsh',
  age: 12,
};

4.泛型类的使用

class point<T> {
  name: T;
  age: T;
  old: T;
  constructor(x: T, y: T, z: T) {
    this.age = x;
    this.name = y;
    this.old = z;
  }
}
const paps = new point<string>('111', '222', '333');
const paps1: point<string> = new point('111', '222', '333');

5.泛型类型的约束(extends) 继承一个子类型/该接口的属性条件

泛型的约束
// 使用关键字extends 继承一个接口的属性 所有调用者的参数必须有我接口定义的属性(子类型)来进行给泛型进行约束

interface Ilenght {
  length: number;
}

function getLength<T extends Ilenght>(arg: T) {}

getLength('2312');

getLength({ length: 222 });
getLength(['sss']);

6.模块化 typescript 支持两种方式来控制我们的作用域

  • 模块化 每个文件都是一个独立的模块 支持ES Module 也支持CommonJS
  • 命名空间(内部模块) 通过namespace来声明一个命名空间
文件有自己的作用域 -模块化 -命名空间namespace
// 比如在命名空间内部导出(export)才可以在外部获取到
namespace time {
  export function foo(n1: number) {}
  function bar() {}
}
// 在本文件中使用
time.foo;
// 在外部文件需要给namespace加上导出
export namespace time1 {
  export function foo(n1: number) {}
  function bar() {}
}
import { time1 } from './index';
time1.foo;

7.类型的查找 如果一个变量/一个模块/第三方库只要被类型声明过的 就可以使用 否则就会报错

ts类型的管理和查找规则 ts一共有两种文件 .ts .d.ts

  • .ts 一般这些文件都是最终输出成 .js文件的 也是我们通常写代码的地方
  • .d.ts(.declare.ts) 一般都是用来类型声明的 它仅仅用来做类型检测 告知ts我们有哪些类型

那么ts会在三种地方寻找类型的声明文件他不会转换成js代码 不需要写实现

  1. 内置的声明文件 : 它是ts在安装的时候自带的 帮助我们内置了js运行时候标准化的api声明文件 eg:Math,Date等内置类型 也包括DOM API比如 Window Document 源代码实在ts/tree/main/lib下面
  • 外部定义类型声明(手动安装第三方库) 比如axios(在他自己的库编写了.d.ts) 这个就可以用 在安装的包里面有.d.ts声明文件 所以它可以使用 比如lodash 他就是纯js写的 没有.d.ts声明文件 所以无法使用 这个时候需要去社区中寻找一个该包的是否有没有.d.ts文件
  1. 该库的GitHub地址:github.com/DefinitelyT…
  2. 该库查找声明安装方式的地址:www.typescriptlang.org/dt/search?s… .如我们安装react的类型声明: npm i @types/react --save-dev
  • 自定义声明 (declare)
// 37.自定义声明声明文件
declare module 'lodash' {
  export function join(arr: any[]);
}

//  38.声明变量/函数/类 只要声明的变量 可以在对应的实现 就可以在任何地方直接调用
declare let nameXSH: string;
declare function name213123(params: string);
declare class nshads {
  name: string;
  constructor(name: string, age: number);
}

// 声明文件  以.jpg结尾的文件都已经声明了 可以在其他地方导入这种后缀名的文件
declare module '*.jpg';

// 声明命名空间
declare namespace $ {
  export function ajxisj(set: any): any;
}

// 可以全局调用
$.ajxisj('sss');

// html文件
let nameXSH = 12;

console.log(nameXSH);