Typescript基础入门-学习笔记

103 阅读10分钟

快速入门

Typescript简介

  1. TypeScript是JavaScript的超集。

  2. 它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。

  3. TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。

  4. TS完全兼容JS,换言之,任何的JS代码都可以直接当成TS使用。

  5. 相较于JS而言,TS拥有了静态类型,更加严格的语法,更强大的功能;TS可以在代码执行前就完成代码的检查,减小了运行时异常的出现的几率;TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS。

开发环境搭建

  1. 安装Node.js

  2. 使用npm全局安装typescript

    npm i -g typescript

  3. 创建一个ts文件

  4. 使用tsc对ts文件进行编译

    执行命令:tsc xxx.ts

基本类型

  • 常见类型

    类型 | 例子 | 描述 | | ------- | ---------------- | ---------------- | | number | 1, -33, 2.5 | 任意数字 | | string | 'hi', "hi", hi | 任意字符串 | | boolean | true、false | 布尔值true或false | | object | {name:'小明'} | 任意的JS对象 | | array | [1,2,3] | 任意JS数组 | | tuple | [4,5] | 元素,TS新增类型,固定长度数组 | | enum | enum{A, B} | 枚举,TS中新增类型 | 字面量 | 其本身 | 限制变量的值就是该字面量的值 | | any | * | 任意类型 | | unknown | * | 类型安全的any | | void | 空值(undefined) | 没有值(或undefined) | | never | 没有值 | 不能是任何值 |

  • 类型声明

// 变量类型声明
// 声明一个变量a,同时指定它的类型为number
let a: number;
// a 的类型设置为了number,在以后的使用过程中a的值只能是数字
a = 10;

// 声明完变量直接进行赋值
let b: boolean = false;

// 如果变量的声明和赋值是同时进行的,TS可以自动对变量进行类型检测
let c = false;
c = true;

// 函数类型声明
function sum(a: number, b: number): number{
    return a + b;
}
let result = sum(123, 456);

类型声明的作用:变量和函数(参数和返回值)指定类型后,TS编译器会自动检查变量赋值和函数调用时的参数和返回值是否符合类型声明,符合则赋值和调用,否则报错,类型检查可以让程序在编译阶段就发现很多错误。

  • 自动类型推断
    • TS拥有自动的类型判断机制
    • 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
    • 所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明
  • 新增的几种类型
    • 字面量

      // 指定变量的取值范围
      let color: 'red' | 'blue' | 'black';
      color = 'blue'
      let num: 1 | 2 | 3 | 4 | 5;
      
    • any

      // 变量可以是任意类型,告诉编译器不做类型检查
      let d: any = 4;
      d = 'hello';
      d = true;
      
    • unknown

      // 不知道变量的类型,可以是任何类型
      // unknown 实际上就是一个类型安全的any
      let notSure: unknown = 4;
      notSure = 'hello';
      
      // 与any的区别
      let a: any = 123;
      a = 'hello'
      
      let b: unknown = 123;
      b = 'hello'
      
      let s:string;
      s = a // a的类型是any,它可以赋值给任意变量
      s = b // 报错:Type 'unknown' is not assignable to type 'string'.
      
    • void

      // void 用来表示空,以函数为例,就表示没有返回值的函数
      function fn(): void {
      }
      console.log('===', fn())
      // 输出:=== undefined
      
    • never

      // never 表示永远不会返回结果
      function fn2(): never {
          throw new Error('报错了!');
      }
      console.log('***', fn2())
      // 输出:Uncaught Error: 报错了!
      
    • tuple

      // 元组就是固定长度的数组
      let x: [string, number];
      x = ["hello", 10];
      
    • enum

      // 用于定义一些常量,没有指定对应关系时默认从0开始
      enum Week {
          Monday = 1,
          Tuesday = 2,
          Thriday = 3
      }
      let today: Week = Week.Thriday
      console.log(today)
      // 输出:3
      
    • 类型断言

       // 有时候变量的类型对于我们来说是很明确,但是TS编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型
       let b: unknown = 'hello';
       let s:string;
       // 断言有两种形式
       s = b as string;
       s = <string>b;
      

配置文件

  • 自动编译文件
// TS编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译
tsc xxx.ts -w
  • 自动编译整个项目

在项目根目录下创建一个ts的配置文件 tsconfig.json,添加配置文件后,只需只需 tsc 命令即可完成对整个项目的编译。配置文件还可以通过编译选项控制ts的编译行为。

// tsconfig.json是ts编译器的配置文件,ts编译器可以根据它的信息来对代码进行编译
{
  // 用来指定哪些ts文件需要被编译
  "include": [ "./src/**/*" ],
  // 不需要被编译的文件目录,默认值:["node_modules", "bower_components", "jspm_packages"]
  "exclude": [ "./src/hello/**/*" ]

   // compilerOptions 编译器的选项
  "compilerOptions": {
    // target 用来指定ts被编译为的ES的版本
    // 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'esnext'
    "target": "es2015",
    // module 指定要使用的模块化的规范
    // 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'esnext'
    "module": "es2015",
    // lib用来指定项目中要使用的库 'es5', 'es6', 'es2015'...
    "lib": ["es6", "dom"]
    // outDir 用来指定编译后文件所在的目录
    "outDir": "./dist",
    // 将代码合并为一个文件
    // 设置outFile后,所有的全局作用域中的代码会合并到同一个文件中
    "outFile": "./dist/app.js"
    // 是否对js文件进行编译,默认是false
    // "allowJs": true,
    // 是否检查js代码是否符合语法规范,默认是false
    // "checkJs": true,
    // 是否移除注释
    "removeComments": true,
    // 不生成编译后的文件
    "noEmit": false,
    // 当有错误时不生成编译后的文件
    "noEmitOnError": true,
    // 所有严格检查的总开关
    "strict": true,
    // 用来设置编译后的文件是否使用严格模式,默认false
    "alwaysStrict": true,
    // 不允许隐式的any类型
    "noImplicitAny": true,
    // 不允许不明确类型的this
    "noImplicitThis": true,
    // 严格的检查空值
    "strictNullChecks": true
  }
}

webpack

实际项目一般不会自己去编译ts,而是结合打包工具来使用,比如下面是使用常用的webpack打包工具来打包ts的方法。

  • 初始化项目
// 创建项目目录 -> 进入项目目录
npm init -y
// 生成package.json配置文件,管理项目的依赖和命令
  • 安装webpack和ts的依赖
npm i -D webpack webpack-cli typescript ts-loader
  • 创建webpack的配置文件webpack.config.js
// 引入一个包
const path = require('path');
// 引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
// 引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

// webpack中的所有的配置信息都应该写在module.exports中
module.exports = {
    // 指定入口文件
    entry: "./src/index.ts",
    // 指定打包文件所在目录
    output: {
        // 指定打包文件的目录
        path: path.resolve(__dirname, 'dist'),
        // 打包后文件的文件
        filename: "bundle.js",

        // 告诉webpack不使用箭头
        environment:{
            arrowFunction: false
        }
    },
    // 指定webpack打包时要使用模块
    module: {
        // 指定要加载的规则
        rules: [
            {
                // test指定的是规则生效的文件
                test: /\.ts$/,
                // 要使用的loader
                use: [
                     // 配置babel
                     {
                         // 指定加载器
                         loader:"babel-loader",
                         // 设置babel
                         options: {
                             // 设置预定义的环境
                             presets:[
                                 [
                                     // 指定环境的插件
                                     "@babel/preset-env",
                                     // 配置信息
                                     {
                                         // 要兼容的目标浏览器
                                         targets:{
                                             "chrome":"58",
                                             "ie":"11"
                                         },
                                         // 指定corejs的版本
                                         "corejs":"3",
                                         // 使用corejs的方式 "usage" 表示按需加载
                                         "useBuiltIns":"usage"
                                     }
                                 ]
                             ]
                         }
                     },
                    'ts-loader'
                ],
                // 要排除的文件
                exclude: /node-modules/
            }
        ]
    },

    // 配置Webpack插件
    plugins: [
        new CleanWebpackPlugin(),
        new HTMLWebpackPlugin({
            // title: "这是一个自定义的title"
            template: "./src/index.html"
        }),
    ],

    // 用来设置引用模块
    resolve: {
        extensions: ['.ts', '.js']
    }

};
  • 创建ts的配置文件
{
  "compilerOptions": {
    "module": "ES2015",
    "target": "ES2015", 
    "strict": true,
    "noEmitOnError": true
  }
}
  • 在package.json中加入打包命令
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack", // 打包的命令
    "start": "webpack serve"
  }
  • 运行打包命令
npm run build // 将ts文件打包成js后放在dist目录中

面向对象

面向对象的特点

  • 类的定义与js里面的写法是一样的,只是属性和方法的定义多了类型的声明。

    class Student {
        // 属性定义
        name: string;
        age: number;
        // 构造方法
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }
        // 方法定义
        sayHello() {
            console.log(`hello, I am ${this.name}`)
        }
    }
    
    const student = new Student('tom', 10)
    student.sayHello()
    
  • 封装

    • 只读属性

      声明属性时添加一个readonly,则属性便成了只读属性无法修改

    • 静态属性

      static修饰的属性或方法,属于类而不是实例,可以直接通过类名来调用。

    • 3种属性修饰符

      • public(默认值),可以在类、子类和对象中修改

        class Person{
            public name: string; // 写或什么都不写都是public
            public age: number;
        
            constructor(name: string, age: number){
                this.name = name; // 可以在类中修改
                this.age = age;
            }
        
            sayHello(){
                console.log(`大家好,我是${this.name}`);
            }
        }
        
        class Employee extends Person{
            constructor(name: string, age: number){
                super(name, age);
                this.name = name; //子类中可以修改
            }
        }
        
        const p = new Person('孙悟空', 18);
        p.name = '猪八戒';// 可以通过对象修改
        
      • protected ,可以在类、子类中修改,不能在实例对象中修改

        class Person{
            protected name: string;
            protected age: number;
        
            constructor(name: string, age: number){
                this.name = name; // 可以修改
                this.age = age;
            }
        
            sayHello(){
                console.log(`大家好,我是${this.name}`);
            }
        }
        
        class Employee extends Person{
        
            constructor(name: string, age: number){
                super(name, age);
                this.name = name; //子类中可以修改
            }
        }
        
        const p = new Person('孙悟空', 18);
        p.name = '猪八戒';// 不能修改
        
      • private ,可以在类中修改,不能在子类和实例对象中修改

        class Person{
            private name: string;
            private age: number;
        
            constructor(name: string, age: number){
                this.name = name; // 可以修改
                this.age = age;
            }
        
            sayHello(){
                console.log(`大家好,我是${this.name}`);
            }
        }
        
        class Employee extends Person{
        
            constructor(name: string, age: number){
                super(name, age);
                this.name = name; //子类中不能修改
            }
        }
        
        const p = new Person('孙悟空', 18);
        p.name = '猪八戒';// 不能修改
        

总结:在面向对象的编程中,对属性和方法进行封装,并通过权限来控制外部的访问,可以提高数据的安全性。

  • 继承
    # 使用extends关键字
    class Pet {
        name: string;
        constructor(name: string) {
            this.name = name
        }
        bark() {
            console.log('我会叫')
        }
    }
    
    class Cat extends Pet {
        age: number;
        constructor(name: string, age: number) {
            super(name);
            this.age = age
        }
        bark() {
            console.log('喵喵~')
        }
    }
    
    const cat = new Cat('小黑', 3)
    cat.bark()
    
  • 抽象类
    • 抽象类专门用来被其他类继承,不能实例化
    • 使用abstract开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要实现
    abstract class Animal {
        abstract run(): void;
        bark(){
            console.log('动物在叫~');
        }
    }
    
    class Dog extends Animals{
        run(){
            console.log('狗在跑~');
        }
    }
    

接口

接口是特殊的抽象类,接口主要负责定义类的结构,包括属性的类型和抽象方法,用来给其他的类来实现。

interface Animal {
    name: string;
    bark(): void;
}

class Cat implements Animal {
    name: string
    constructor(name: string) {
        this.name = name
    }

    bark() {
        console.log('小黑喵')
    }
}

const cat = new Cat('小黑')
cat.bark()

泛型

定义函数或类时,有时候无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),具体的类型需要在使用的时候才能确定,这时候就可以使用泛型。

// 不知道参数类型,知道返回类型和参数类型一致
function test(arg: any): any{
    return arg;
}
// <T>就是泛型,T是我们给这个类型起的名字,可以叫其他名字
function demo<T>(num1: T): T{
    return num1
}

// 方式1:直接调用,ts会自己推断类型
demo(666)
// 方式2:指定参数类型
demo<number>(666)

// 多个泛型的使用
function test<T, K>(a: T, b: K): K{
    return b;
}
test<number, string>(10, "hello");

使用泛型时,完全可以将泛型当成是一个普通的类去使用,类中同样可以使用泛型。

// 类中使用泛型定义属性
class MyClass<T>{
    prop: T;

    constructor(prop: T){
        this.prop = prop;
    }
}

// 还可以对泛型的范围进行约束
interface MyInter{
    length: number;
}

function test<T extends MyInter>(arg: T): number{
    return arg.length;
}

下面的例子使用接口对泛型的范围进行约束,使用T extends Person表示泛型T必须是Person的子类,不一定非要使用接口类,抽象类也同样适用。

interface Person {
    name: string;
}

function test<T extends Person>(person: T): void {
    console.log(person.name)
}
class Student implements Person {
    name: string = 'xiaoming'
}
test(new Student())
// 这里有鸭子类型的特点,只要符合Person的结构,编译器就认为是Person的子类
test({ name: 'hello' })
// 输出:
// xiaoming
// hello

练习案例

贪吃蛇小游戏

经典的贪吃蛇小游戏,用typescript基本语法实现,配合webpack来打包代码。主要的功能:蛇可以吃到随机生成的食物,蛇吃到一定数量食物会有等级的提升,等级的提升会让蛇的速度变快,蛇不能撞墙和撞到自己的身体。游戏体验地址:http://81.70.245.149:8888/

  • 项目的结构
├── package.json
├── src
│   ├── index.html
│   ├── index.ts
│   ├── module
│   │   ├── Food.ts
│   │   ├── GameController.ts
│   │   ├── Sanke.ts
│   │   └── ScorePanel.ts
│   └── style
│       └── index.less
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
  • 几个模块的作用
    • Food.ts:随机位置生成食物
    • ScorePanel.ts:记分牌,蛇吃到食物分数加1,达到条件提升等级
    • Sanke.ts:蛇身体移动,增加身体,碰撞检测
    • GameController.ts:游戏初始化,主要游戏逻辑处理和各个模块的交互。

整个案例很简单,涉及到的是typescript的基础语法,比较麻烦的就是使用webpack来构建项目的时候,需要自己去配置几个配置文件,最后可以用webpack打包成静态资源来部署。

代码链接:gitee.com/samll__oran…