【typescript】寫給JS老鳥的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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM