21条-总结typescript基础知识

1,290 阅读13分钟

一、TypeScript 基础语法

1、typescript定义

TypeScript是JavaScript的超集,主要提供可选的静态类型,类和接口。其中一个重要好处是使IDE能够在您键入代码时提供更丰富的环境来发现常见错误。

二、TypeScript带来了什么优势

2.1 使用ts如果代码写错了 ts可以很好的有错误提示

2.2 使用ts 编辑器可以很好的提示我们需要的代码(更友好的编辑器提示功能)

2.3 代码语义更清晰易懂

三、ts的静态类型

ts静态类型,意思是ts在声明一个变量的时候其类型就已经确定好了,也就是后面变量的属性是不能改的。

// ts文件
// 定义了一个对象的静态类型 属性x和y的类型都是number,不能赋值其他类型例如字符串,否则会报错。
interface Point {
  x: number, // number 类型
  y :number // number 类型
}
// 定义变量point的类型为Point
// 变量的类型不能修改,同时变量的属性也就确定了
const point: Point = {
  x: 3,
  y: 4
}
console.log( point.x )

四、基础类型和对象类型

ts的基础类型有 :null underfined symbol boolean void

ts的复杂类型有: 对象类型,数组,对象类类型,函数类型

1、基础数字类型

let count: number = 123

2、定义一个静态字符串类型

let username: string = 'wangyahao'

3、复杂类型 -- 对象类型

// 定义方式 声明一个变量为techer:跟上定义的对象内置静态类型= 跟上对象内的属性
const teacher: 
	{  name:string, age: number } = 
{  name:'zhangsan', age: 12}

4、复杂类型--复杂类型 --- 数组

// 上面这句话表示我定义一个数组变量 冒号后面跟上我定义的类型是组数,数组中的每一项放在
const arr: number[]  = [1,2,3]

5、函数类型 --- 返回值是number () 第二个后面是函数实现

const getTotal:() => number =()  => {
  return 123;
}

6、函数类型错误示范

// 错误示范 返回值是字符串跟上面的定义的静态类型number相冲
 const getTotal1:() => number =()  => {
   return '123';
 }

7、汇总

// ts文件
// 基础类型 null underfined symbol boolean void
// 静态基础数字类型
let count: number = 123

let username: string = 'wangyahao'
class Person  {} //定义一个Person类
// 复杂类型 -- 对象类型
// 定义方式 声明一个变量为techer:跟上定义的对象内置静态类型= 跟上对象内的属性
const teacher: {  name:string, age: number } = {  name:'zhangsan', age: 12
}
// 复杂类型 --- 数组 -- 数组中每一项都是number
const arr: number[]  = [1,2,3]
// 上面这句话表示我定义一个数组变量 冒号后面跟上我定义的类型是组数,数组中的每一项放在
// 对象类 类型
const dell: Person = new Person();

// 函数类型 --- 返回值是number () 第二个后面是函数实现
// 定义一个函数 第一个 () => number 我们可以理解为我不接受任何值,函数返回值是number类型 = 号后面我是定义的函数体
const getTotal:() => number =()  => {
  return 123;
}

// 错误示范 返回值是字符串跟上面的定义的静态类型number相冲
// const getTotal1:() => number =()  => {
//   return '123';
// }

五、类型注解和类型推断

TypeScript希望开发者写出的代码中每个变量的类型都是确定的,而想要做到这一点就需要类型注解类型推断

1、所谓类型注解,就是人为为一个变量指定类型

2、所谓类型推断就是TypeScript可以通过变量值倒推变量类型。因此在绝大部分情况下,我们是不需要去写类型注解的。 但是也有某些情况类型推断是无法推断变量类型的,例如函数的参数。

// 函数静态类型定义,函数返回值无静态类型定义 默认为类型推断
 function add(first: number, second: number) {
   return first + second
 }
 const total  = add(1,2)

// 函数返回值定义静态类型
 function add1(first: number, second: number): number {
   return first + second 
 }
 const total  = add(1,2)

六、函数相关类型

1、首先我先声明一个函数

function getTotals (fn,sn) {
  return fn + sn
}

2、声明这个函数我可以传递number和string

// 这种情况ts 就推断不出来getTotals 需要的参数是什么,以下2个表达式都成立,因此我们组要加注解
const t = getTotals(1,23)
const t1 = getTotals('1212',23)

3、这是违反ts规定的因此我们需要给函数的参数做约束

 function getTotals (fn: number,sn: number) {
   return fn + sn
 }

4、上面这个函数接受2个参数 参数类型都是number

 这种情况getTotals 函数接受2个参数 类型都是数字,因此 参数1 如果传递字符串就会报错
 const t = getTotals(1,23)
 const t1 = getTotals('1212',23)

5、因此 我们如果给函数传递string类型的数据类型 编辑器代码就会报错说我们参数类型有误

6、void 代表返回值为空 ,函数的返回值类型要放在函数名括号后面

function sayHello(): void { 
  console.log('hello')
  // 例如 有返回值
}

7、函数的返回值注解使用never ,表示这个函数永远不会执行完

function errorEmitter (): never {
  // throw new Error();
  while(true) { }

  console.log(123)
}

8、函数参数解构

静态类型定义 冒号后面跟的类型定义{ first:number, second:number} ,只要是解构 都要放在冒号后面的花括号里面

function add({ first,second } : { first:number, second:number}): number {
  return first+second
}

七、数组和元组

数组类型定义

下面这个变量的意思是我定义一个数组,数组中的每一项 可以是number 也可以是 string

const arr: (number | string) [] = [1,2,3 ,'4',5]

2、下面这条语句的意思是 我声明一个数组 数组的值是 string

const stringArr: string [] = ['a','b']

3、 下面这条语句只能存 underfined

const undefinedArr: undefined[] = [undefined]

4、 下面这条语句 定义数组中是对象类型,类型的每一项是字符串或者是数字type alias 类型别名

type User = { 
  name: string, 
  age: number 
}
const objectArr1: User[] = [{
  name:"wangyahao",
  age:2
}]

还可以这样写

class Teacher {
  name: string;
  age: number;
}
const objectArr: Teacher[] = [
  new Teacher() // 这样也ok
  ,{
  name: "wangyahao",
  age: 2
}]

5、元组tuple


const teacherInfo: [string,string,string,number] = ['zhansgan','lisi','wangwu',22]  //正确
const teacherInfo1: [string,string,string,number] = ['zhansgan','lisi','wangwu',22,'3'] 错误
// csv 
const teacherList: [ string,string,string ] [] = [
  ['1','2','3'],
  ['1','2','3'],
  ['1','2','3'],
]

八、 Interface接口

1、type可以做类型定义 interface 也可以做类型定义


// 定义一个Person
interface Person {
  name: string,  //必须要的属性
  age?: number  //在属性后面加个问号,就表示这个属性可有可无
}

还可以这样定义

interface Person {
  readonly name: string,  //readonly 表示此属性只读,不可写 setPersonName 方法 person.name 报错
  name: string,  //readonly 表示此属性只读,不可写 setPersonName 方法 person.name 报错
  age?: number, //在属性后面加个问号,就表示这个属性可有可无
  [proName: string]: any,  // 这个代表 此接口还可以传其他字段 属性的名字是字符串就行,值可以是任意值 ,这样强校验就没问题了
  say(): string;  //我定义一个say方法,传参的时候必须有返回值是字符串
}

2、接口类可以实现继承

interface Teacher extends Person {
  teach(): string
}
// 实现teacher 类继承自Person

3、接口还可以定义为函数

// 定义一个SayHi函数接口,接受一个参数word string类型,返回一个值 string
interface SayHi {
  (word: string):string;
}
// 使用
const say: SayHi = (word) => {
  return word
}

4、小案例

// 定义一个函数
const getPersonName = (person: Person): void => {
  // 尝试接受一个name属性
  console.log(person.name)
}

const setPersonName = ( person: Teacher,name: string ) => {
  person.name = name
}
const person = {
  name:'wangyahao',
  age:12,
  sex:12,  //这样传没问题
  say() {
    // say 方法必须有返回字符串的值
    return 'say'
  },
  teach() {
    return 'teach'
  }
}

// 获取name
getPersonName(person)
// getPersonName({
//   name:'wangyahao',
//   // age:12,
//   sex:12  //这样传有问题,ts会做强校验
// })

// 设置name 
setPersonName(person,'wangyahao')

// 类可以应用接口
// UserInfo 类应用 Person 接口
// class UserInfo implements Person {
//   name = 'dell'
//   say() {
//     return 'say'
//   }
// }

九、 类的定义与继承

class Person {
  protected name: string;
  public satHi() {
    console.log('HI')
  }
}

class Teacher extends Person {
  // new 实例的瞬间 construction 会被调用 需要在里面调用super
  constructor( public name:string ) {
    super()
  }

  public sayHa() {
    this.name = '年号'
  }
}

const person = new Person();
// person.name = 'dell';  //类外调用 name 会报错
person.satHi()

const te = new Teacher('老师')

十 、类中的访问类型和构造器

访问类型介绍 private , protected, public访问类型

publi允许我在类的内外被调用

private 允许我在类内调用

protected 允许在子类使用

定义一个类

class Person {
   public name: string;  //访问类型定义为全局访问
   public satHi() { //访问类型定义为全局访问
     console.log('HI')
   }
 }

使用上述类

 const person = new Person();
 person.name = 'dell';  //类外调用 name
 person.satHi()

// 以上代码都是正确的

在定义一个类,其中更改一下访问类型

class Person {
  private name11: string;  // 只允许在类的内部调用
  public satHi() {
    this.name11 = '张三' // 类内调用不会报错
  }
}

使用示例

const person = new Person();
person.name11 = 'dell';  //类外调用 name会报错
person.satHi()
// // name  属性使用了 private 定义 

十一、静态属性,Setter和Getter

这个意思是首先我们定义一个类,类型我们有一个属性name,此属性name不可被外部访问和修改,然后我们在定义个setter方法和getter方法, 如果外部想要获取name值我们就调用getter方法,设置的话就调用setter方法

class PersonSet {
  // 类中我们定义一个外部不访问的_name属性 ,我们称之为私有属性
  constructor( private _name:string ) {}
  // 如果外部想使用name属性,则需要可以调用name方法
  get name() {
    // 外部可以获取到name
    return this._name + 'wangyahao'
  }
  set name (name:string) {
    // 外部可以设置name
    const realName = name.split('')[0]
    this._name = realName
  }
}

const p = new PersonSet('zhangsan')
// p.name = 'wangyahao'
console.log(p.name)

十二、抽象类

十三、联合类型和类型保护

1、联合类型举例


// 定义两个接口
interface Bird {
  fly: boolean;
  sing:() => {}
}

interface Dog {
  fly: boolean;
  bark:() => {}
}

function trainAnial( animal:Bird | Dog ) {  //这属于联合类型
 
}

2、联合类型保护

为了避免ts在写代码的时候报错需要对类型做保护,下面提供四种方式,做联合类型保护

方式一、联合类型保护方式 一

// 声明一个函数 函数接受一个参数 animal 类型为Bird 或者 Dog 此种方式称之为联合类型
function trainAnial( animal:Bird | Dog ) {
  // 类型断言
  if(animal.fly) {
    // 如果参数animal 属于Bird 则调用sing 方法
    (animal as Bird).sing()
  }else{
    // 否则
    (animal as Dog).bark()
  }
}

方式二、联合类型保护方式二 使用in操作符

function trainAnialSecond( animal:Bird | Dog ) {
  // 类型断言
  if('sing' in animal) {
    // 如果参数animal 属于Bird 则调用sing 方法
    animal.sing()
  }else{
    // 否则
    animal.bark()
  }
}

方式三、联合类型保护三 --- 使用typeof

function add(first:string | number, second: string | number) {
    if(typeof first === 'string' || typeof second === 'string') {
      return `${first}${second}`
    }else {
      first + second
    }
}

方式四、联合类型保护 使用instanceof

tyclass NumberObj {
  count: number;
}

function addSecond(first:object | NumberObj, second: object | NumberObj) {
    if(first instanceof NumberObj && second instanceof NumberObj) {
      return first.count + second.count
    }
    return 0
}

十四、Enum 枚举类型

枚举基础 默认情况下,枚举是基于 0 的,也就是说第一个值是 0,后面的值依次递增。不要担心,当中的每一个值都可以显式指定,只要不出现重复即可,没有被显式指定的值,都会在前一个值的基础上递增。

// ts 枚举类型
enum Status {
  OFFINE = 2,  //代表枚举类型下表是从那个第几个数字位置开始
  ONLINE,
  DELETED
}


function getResult (status:number) {
  if(status === Status.OFFINE) {
    return 'offline'
  } else if (status === Status.ONLINE) {
    return 'online'
  }  else if (status === Status.DELETED) {
    return 'error'
  }
}

const result = getResult(3)
console.log("result",result)

十五、 函数泛型

主要是针对函数的泛型,

泛型指的是 泛型 generic 泛指的类型

// 定义一个泛型
// 泛型可以传多个类型
function join<T, P>(first: T, second: P) {
  return `${first}${second}`;
}

// T[]
function map<T>(params: Array<T>) {
  return params;
}

// 泛型定义的时候是不知道接受的是什么类型,只有使用的时候才知道泛型是什么类型
console.log(join<number,string>(1,'2'))   //join 函数接受2种类型,一种number,一种string
console.log(map<string>(['23']) ) // map 函数接受一种数据类型string ,如果参数定义array 则需要传入字符串数组
console.log(join(1,'2'))  //ts 底层会自动推断类型

十六、类中定义泛型以及泛型的类型

1、首先我先顶一个普通的类和其类型

class DataManager {
  constructor(private data:string[] | number []) {} 
  getItem(index:number): string | number {
    return this.data[index];
  }
}

const data = new DataManager([1])
data.getItem(0)

2、使用泛型改造上面的函数

class DataManager<T> { //接受一个泛型
  constructor(private data:T[] ) {}   //接受一个泛型数组
  getItem(index:number): T{  // 返回一个泛型
    return this.data[index];
   }
 }
const data1 = new DataManager<string>(['1'])
const data = new DataManager<number>([1])
data.getItem(0)

3、泛型中的继承


interface Item {
  name: string
}
class DataManager<T extends Item> { //接受一个泛型
  constructor(private data:T[] ) {}   //接受一个泛型数组
  getItem(index:number): string{  // 返回一个泛型T类型会报错,应该返回string
    return this.data[index].name;
  }
}

const data1 = new DataManager([{
  name:'wangyahao'
}])  //这个是正确的
const data = new DataManager<number>([1])  //这个会报错

4、案例

class DataManager<T extends number | string> { //接受一个泛型
  constructor(private data:T[] ) {}   //接受一个泛型数组
  getItem(index:number): T{  // 返回一个泛型
    return this.data[index];
  }
}

const data = new DataManager<string>(['1'])
// 如何使用泛型作为一个具体的类型注解
function hello<T> (params:T) {
  return params
}
// 下面这个函数<T> (params: T) => T  这部分是对函数的注解
// 此函数接受一个泛型 参数接受一个泛型 返回一个泛型 
// 函数体是hello
const func11: <T> (params: T) => T = hello

十七、命名空间

命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的。这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中。

在ts中 定义命名空间 ,并声明构造函数

// 定义name 命名空间
namespace Home {
  // 声明 命名空间构造函数
  class Header {
    constructor() {
      const elem = document.createElement('div');
      elem.innerText = 'this is Header';
      document.body.appendChild(elem)
    }
  }

  // 声明命名空间 page 并暴露 变量
  export class Page {
    constructor() {
      new Header()
    }
  }
}

ts编译出来的代码

// 定义name 命名空间
var Home;
(function (Home) {
    // 声明 命名空间构造函数
    var Header = /** @class */ (function () {
        function Header() {
            var elem = document.createElement('div');
            elem.innerText = 'this is Header';
            document.body.appendChild(elem);
        }
        return Header;
    }());
    // 声明命名空间 page 并暴露 变量
    var Page = /** @class */ (function () {
        function Page() {
            new Header();
        }
        return Page;
    }());
    Home.Page = Page;
})(Home || (Home = {}));

十八 、模块化

import 导入模块

export 导出模块

十九、 使用 parcel 打包

中文网 www.parceljs.cn/

官网简介:极速零配置Web应用打包工具

使用方法:

0、安装

yarn add --dev parcel@next

安装报错的时候The engine "node" is incompatible with this module
yarn config set ignore-engines true  运行这个命令即可

1、修改tsconfig.json 中的配置文件打包出口

    "outDir": "./dist",                        
    "rootDir": "./src",  

2、在index.html中引入ts入口文件

  <script src="./pages.ts"></script>

3、配置启动命令

2、在package.json 配置启动命令
  "scripts": {
    "dev": "parcel ./src/index.html"
  },

4、运行启动命令

 运行启动命令 npm run dev

二十、描述文件中的全局类型和模块描述文件

什么是描述文件

  • 帮助ts理解js文件或者js库,因为ts缺少js的概念。
  • 一般以d.ts为结尾

比如以常用的jquery为例,通过在ts中手写一个jquery的描述文件帮助理解。

需要对$进行定义:

我们需要使用 declare 关键字来定义它的类型,帮助 TypeScript 判断我们传入的参数类型

jquery.t.ts

// 模块化描述文件 $

declare module 'jquery' {
  interface JqueryInstance {
    // 这里应该返回jq对象
    html:( html: string) => JqueryInstance
  }
  
  // // 定义全局函数
   function $(params: () => void): void; 
  
   function $(param:string): JqueryInstance
  
  // 借用interface的语法,实现函数重载
  
  // interface JQuery {
  //   (readyFunc: () => void) :void;
  //   (selector:string):JqueryInstance;
  // }
  // declare var $:JQuery
  
  // 如何对对象进行类型定义,以及对类进行类型定义
   namespace $ {
    namespace fn {
      class init {}
    }
  }

  // 使用模块化命名空间需要导出 $
  export = $
}

补充:安装其他插件包的时候有时候需要安装全局描述文件此时我们可以这样安装

@type/ + 包名

二十一、泛型中keyof语法的使用

使用案例


// 泛型中keyof语法的使用2

interface Person1111 {
  name: string;
  age1: number;
  gender: string
}
class Teacher111 {
  // <> 这个语法叫泛型
  constructor(private info: Person1111) {}
  // T 泛型可以是string  number string 
  // Person1111[T] 这是返回值
  // Person1111[T] 他是返回值,是个字符串或number
  getInfo <T extends keyof Person1111> (key:T): Person1111[T]{
   return this.info[key]
  }
}

const teacher111 = new Teacher111({
  name: 'dell',
  age1: 18,
  gender: 'male'
})

const tt1 = teacher111.getInfo('ag1')
console.log('tt1',tt1)