TypeScript (基礎)
TypeScript 是 JavaScript 的型別的超集,它可以編譯成純 JavaScript。編譯出來的 JavaScript 可以執行在任何瀏覽器上。TypeScript 編譯工具可以執行在任何伺服器和任何系統上。TypeScript 是開源的。
以下內容均出自於TS入門教程
以及Ts 官網的一些內容,沒有基礎的小夥伴直接看打了:star:️的內容即可。
嘗試
看了之後怎麼搭個環境寫一寫?
mkdir demo cd demo touch 1.ts 複製程式碼
開啟vscode,開啟控制檯,切換到問題 tab
歐了,開始嘗試 ts 吧
:star:️基礎型別
└boolean
let isDone: boolean = false; // 使用建構函式 Boolean 創造的物件不是布林值 複製程式碼
└null & undefined
是所有型別的子型別
void型別不能賦值給 number
let u: undefined = undefined; let n: null = null; let num: number = undefined; let u: undefined; let num: number = u; 複製程式碼
└void 型別
一般表示函式沒有返回值。用在變數上沒有什麼卵用。
function warnUser(): void { console.log("This is my warning message"); } let a: void = undefined let a: void = 'undefined' // 報錯,這是字串 複製程式碼
跟它相似的型別還有undefined
和null
在不開啟嚴格空檢查的情況下--strictNullChecks
,他們可以賦值給所有已經定義過***其他型別***的變數。
也就是說他們是所有型別的子型別
let a: undefined = undefined let a: null = null 複製程式碼
└數字 number
TypeScript裡的所有數字都是浮點數。 這些浮點數的型別是 number。支援十進位制和十六進位制字面量二進位制和八進位制字面量。
let decLiteral: number = 6; let hexLiteral: number = 0xf00d; // ES6 中的二進位制表示法 let binaryLiteral: number = 0b1010; // ES6 中的八進位制表示法 let octalLiteral: number = 0o744; let notANumber: number = NaN; let infinityNumber: number = Infinity; 複製程式碼
└字串 string
單雙引''
""
,模板字元的都被視為字串
let str:string = '' 複製程式碼
:star:️陣列型別
有多種宣告陣列的方式
-
最簡單的方法是使用
型別 + []
來表示陣列:
const arr: number[] = [1,2,3] const arr2: string[] = ['1','2'] 複製程式碼
- 陣列泛型定義方式
const arr2: Array<number> = [1,2,3,3] const arr2: Array<string> = [1,2,3,3] 複製程式碼
- 介面表示陣列
interface NumArr { [index: number]: number; } let numArr: NumArr = [1,2,3]; 複製程式碼
- any 型別陣列
let list:any[] = [1,"z",{}] 複製程式碼
- 元組型別宣告
// 表示一個確定陣列長度和型別的寫法 const arr:[string,number] = ['2',3] 複製程式碼
- 類陣列
就是偽陣列的定義
官方已給了各自的定義介面Arguments
,NodeList
,HTMLCollection
function sum() { let args: IArguments = arguments; } 複製程式碼
列舉 enum
js中沒有這型別,仿照強型別語言來的。值只能為數字,不定義預設值得情況為從0開始。
enum Color {Red, Green, Blue} let c: Color = Color.Green; // c = 1 enum Number {one = 10, two} let c: Number = Number.two; // c = 11 複製程式碼
:star:️any 型別(任意值)
指代所有的型別
let a: any = '123' let a = 123; // 不宣告預設 any 複製程式碼
never
表示永遠不存在的值,一般會用來寫丟擲異常或推斷為返回值為never的函式。(比如return一個其他的never型別)
function error(message: string): never { throw new Error(message); } error('a') 複製程式碼
object 型別
非簡單型別 也就是除number,string,boolean,symbol,null或undefined之外的型別。
function create(o: object | null): void{ console.log(o); }; create({ prop: 0 }); // OK create(null); // OK create([]); // OK create('a'); // error 複製程式碼
:star:️interface 介面
在 TypeScript 中,我們使用介面(Interfaces)來定義物件的型別。
對物件的描述
介面一般首字母大寫。
賦值的時候,變數必須和介面保持一致。
interface Person { name: string; age: number; } let tom: Person = { name: 'Tom', age: 25 }; 複製程式碼
└可選屬性
不想完全匹配某個介面,通過?
表示這個屬性是可選的
仍然不允許新增未定義的屬性
interface Person { name: string; age?: number; } let tom: Person = { name: 'Tom' }; 複製程式碼
└任意屬性
讓介面允許新增任意的屬性值
[propName: string]: any;
interface Person { name: string; age?: number; [propName: string]: any; } let tom: Person = { name: 'Tom', gender: 'male' }; 複製程式碼
一旦定義了任意屬性, 那麼確定屬性和?
可選屬性都必須是任意屬性的子集
interface Person { name: string; age?: number; [propName: string]: string; } let p:Person = { name: 'zzc', age: 12, // error , 定義了 propName 必須將值設定為 string 型別 gender: 'male' , } 複製程式碼
└只讀屬性 readonly
相當於是常量了,初次賦值後不能重新賦值
做為變數使用的話用const
,若做為屬性則使用readonly
。
interface demo { readonly a: string; // readonly定以後不能改值 b: number } let obj: demo = { a: 'ss', b: 1 } obj.a = 'aa' // error obj.b = 2 // success 複製程式碼
只讀的約束存在於第一次給物件賦值的時候,而不是第一次給只讀屬性賦值的時候
interface Person { readonly id: number; } const tom: Person = {} // error tom.id = 1 // error, 複製程式碼
會報兩次錯,第一個是因為指定了 id,但沒有給 id 賦值
第二個錯是給只讀屬性id賦值了
└ReadonlyArray
通過ReadonlyArray
定義的陣列,再也無法改變了。
let a: number[] = [1, 2, 3, 4]; let ro: ReadonlyArray<number> = [1,2,3]; a[0] = 10 // success ro[0] = 12; // error! ro.push(5); // error! ro.length = 100; // error! a = ro; // 注意! 將readonly的值賦值給一個可變得陣列也是不行的。 a = ro as Array<any> // 但是可以用斷言重寫 複製程式碼
:star:️函式型別
常見的函式宣告方式有: 函式宣告 & 函式表示式
用 ts 定義函式要考慮它的輸入和輸出
- 函式宣告方式定義
function sum(a:number,b:number):number{ return a+b } // 形參和實引數量要一致 sum(1) // error sum(1,2) //3 sum(1,2,3) // error 複製程式碼
- 函式表示式定義
// 方式 1 let sum = function(a:number,b:number):number { return a + b; } // 方式二 let sum: (x: number, y: number) => number = function (x: number, y: number): number { return x + y; }; 複製程式碼
方式一中只對等號右側的匿名函式定義了型別,左邊是ts通過型別推論定義出來的
方式二才是給 sum 定義型別,**其中的=>
不是 es6的=>
** ,它用來表示函式的定義,左邊是輸入型別,需要用括號括起來,右邊是輸出型別。
因為和 es6箭頭函式可能造成混淆,最好用方式一;
└可選引數
通過?
給函式定義可選引數
可選引數後面不允許再出現必須引數了
如果給引數添加了預設值,ts 會自動識別為可選,且不受上一條規則的限制。
function sum(a:number,b?:number){} function sum(a?:number,b:number){} // error function sum(a:number = 1,b:number){} // 預設值,識別為可選,且不報錯 複製程式碼
└...rest
使用…rest獲取剩餘引數,使用陣列型別去定義它
剩餘引數必須是函式的最後一個引數
function (a, ...items:any[]){} function (...items:any[], a){} // error 複製程式碼
└函式的過載
過載允許一個函式接受不同數量或型別的引數時,作出不同的處理。
可以重複定義一個函式的型別
function say(somthing:string):string; function say(somthing:number):string; // 以上是函式定義 // 以下是函式實現 function say(somthing:string|number):string|number { return somthing } 複製程式碼
注意,TypeScript 會優先從最前面的函式定義開始匹配,所以多個函式定義如果有包含關係,需要優先把精確的定義寫在前面。
:star:️型別斷言
型別斷言(Type Assertion)可以用來手動指定一個值的型別。
斷定這個變數的型別是啥
型別斷言不是型別轉換
兩種寫法
<型別>值
or值 as 型別
如果在 tsx 語法中使用,必須用as
- 例子
聯合型別可以指定一個變數為多種型別,此變數只能訪問型別們的共有方法。
但一些情況下我們必須使用某一型別的方法或屬性時,就可以用斷言
function say(something:number|string):void{ alert(something.length) // 聯合型別,報錯 } // ==> 使用斷言, 在變數前加上 <型別> function say(something:number|string):void{ alert( (<string>something).length ) // success } 複製程式碼
斷言成一個聯合型別中不存在的型別是不允許的
function say(something:number|string):void{ alert(<boolean>something.length) // 聯合型別沒有 boolean ,error } 複製程式碼
:star:️declare 宣告
第三方庫會暴露出一個變數,讓我們在專案中直接使用。
但是 ts 編譯時不知道這是啥,編譯無法通過。
此時我們就要用declare var
宣告語句來定義他的型別
// 比如 jquery $('div') // ERROR: Cannot find name 'jQuery'. // ==> 使用 declare var 第三方庫變數: (引數: string) => 返回型別 declare var $: (selector: string) => any; $('#foo'); // success 複製程式碼
declare var
並不是真正的宣告一個變數,編譯完會刪除,僅僅是定義型別。
└宣告檔案
通常我們會把宣告語句放到一個單獨的檔案(*.d.ts
)中,這就是宣告檔案
宣告檔案必需以.d.ts
為字尾
假如仍然無法解析,那麼可以檢查下tsconfig.json
中的files
、include
和exclude
配置,確保其包含了jQuery.d.ts
檔案。
// src/jQuery.d.ts declare var jQuery: (selector: string) => any; 複製程式碼
這只是非模組化專案中使用的例子
└第三方宣告檔案
當然,jQuery 的宣告檔案不需要我們定義了,社群已經幫我們定義好了:jQuery in DefinitelyTyped 。
我們可以直接下載下來使用,但是更推薦的是使用@types
統一管理第三方庫的宣告檔案。
@types
的使用方式很簡單,直接用 npm 安裝對應的宣告模組即可,以 jQuery 舉例:
npm i @types/jquery -D 複製程式碼
可以在這個頁面搜尋你需要的宣告檔案。
└自定義宣告檔案
宣告檔案有以下方法
-
全域性變數:通過
<script>
標籤引入第三方庫,注入全域性變數 -
npm 包:通過
import foo from 'foo'
匯入,符合 ES6 模組規範 -
UMD 庫:既可以通過
<script>
標籤引入,又可以通過import
匯入 -
模組外掛:通過
import
匯入後,可以改變另一個模組的結構 -
直接擴充套件全域性變數:通過
<script>
標籤引入後,改變一個全域性變數的結構。比如為String.prototype
新增了一個方法 -
通過匯入擴充套件全域性變數:通過
import
匯入後,可以改變一個全域性變數的結構
這裡只記錄 npm 匯入的方法, 其他請看書寫宣告檔案
在給一個第三方庫寫宣告檔案之前,先檢視這個庫有沒有宣告檔案。一般來說,npm 包的宣告檔案可能存在於兩個地方:
-
跟 npm 包綁在一起,
package.json
中有type
欄位,或者有index.d.ts
宣告檔案。一般常用的包都有了,自己要釋出 npm 包的時候最好也繫結在一起。 -
釋出到了@types](microsoft.github.io/TypeSearch/
) 裡,一般這種情況是作者沒寫,其他人寫了發上去的。
npm install @types/foo --save-dev
直接通過安裝。
如果都沒有,才自己寫。
宣告檔案存放的位置是有約束的,一般在兩個位置。
-
在
node_modules
建立第三方庫的宣告檔案,但這種一般不採納。一般node_modules
不會隨我們的應用釋出到伺服器|git上。 -
建立一個
types
目錄來寫,要配合tsconfig.json
來使用。
# 專案結構 ├── README.md ├── src |└── index.ts ├── types |└── foo |└── index.d.ts └── tsconfig.json 複製程式碼
- 配置
{ "compilerOptions": { "module": "commonjs", "baseUrl": "./", "paths": { "*" : ["types/*"] } } } 複製程式碼
不管採用了以上兩種方式中的哪一種,我都強烈建議
大家將書寫好的宣告檔案(通過給原作者發 pr,或者直接提交到@types
裡)釋出到開源社群中,享受了這麼多社群的優秀的資源,就應該在力所能及的時候給出一些回饋。只有所有人都參與進來,才能讓 ts 社群更加繁榮。
- export
npm 包寫的宣告檔案declare
不會宣告一個全域性變數,只有匯出的時候才會應用型別宣告。
export const name: string; // 簡單型別 export function getName(): string; // 函式 export class Animal { // class 宣告 constructor(name: string); sayHi(): string; } export interface Options { // 介面 data: any; } // ===> 對應使用到專案中 import { name, getName, Animal, Directions, Options } from 'foo'; let myName = getName(); let cat = new Animal('Tom'); let options: Options = { data: { name: 'foo' } } 複製程式碼
-
混用
declare
export
通過 declare 定義多個變數,一次性匯出
declare const name: string; declare function getName(): string; declare class Animal { constructor(name: string); sayHi(): string; } export { name, getName, Animal, } 複製程式碼
- 匯出預設值
只有function
、class
和interface
可以直接預設匯出,其他的變數需要先定義出來,再預設匯出
針對預設匯出,一般會把匯出語句煩惱歌在宣告檔案的最前面。
export default function foo(): string; export default interface Options { data: any } export default class Person { constructor(name: string); sayHi(): string; } declare const str:string; export default str; 複製程式碼
-
export namespace
namespace
本來是 TS 的模組化方案,隨著 es6越來越屌基本已經不在 ts 中使用了。
但是宣告檔案中還是很常用的,表示變數是一個包含了子屬性的物件 型別。
像是lodash
,它是個物件,但提供了很多子屬性方法如lodash.debunce
如果物件擁有深層的層級,則需要用巢狀的namespace
來宣告深層的屬性的型別:
總的來說,用來匯出一個擁有子屬性的物件。
export namespace obj { const name: string; function fn(a:string,b?:nnumber):void; class Event { say(str:string):void }; // 如果還有包含子屬性的物件,就巢狀 namespace sonObj { const foo: string; } } 複製程式碼
└宣告合併
當一個變數,既是函式又是物件。可以組合多個語句宣告。
export function objAndFn(foo:string):any; export namespace objAndFn { function fn(boo:number):void; const name:string; } 複製程式碼
└針對commonjs
規範
用以下方式來匯出:
// 整體匯出 module.exports = foo; // 單個匯出 exports.bar = bar; 複製程式碼
在 ts 中,針對這種匯出,有多種方式可以匯入,第一種方式是const ... = require
:
// 整體匯入 const foo = require('foo'); // 單個匯入 const bar = require('foo').bar; 複製程式碼
第二種方式是import ... from
,注意針對整體匯出,需要使用import * as
來匯入:
// 整體匯入 import * as foo from 'foo'; // 單個匯入 import { bar } from 'foo'; 複製程式碼
第三種方式是import ... require
,這也是 ts 官方推薦的方式:
// 整體匯入 import foo = require('foo'); // 單個匯入 import bar = require('foo').bar; 複製程式碼
對於commonjs
規範的庫,需要使用export = 變數
的語法寫宣告檔案
準確地講,export =
不僅可以用在宣告檔案中,也可以用在普通的 ts 檔案中。實際上,import ... require
和export =
都是 ts 為了相容 AMD 規範和 commonjs 規範而創立的新語法
export = foo; declare function foo():string; declare namespace foo { const bar: nnumber; } 複製程式碼
:star:️內建物件
TS定義了 js 中內建物件的型別,在TypeScript 核心庫的定義檔案 中。
包括 ECMAscript、DOM、Bom 等
這些內建物件的型別在.ts
中都可以直接使用
let b: Boolean = new Boolean(1); let e: Error = new Error('Error occurred'); let d: Date = new Date(); let r: RegExp = /[a-z]/; let body: HTMLElement = document.body; let allDiv: NodeList = document.querySelectorAll('div'); document.addEventListener('click', function(e: MouseEvent) { // Do something }); 複製程式碼
還會在該內建物件中定位錯誤
Math.pow(10, '2'); // error 必須是兩個 number 型 document.addEventListener('click', function(e) { console.log(e.targetCurrent); // error,MouseEvent 型別不存在 targetCurrent屬性 }); 複製程式碼
內建物件不包含 Node ,如果要使用
npm install @types/node --save-dev 複製程式碼
類
這章是介紹面向物件程式設計
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; console.log(this.greeting); // world } greet() { return "Hello, " + this.greeting; } } let greeter = new Greeter("world"); 複製程式碼
繼承
基本繼承 作為基類的類一般被叫做超類,繼承的類叫做派生類
class Car { name: string = '大眾'; by() { console.log(this.name); } } class Baoma extends Car { name: string = '寶馬' } let bm = new Baoma() bm.by() 複製程式碼
在子類constructor
建構函式中***必須呼叫一下super***,它會執行基類的建構函式
在子類constructor
建構函式呼叫this之前必須先呼叫super
class FClass { name: string = '超類' constructor(name: string) { this.name = name; } setAge(age:number) { console.log(`${this.name} is ${age}`); } } class SClass extends FClass { constructor(name:string) { super(name) } } let f = new SClass('zzc') f.setAge(20) 複製程式碼
公共,私有與受保護的修飾符
public
可以被除自己之外的所以子類訪問到
ts中所有成員預設為public
當一個成員被標記成private
時,無法被外部訪問
如果類的constructor
宣告成了private
那就沒辦法new
和繼承了。protected
受保護的,跟private
基本一樣,但它在子類中可以訪問;
class FClass { name: string = '超類' private constructor(name: string) { this.name = name; } setAge(age:number) { console.log(`${this.name} is ${age}`); } } let f = new FClass() // error class Son extends FClass {} // error 複製程式碼
protected受保護的例子
class F { protected name: string; constructor(name: string) { this.name = name; } setAge(age:number) { console.log(`${this.name} is ${age}`); } } class Son extends F { constructor() { super('son') } getName() { super.name // 可以在子類訪問 } } let f = new F('super class') let s = new Son() console.log(s.name); // 不能在外部訪問 console.log(s.getName()); // 但可以通過子類的方法return出來獲取到 複製程式碼
在類中使用readonly
在類中或者constructor
都還可以更改readonly
的值,但在外部就無法更改了。
class F { readonly a:number = 8 constructor(age:number) { this.a = age } } let f = new F(9) f.a = 10 // error 無法在外部更改 複製程式碼
- 引數屬性
在constructor
用一句話定義並賦值一個屬性
只要在引數前面加了訪問限定符
就可以直接給一個屬性直接賦值readonly
protected
public
private
static
是私有的,所以不能加。
class F { readonly a:number = 8 constructor(readonly b:number) { b = 10 } } let f = new F(9) console.log(f); // {a,b} 複製程式碼
存取器 getter/setters
當一個屬性只有get方法的時候,它就是隻讀的。 這也是一種外部改變靜態屬性的方法
// 當a = 'a' 時,內部的_a才會等於賦的值,否則報錯。 class F { private _a:string; get a():string { return this._a } set a(newA:string) { if(newA === 'a') { this._a = newA } else { this._a = 'error' } } } let f = new F() f.a = 'b' console.log(f); 複製程式碼
靜態屬性 static
它只掛在class本身,而不是通過new例項化後出來的物件
所以你可以通過類.static屬性
來呼叫,但不能用this
class F { static num: number; changeStatic() { F.num = 19; } constructor () { this.changeStatic() console.log(F.num); } } let f = new F(); 複製程式碼
抽象類 & abstract
abstract
用來定義抽象類 和 在抽象類中定義抽象方法的
抽象類就是派生類的一個模板類,一般不會把它例項化,只是給子類繼承用的。
abstract class Animal { // 抽象一個Animal類 abstract makeSound(): void;// 抽象一個方法,必須在子類實現它 move(): void { console.log('roaming the earch...'); } constructor () { } } class Son extends Animal { constructor() { super() } makeSound() { // 必須實現抽象類中的方法 return false } haha() { console.log('error'); } } let s:Animal // 可以指定抽象類為一個型別 s.haha() // 如果上面的聲明瞭,那麼呼叫抽象類中不存在的haha方法是不允許 s = new Animal() // 不可以new 抽象類 s = new Son()//正確 s.makeSound() // 正確 複製程式碼
:star:型別推論
如果沒有明確的指定型別,那麼 TypeScript 會依照型別推論(Type Inference)的規則推斷出一個型別。
let myFavoriteNumber = 'seven'; myFavoriteNumber = 7; // error // 等價於 ==> let myFavoriteNumber: string = 'seven'; myFavoriteNumber = 7; 複製程式碼
如果定義的時候沒有賦值,不管之後有沒有賦值,都會被推斷成 any 型別而完全不被型別檢查:
let myFavoriteNumber; myFavoriteNumber = 'seven'; myFavoriteNumber = 7; 複製程式碼
:star:️聯合型別
表示變數可以是多種型別其中的一種
通過|
分隔
let numOrStr : string | number; numOrStr = 7; numOrStr = '7'; numOrStr = true; // false 複製程式碼
當呼叫聯合型別的方法時,只能呼叫倆型別中共有的方法。
let numOrStr : string | number; numOrStr.length // 報錯length 不是 number 的方法 numOrStr.toString() // 可以 複製程式碼