【typescript】写给JS老鸟的TS速成教程

  • A+
所属分类:Web前端
摘要

 搭建基础开发环境要准备的环境node.js 14.14以上 ,vs code最新,vs code TS开发插件


写给JS老鸟的TS速成教程

 

搭建基础开发环境

要准备的环境

node.js 14.14以上 ,vs code最新,vs code TS开发插件

开始开发

方式一:TS原生编译开发

补充知识:i是install的简写,-g是global的简写,除此外还有-D = --save-dev 、-S=  --save,现在新版npm cli好像会默认执行—save

 

npm i -g typescript && tsc -init

vs code创建 文件夹/index.ts,

手动编译 tsc+文件名,改一次编译一次

自动编译 Ctrl+shift+B —> 监视模式,文件变动时自动编译 或 如下:

①    tsc 文件名 -w,缺点是只能监视一个文件,除非开多个命令行。

②    创建tsconfig.json,只写一对{},使其符合json规范,然后直接执行 tsc,即可监视所有ts文件的改动。

 

运行结果:创建index.html, script.import 编译好的index.js,用浏览器打开index.html

开箱即用见 附件【TS_ori】

 

编译控制

tsconfig.json是ts编译器的配置文件,可以对编译进行自定义

不知道可选值可以先给一个错误的值,编译器会列出所有可选的正确配置值

tsconfig.json可以写注释,因为其是ts编译器的配置文件。

{

  // 初步

 

  "include":[ //指定哪些目录的ts文件需要被编译,包含目录

    "./src/**/*" //.当前目录 src文件夹 **任意目录 *任意文件

  ],

  "exclude":[],//指定哪些ts文件不被编译,排除目录,有默认值,比如node_modules

  "extends":"./config/base",//继承某个配置文件,配置复用

  "files":[],//指定ts文件编译,包含文件,与include类似

 

  //进阶

  "compilerOptions":{

    "target":"ES6", //指定ts编译成何种js版本,即目标代码的版本

  //ES2015(ES6),ES2016...ESNext(最新版ES)

  "module":"ES6",//指定模块化解决方案

   "lib":["dom"],//用来指定项目中使用到的库,一般不写此属性(有默认值),除非代码在nodejs中运行,缺少某些库,如dom。dom库为document

  "outDir":"./dist",//指定编译后文件所在目录。dist即distribution,发行版

  "outFile":"./dis/app.js",//将代码合并为一个文件,若需要把各模块合并为一个文件,只能支持amd and system模块方案

  "allowJs":false, //是否对js进行编译,默认否  - false                             //合并文件一般不用outFile,而是结合打包工具来实现

  "checkJs":false, //是否检查js代码是否符合语法规范

  "removeCommentss":true,//是否移除注释

  "noEmit":false,//执行编译但是不生成编译后的文件,场景是只用到ts编码和类型检查, 不须编译

  "noEmitOnError":false,//发送错误时不生成编后的译文件,默认为false,为的是让js程序员缓慢过渡到ts,建议改为true

 

  "strict":false,//以下三个检查的总开关,建议开发时设为true

  "alwaysStrict":false,//指定编译后的js文件是否使用严格模式。严格模式:语法严格,在browser性能好一些

  "noImplicitAny":false,//Implicit隐式的,开启隐式any的检查,不允许使用

  "noImplicitThis":false,//不允许不明确类型的this

  "strictNullChecks":false,//严格检查空值,若一个变量可能为null,则报错,除非先进行非空判断或box?.addEventListener()

 }

}

 

方式二:自动化开发

补充知识:webpack-cli是通过命令行来使用webpack、ts-loader是webpack加载器,是ts和webpack的桥梁

补充知识:webpack这东西就是会沿着你给定的一个入口文件去遍历所有关联的文件,然后按照一定规则重新整理、压缩成新的一批文件,我们的文件格式是无穷无尽的,webpack不可能认识和处理全部格式,所以我们通过加入各种loader,称加载器,来帮助webpack认识它们。

 

创建空文件夹,执行npm init -y(完成项目初始化)

npm i -D webpack webpack-cli typescript ts-loader

撰写webpack配置。要注意的是,webpack打包必须配合tsconfig.json使用,也就是你的ts代码处要有这个tsconfig.json文件,因为ts程序的具体编译的工作还是由ts本身提供,而ts编译本身要用到tsconfig.json,现在看来,ts-loader真的仅仅是个桥梁。

在package.json中加入打包命令build:webpack,执行npm run build。

撰写配置

使用chrome来查看ts程序的运行结果

补充知识:webpack可以支持自动生成html文件并引入打包好的资源,以演示代码效果,这比自己手动写个html方便多了,生成这个html文件有两种方式,配置式,如传个title参数,其他由webpack自己决定,或者自己拟定一个html模板交给插件。

cnpm i -D html-webpack-plugin

 

自动化构建--所写即所见

cnpm i -D webpack-dev-server ,package.json 加入 "start":"webpack serve --open" ,npm run start

清楚旧的打包文件

打包默认模式是用新文件覆盖旧文件,可能存在残留问题,解决方法:clean-webpack-plugin

 

指定可引入的文件

指定哪些文件可以被其他文件作为模块来引入,这里的引入是代码文件之间的引入,这样我们就可以愉快地使用ESM(ES Module)了

解决目标代码的兼容性问题

补充知识:前端常见兼容性问题有两种,一种是浏览器内核类,一种是规范版本类。前者主要表现为在Chrome能运行的代码,在Firefox却出现问题,在iPhone默认浏览器能运行的代码在华为默认浏览器却有问题;后者主要表现为ES6的代码在浏览器中报错,因为ES6对比ES5变化是较大的,现在ES规范每年一个版本,浏览器跟进也比以前快了,这个问题正变得越来越不是问题。

TS的tsc仅仅能够实现把ts源码编译成不同ES规范版本的js代码而已。

babel/core是babel的核心库、present-env是预置环境,预置不同浏览器环境,帮助代码兼容不同浏览器,babel-loader是结合webpack和babel的桥梁、core-js(Modular standard library )可以使老版本的浏览器使用到新版本的js的一些技术,如promises等,由于这个库比较庞大,内含很多小库,且是模块化的,我们应按需使用,按需使用我们直接通过webpack来实现

webpack rules的use执行顺序是从下往上执行,我们先用ts-loader把ts转换为js,然后用babel-loader把新版本的js转换为老版本/兼容性高的js

  1. cnpm i -D @babel/core @babel/preset-env babel-loader core-js

 

关于兼容性打包后仍报错

补充知识:设置了targets.ie==11后,打包的代码拿到ie11运行依然报错,原因在于webpack打包后的代码用了一个箭头函数实现的自执行函数包裹,作用是创造一个代码作用域,防止全局变量污染等,它实际是webpack自动生成的,与babel无关,babel只能源文件内的箭头函数起作用,实际上,这可能是webpack故意为之,其本身就是不想兼容某些低版本浏览器,解决方法,out加上environment:{arrowFunction:false},取消箭头函数。

 

TS语言

报错信息,assign:赋值、指派、指定,resolve:解析、决定、解决

默认ts代码有错误,仍然可以编译生成js,在tsconfig中可更改

ts可编译成任意版本js

若赋初值,ts会根据值类型推算变量类型,这会使变量声明加上类型变得多此一举,没错,实际上,类型检测更多是用给函数传参(形参)和返回值的。

 

类型

补充知识:可用字面量代替类型名,如10,以后只能赋值10,有点常量的意思。除此之外,还有联合类型、任意类型等

 

talk is cheap,show me the code

 

export const zex: number = 1;

 

{

    let a: number = 10;

    let b: number = 20;

    console.log(a, b)

 

    //计算变量类型

    let c = true;

 

    //计算函数返回值类型

    function sum(a: number, b: number): string {

        return a + b + "";

    }

    let result = sum(a, b);

}

 

 

const zex:number=1;

{

// 字面量赋值

let a:10;

let a1:number;

let b:10=10;

a=10;

a1=10;

 

// 用 或符号 构成 联合类型

let c:"male"|"female";

c="male";

c="female";

 

let d:string|number;

d=10;

d="hello";

 

// 任意类型 - 关闭类型检测

let e:any;

e=10;

e="female";

e=true;

 

// 隐式any

let f;

f=10;

f="female";

f=true;

 

// 赋值

// 以下不报错,这导致a的类型检测失效

a1=e;

 

// unknown同any差不多,但是可解决以上问题,是一个类型安全的any

let g:unknown;

// a=g;报错,应改为

 

if(typeof g == "number"){

    a1=g;

}

 

//断言:判断的语言,根据实际情况,把某个变量人为(自己)地断定为某种类型,跳过编译

a1 = g as number;

a1 = <number>g;

 

//函数的返回值

 

//返回值为number|string型

function sum(a:number,b:number){

    if(a>b){

        return a+b;

    }else{

        return a+b+"";

    }

}

 

function sum2(a:number,b:number):number|string{

    if(a>b){

        return a+b;

    }else{

        return a+b+"";

    }

}

 

// 空返回

function sum3(a:number,b:number):void{

    return;

}

function sum4(a:number,b:number):void{}

function sum5(a:number,b:number):void{

    return undefined;

}

 

// never:永远不会返回结果

// 没有返回值也是一种返回值,而never是空空

// 在程序报错时,代码立即停止执行,程序结束,函数结束,所以永远不会有值返回,事情不会发生

function err():never{

    throw new Error("err");

}

 

}

 

{

// object其实是无用的,因为ts一且皆对象,并没有起到类型限制的作用

let a:object;

a={};

a=function(){};

//以下有效

let b:{name:string};

b={name:"John"};

// b={name:"John",age:12}结构不一致报错

// ?-可选属性

let c:{name:string,age?:number};

c={name:"John",age:12};

c={name:"John"}

 

// 任意属性:自由添加属性,新属性未知

// 新属性名为字符串,属性值为任意类型,propName命名随意

let d:{name:string,[propName:string]:any}

d={name:"John",str:123}

let d1:{name:string,[propName:string|number]:any}

d1={name:"John",str:123}

d1={name:"John",123:123}

 

// 限制函数,单Function无意义

let e:(a:number,b:number)=>number

 

// 数组

let f:string[];

f=["John"]

let f1:Array<number>;

f1=[123];

 

// 元组:固定长度的数组

let g:[String,String];

// 必须符合给定,不多不少

g=["Hello","Hello"];

 

//考虑数据存储与表示分离,数据库存储应简短、非字符串,此时Object并不满足要求

// 枚举,默认从0开始

enum Gender{

    male=0,

    female=1

};

let h:{name:string,gender:Gender};

h={name:"Jhon",gender:Gender.female};

console.log(h.gender,h.gender==Gender.female)

 

// & - 与,类型组合

let i:number&string //无意义

let i1:{name:string}&{age:number};

i1={name:"John",age:18};

 

// 类型的复用-别名

let j1:number;

let j2:1|2|3|4|5;

let j3:1|2|3|4|5|6;

 

type myType=number;

type myType2=1|2|3|4|5;

let k1:myType;

let k2:myType2;

let k3:myType2|6;

 

k3=3

k3=6

// k3=7 报错

 

}

 

 

// 类

 

class Person {

    // 实例属性,通过实例访问

    readonly name: string = "默认名字";

    age: number = 18;

 

    // 类属性,通过类访问

    static avgAge: number = 18;

    //只读属性

    static readonly baseName: string = "张"

 

    // 类方法

    sayHello(name: string): string {

        return "Hello";

    }

    static sayHi(name: string): string {

        return "Hi";

    }

};

 

const per = new Person();

Person.avgAge = 19;

per.age = 20;

// Person.name="三三"; // 报错

// Person.baseName="李"; // 报错

console.log(Person.avgAge, per.age);

// 构造器与this

class Dog {

    name: string;

    age: number;

    constructor(name: string, age: number) {

        // this表示当前实例

        this.name = name;

        this.age = age;

    }

    bark() {

        // 当前调用方法的对象,如dog1.bark(),this为dog1

        console.log(this, "旺旺旺");

    }

}

 

const dog: Dog = new Dog("旺财", 3);

 

// 继承

{

    class Animal {

        name: string;

        age: number;

        constructor(name: string, age: number) {

            this.name = name;

            this.age = age;

        }

        bark() {

        }

    }

 

    class Dog extends Animal {

        bark() {

            console.log(this.name + this.age + "旺旺旺");

        }

 

        run() {

            console.log("蹦蹦跳跳");

        }

    }

 

    class Cat extends Animal {

        sex: string;

 

        constructor(name: string, age: number, sex: string) {

            // 调用父类构造器

            super(name, age);

            this.sex = sex;

        }

 

        bark() {

            // 引用父类的方法

            super.bark();

        }

    }

 

    const cat = new Cat("小喵", 3, "母");

 

    //抽象类

    //对于某些类,由于本身拿来实例化是不合适的,且我们也不希望被这样做

    //因此我们就把他设为抽象类,只可以继承,不可以实例化

 

    abstract class Food {

        name: string;

        color: string;

 

        //  抽象类须有构造器

        constructor(name: string, color: string) {

            this.name = name;

            this.color = color;

        }

 

        abstract eat(name: string): void;

        abstract cook(name: string): number | string;

    }

 

    /**

     * 在限制对象的类型上,以下两种方式功能一致

     * 接口,定义了类的结构(属性、方法)

     * 此接口非彼接口,它与Java的接口有点不同

     */

 

    type myType = {

        name: string,

        age: number

    };

 

    interface myInterface {

        name: string;

        age: number;

    }

 

    // 接口可以重复定义,实际效果是同名接口的总和

    interface myInterface {

        sex: string;

    }

 

    const man: myInterface = {

        name: "张三",

        age: 18,

        sex: "男"

    }

 

    // 限制类的结构

    interface myInterface2 {

        name: string;

 

        sayHi(): void;

    }

    interface myInterface3 { }

 

    class People implements myInterface2, myInterface3 {

        name: string;

 

        constructor(name: string) {

            this.name = name;

        }

 

        sayHi(): void {

            console.log(this.name);

        }

    }

 

    /**

     * 抽象类和接口

     * 抽象类:

     * 1、可以普通方法,也可以抽象方法

     * 2、通过继承来使用,TS 类的设计为单继承

     *

     * 接口:

     * 1、只有抽象方法

     * 2、通过实现来使用,支持多继承

     */

}

 

// 属性的封装 - 保护类的属性

 

// 属性可被随意修改将导致对象中的数据变得不安全

// 通过类修饰符解决这个问题

 

// 默认为public,属性可被随意修改

{

    class Person {

        name: string;

        public sex: string;

        public age: number;

 

        constructor(name: string, sex: string, age: number) {

            this.name = name;

            this.sex = sex;

            this.age = age;

        }

 

    }

 

    // 最强安全性

    // private修饰的属性,只有两种修改方式:

    // 1、构造器传入

    // 2、调用实例方法修改,这种方法在java中被称为setter

    // 此外,这种方式也给属性的访问带来麻烦,我们同样只能通过方法return该属性来访问,在java中称为getter

    // 这种setter和getter模式在C#中被吸收为语法:

    /**

     * get{

             return _name;

        }

        set{

            _name = value;

        }

     */

    class Person2 {

        private name: string;

        private sex: string;

        private age: number;

        protected height: number;

 

        constructor(name: string, sex: string, age: number, height: number) {

            this.name = name;

            this.sex = sex;

            this.age = age;

            this.height = height;

        }

 

        setName(name: string): void {

            this.name = name;

        }

        getName(): string {

            return this.name;

        }

    }

 

    // 此外,private属性也不可以被子类访问,protected可以

    class Person3 extends Person2 {

        showName(): void {

            // 无法访问name,可以访问height

            // console.log(this.name);

            console.log(this.height);

        }

    }

 

    // 泛型

    // 当一种类型是什么要在实际中才能确定是,我们使用泛型

    // 泛型就是类型的抽象表示(代表)

 

    // 当以下函数的参数与返回值类型一致,但是无法确定是什么具体类型时,用泛型

    function fn(a: number): number {

        return a;

    }

 

    // 能否使用any?不行,一是关掉了类型检查,这将埋下隐患,二是无法体现两者类型一致

 

    function fn2<T>(a: T): T {

        return a;

    }

    function fn3<T, K, I>(a: T, b: K, c: I): T {

        return a;

    }

    function fn4<T, K>(a: T, b: K, c: number): number {

        return c;

    }

 

    // 在调用含泛型函数时,一般会自动推定泛型的类型,我们也可手动指定

    fn2(1);

    fn4(1, "2", 3);

    fn4<number, string>(1, "2", 3);

}

 

【附件】

https://github.com/heytheww/TSLenrning