1. 程式人生 > 實用技巧 >Typescript | Vue3原始碼系列

Typescript | Vue3原始碼系列

TypeScript 是開源的,TypeScript 是 JavaScript 的型別的超集,它可以編譯成純 JavaScript。編譯出來的 JavaScript 可以執行在任何瀏覽器上。TypeScript 編譯工具可以執行在任何伺服器和任何系統上

  • 問題: 什麼是超集

超集是集合論的術語
說到超集,不得不說另一個,子集,怎麼理解這兩個概念呢,舉個例子

如果一個集合A裡面的的所有元素集合B裡面都存在,那麼我們可以理解集合A是集合B的子集,反之集合B為集合A的超集

現在我們就能理解為Typescript裡包含了Javascript的所有特性,這也意味著我們可以將.js字尾直接命名為.ts檔案跑到TypeScript

的編繹系統中

Typescript 解決了什麼問題

一個事物的誕生一定會有其存在的價值

那麼Typescript的價值是什麼呢?

回答這個問題之前,我們有必要先來了解一下Typescript的工作理念

本質上是在JavaScript上增加一套靜態型別系統(編譯時進行型別分析),強調靜態型別系統是為了和執行時的型別檢查機制做區分,TypeScript的程式碼最終會被編譯為JavaScript

我們再回到問題本身,縮小一下範圍,Typescript創造的價值大部分是在開發時體現的(編譯時),而非執行時,如

  • 強大的編輯器智慧提示 (研發效率,開發體驗)
  • 程式碼可讀性增強 (團隊協作,開發體驗)
  • 編譯時型別檢查 (業務穩健,前端專案中Top10 的錯誤型別低階的型別錯誤佔比達到70%)

    正文

    本篇文章作為Vue3原始碼系列前置篇章之一,Typescript的科普文,主要目的為了大家在面對Vue3原始碼時不會顯得那麼不知所措,下來將介紹一些Typescript的基本使用

    變數申明

    基本型別

    letisDone:boolean=false
    letnum:number=1
    letstr:string='vue3js.cn'
    letarr:number[]=[1,2,3]
    letarr2:Array<number>=[1,2,3]//泛型陣列
    letobj:Object={}
    letu:undefined=undefined;
    letn:null=null;

    型別補充

    • 列舉Enum

    使用列舉型別可以為一組數值賦予友好的名字

    enumLogLevel{
    info='info',
    warn='warn',
    error='error',
    }
    • 元組Tuple

    允許陣列各元素的型別不必相同。比如,你可以定義一對值分別為 string和number型別的元組

    //Declareatupletype
    letx:[string,number];
    //Initializeit
    x=['hello',10];//OK
    //Initializeitincorrectly
    x=[10,'hello'];//Error
    • 任意值Any

    表示任意型別,通常用於不確定內容的型別,比如來自使用者輸入或第三方程式碼庫

    letnotSure:any=4;
    notSure="maybeastringinstead";
    notSure=false;//okay,definitelyaboolean
    • 空值Void

    與 any 相反,通常用於函式,表示沒有返回值

    functionwarnUser():void{
    console.log("Thisismywarningmessage");
    }
    • 介面interface

    型別契約,跟我們平常調服務端介面要先定義欄位一個理

    如下例子 point 跟 Point 型別必須一致,多一個少一個也是不被允許的

    interfacePoint{
    x:number
    y:number
    z?:number
    readonlyl:number
    }
    constpoint:Point={x:10,y:20,z:30,l:40}
    constpoint2:Point={x:'10',y:20,z:30,l:40}//Error
    constpoint3:Point={x:10,y:20,z:30}//Error
    constpoint4:Point={x:10,y:20,z:30,l:40,m:50}//Error

    可選與只讀 ? 表示可選參, readonly 表示只讀

    constpoint5:Point={x:10,y:20,l:40}//正常
    point5.l=50//error

    函式引數型別與返回值型別

    functionsum(a:number,b:number):number{
    returna+b
    }

    配合interface使用

    interfacePoint{
    x:number
    y:number
    }

    functionsum({x,y}:Point):number{
    returnx+y
    }

    sum({x:1,y:2})//3

    泛型

    泛型的意義在於函式的重用性,設計原則希望元件不僅能夠支援當前的資料型別,同時也能支援未來的資料型別

    • 比如

    根據業務最初的設計函式identity入參為String

    functionidentity(arg:String){
    returnarg
    }
    console.log(identity('100'))

    業務迭代過程引數需要支援Number

    functionidentity(arg:String){
    returnarg
    }
    console.log(identity(100))//Argumentoftype'100'isnotassignabletoparameteroftype'String'.

    為什麼不用any呢?

    使用any會丟失掉一些資訊,我們無法確定返回值是什麼型別
    泛型可以保證入參跟返回值是相同型別的,它是一種特殊的變數,只用於表示型別而不是值

    語法<T>(arg:T):T其中T為自定義變數

    consthello:string="Hellovue!"
    functionsay<T>(arg:T):T{
    returnarg;
    }
    console.log(say(hello))//Hellovue!

    泛型約束

    我們使用同樣的例子,加了一個console,但是很不幸運,報錯了,因為泛型無法保證每種型別都有.length屬性

    consthello:string="Hellovue!"
    functionsay<T>(arg:T):T{
    console.log(arg.length)//Property'length'doesnotexistontype'T'.
    returnarg;
    }
    console.log(say(hello))//Hellovue!

    從這裡我們也又看出來一個跟any不同的地方,如果我們想要在約束層面上就結束戰鬥,我們需要定義一個介面來描述約束條件

    interfaceLengthwise{
    length:number;
    }

    functionsay<TextendsLengthwise>(arg:T):T{
    console.log(arg.length)
    returnarg;
    }
    console.log(say(1))//Argumentoftype'1'isnotassignabletoparameteroftype'Lengthwise'.
    console.log(say({value:'hellovue!',length:10}))//{value:'hellovue!',length:10}

    交叉型別

    交叉型別(Intersection Types),將多個型別合併為一個型別

    interfacefoo{
    x:number
    }
    interfacebar{
    b:number
    }
    typeintersection=foo&bar
    constresult:intersection={
    x:10,
    b:20
    }
    constresult1:intersection={
    x:10
    }//error

    聯合型別

    交叉型別(Union Types),表示一個值可以是幾種型別之一。我們用豎線 | 分隔每個型別,所以 number | string | boolean表示一個值可以是 number, string,或 boolean

    typearg=string|number|boolean
    constfoo=(arg:arg):any=>{
    console.log(arg)
    }
    foo(1)
    foo('2')
    foo(true)

    函式過載

    函式過載(Function Overloading), 允許建立數項名稱相同但輸入輸出型別或個數不同的子程式,可以簡單理解為一個函式可以執行多項任務的能力

    例我們有一個add函式,它可以接收string型別的引數進行拼接,也可以接收number型別的引數進行相加

    functionadd(arg1:string,arg2:string):string
    functionadd(arg1:number,arg2:number):number

    //實現
    functionadd<T,U>(arg1:T,arg2:U){
    //在實現上我們要注意嚴格判斷兩個引數的型別是否相等,而不能簡單的寫一個arg1+arg2
    if(typeofarg1==='string'&&typeofarg2==='string'){
    returnarg1+arg2
    }elseif(typeofarg1==='number'&&typeofarg2==='number'){
    returnarg1+arg2
    }
    }

    add(1,2)//3
    add('1','2')//'12'

    總結

    通過本篇文章,相信大家對Typescript不會再感到陌生了

    下面我們來看看在Vue原始碼Typescript是如何書寫的,這裡我們以defineComponent函式為例,大家可以通過這個例項,再結合文章的內容,去理解,加深Typescript的認識

    //overload1:directsetupfunction
    exportfunctiondefineComponent<Props,RawBindings=object>(
    setup:(
    props:Readonly<Props>,
    ctx:SetupContext
    )=>RawBindings|RenderFunction
    ):{
    new():ComponentPublicInstance<
    Props,
    RawBindings,
    {},
    {},
    {},
    //publicprops
    VNodeProps&Props
    >
    }&FunctionalComponent<Props>

    //defineComponent一共有四個過載,這裡省略三個

    //implementation,closetono-op
    exportfunctiondefineComponent(options:unknown){
    returnisFunction(options)?{setup:options}:options
    }