2.TypeScript 基礎入門(二)

程式碼寫著寫著就懂了發表於2019-01-16

變數型別的那些事

1.基本註解

型別註解使用:TypeAnnotation 語法。型別宣告空間中可用的任何內容都可以用作型別註解。

const num: number = 123;
function identity(num:number):number{
    return num;
}

複製程式碼

加入註解以後會報錯的寫法:

const num: number = 123;
function identity(num: number): number {
  const num1 = '123'
  // 返回的不是 number  報錯
  return num1;
}
const num1 = '123' 
// 引數不是 number 報錯
identity(num1)
複製程式碼

2. 原始型別

Javascript原始型別也同樣適用於 TypeScript的型別系統。因此,string, number,boolean也可以被用作型別註解:

 let num: number;
 let str: string;
 let bool: boolean;

 num = 123;
 num = 123.45;
 num = '123'; //Type '"123"' is not assignable to type 'number'

 str = '123';
 str = 123; //Type '123' is not assignable to type 'string'

 bool = true;
 bool = false;
 bool = 'false';//Type '"false"' is not assignable to type 'boolean'.
複製程式碼

3. 陣列

TypeScript 為陣列提供了專用的型別語法,因此你可以很輕易的註解陣列。它使用字尾[],接著你可以根據需要補充任何有效的型別註解(如:boolean[])。它能讓你安全的使用任何有關陣列的操作,而且它能放置一些類似賦值錯誤型別給成員的行為。

你有兩種定義陣列的方式:

第一種,你可以在元素型別後面接上[],表示由此型別元素組成的一個陣列:

let list:number[] = [1,2,3]
複製程式碼

第二種方式是使用陣列泛型,Array<元素型別>:

let list: Array<number> = [1,2,3]
複製程式碼

一個具體的栗子:

const boolArray: boolean[];

boolArray = [true, false];
console.log(boolArray[0]); // true
console.log(boolArray.length); // 2

boolArray[1] = true;
boolArray = [false, false];

boolArray[0] = 'false'; // Error
boolArray = 'false'; // Error
boolArray = [true, 'false']; // Error
複製程式碼

4.元組

元組型別允許表示一個已知元素數量和型別的陣列,各元素的型別不必相同。比如,你可以定義一對值分別為stringnumber型別的元組。

// 宣告一個元組型別
let x: [string, number];
// 初始化賦值
x = ['hello', 10]; // OK
// 初始化錯誤
x = [10, 'hello']; // Error
複製程式碼

當訪問一個已知索引的元素,會得到正確的型別:

console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
複製程式碼

????????? 當訪問一個越界的元素,會使用聯合型別替代:

x[3] = 'world'; // OK, 字串可以賦值給(string|number)型別
複製程式碼

但是我在編輯器裡使用的時候是報錯的,Index '2' is out-of-bounds in tuple of length 2,我也不知道為什麼??

5. 列舉

enum 型別是對JavaScript 標準資料型別的一個補充。使用列舉型別可以為一組數值賦予友好的名字。

enum Color {Red, Green, Blue}
let c: Color = Color.Green;
複製程式碼

預設情況下,從0開始為元素編號。你也可以手動的指定成員的編號。例如,我們將上面的栗子改成從1開始編號:

enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;
複製程式碼

或者採用全部手動賦值:

enum Color {Red = 1,Geeen = 2, Blue = 4}
let c: Color = Color.Green
複製程式碼

編譯後的js

var Color;
(function (Color) {
    Color[Color["Red"] = 1] = "Red";
    Color[Color["Green"] = 2] = "Green";
    Color[Color["Blue"] = 4] = "Blue";
})(Color || (Color = {}));
var c = Color.Green;
複製程式碼

所有的表示式都有返回值,它的返回值就是等號右邊的賦值。

列舉型別提供的一個遍歷是你可以由列舉的值得到它的名字。 例如,我們知道數值為2,但是不確定它對映到Color裡的哪個名字,我們可以查詢相應的名字:

enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];

console.log(colorName);  // 顯示'Green'因為上面程式碼裡它的值是2
複製程式碼

這個試了一下不給數值number,發現沒辦法通過這個取值

enum Color {Red = 'r', Green = 'g', Blue = 'b'}
let c: Color = Color.Green;
console.log(c) // g
let colorName:string = Color[2]
console.log(colorName) // undefined
複製程式碼

我們看一下編譯後的javascript程式碼就明白了:

var Color;
(function (Color) {
   Color["Red"] = "r";
   Color["Green"] = "g";
   Color["Blue"] = "b";
})(Color || (Color = {}));
var c = Color.Green;
console.log(c);
var colorName = Color[2];
console.log(colorName);
複製程式碼

6. 特殊型別

6.1 any

any型別在 TypeScript型別系統中佔有特殊的地位。它提供給你一個型別系統的【後門】,TypeScript將會把型別檢查關閉。在型別系統裡any能夠相容所有的型別(包括它自己)。因此,所有的型別都能夠被賦值給它。它也能被賦值給其他任何型別。

let power: any;
//賦值任意型別
power = '123'
power = 123
let num:number;
num = power;
power = num;
複製程式碼

6.2 null 和 undefined

在型別系統中,JavaScript 中的 null 和 undefined 字面量和其他被標註了 any 型別的變數一樣,都能被賦值給任意型別的變數,如下例子所示:

let num: numer;
let str: string;

// 這些型別能被賦予
num = null;
str = undefined;
複製程式碼

6.3 void

使用 :void來表示一個函式沒有一個返回值

function log(message:string):void{
    console.log(message)
}
複製程式碼

宣告一個void型別的變數沒有什麼大用,因為你只能為它賦予undefinednull

let unusable:void = undefined
複製程式碼

7.泛型

軟體工程中,我們不僅要建立一致的定義良好的API,同時也要考慮可重用性。

不僅能夠支援當前的資料型別,同時也能支援未來的資料型別,這在建立大型系統時為你提供了十分靈活的功能。

舉一個簡單的演變例子:

當不實用泛型的時候,你的程式碼可能是像這樣:

function identity(arg:number):number{
    return arg;
}
複製程式碼

這個函式可以接收number 型別,返回的也是number型別,考慮到這個函式的可重用性,或者,我們可以使用any型別來定義函式像這樣:

function identity(arg:any):any{
    return arg;
}
複製程式碼

使用any型別會導致這個函式可以接受任何型別的arg引數,且任何型別的值都可能被返回。

我們需要一種方法使返回值的型別與傳入引數的型別是相同的。 接下來我們可以把程式碼像下面這樣:

function identity<T>(arg:T):T{
    return arg
}
複製程式碼

我們給 identity 新增了型別變數 T。 如果傳入的型別(比如:number),我們就可以知道返回的型別也是number。現在我們就可以知道引數型別與返回值型別是相同的了。這有助於我們跟蹤函式裡使用的型別的資訊。

我們把這個版本的identity函式叫做 泛型。 它可以使用與多個型別,不同於使用any,的不確定性,還能保持像第一個例子一樣的準確性引數型別返回值型別相同的。

7.1 泛型的使用

我們定義了泛型函式之火,可以使用兩種方法使用。

第一種是傳入所有的引數,包含型別引數:

let output = identity<string>('myString');//type of output will be 'string'
複製程式碼

這裡我們明確的指定了Tstring型別,並做為一個引數傳給函式,並且確定output的型別是string.

注意⚠️:使用<>不是()

第二種方法,利用型別推論--即編譯器會根據傳入的引數自動的幫助我們確定T的型別。

let output = identity("myString");// type of output will be 'string'
複製程式碼

這裡並沒有使用(<>)來明確地傳入型別;編譯器可以檢視myString的值,然後把T設定為它的型別。

7.2 使用泛型變數

使用泛型建立像identity這樣的泛型函式時,編譯器要求你在函式體必須正確的使用這個通用的型別。換句話說,你必須把這些引數當作時任意或所有型別。

看下我們之前寫的泛型例子:

function identity<T>(arg:T):T{
    return arg
}
複製程式碼

當我們試圖在函式內輸出arg.length的時候,編譯器就會報錯型別“T”上不存在屬性“length”

function idenfity<T>(arg:T):T{
 console.log(arg.length) // Error: T doesn't have .length
 return arg
}
複製程式碼

注意⚠️:

型別變數T代表的時任意型別,所以使用這個函式的人可能傳入的是個數字,而數字時沒有.length屬性的。

然後我們假設想操作T型別的陣列而不直接是T,由於我們操作的是陣列,此時.length的屬性應該是存在。

程式碼像這樣:

function loggingIdentity<T>(arg: T[]):T[]{
console.log(arg.length) // Array has a .length,so no more error
return arg
}
複製程式碼

我們可以這樣理解loggingIdentity的型別:泛型函式loggingIdentity,接收型別引數T和引數arg,它是個元素型別是T的陣列,並返回元素型別T的陣列。如果我們傳入數字陣列,將返回一個數字陣列,因為此時T的型別為number。這可以讓我們把泛型變數T當作型別的一部分使用,而不是整個型別,增加了靈活性。

我們還可以這樣實現上面的例子:

function loggingIdentity<T>(arg:Array<T>):Array<T>{
console.log(arg.length)
return arg
}
複製程式碼

在電腦科學中,許多演算法和資料結構並不會依賴於物件的實際型別。然而,你仍然會想在每個變數裡強制提供約束。

例如:在一個函式中,它接受一個列表,並且返回這個列表的反向排序,這裡的約束是指傳入至函式的引數與函式的返回值:

function reverse<T>(items:T[]):T[]{
  const toreturn = []
  for(let i = items.length - 1;i>=0;i--){
    toreturn.push(items[i])
  }
  return toreturn
}

const sample = [1,2,3]
let reversed = reverse(sample)

console.log(reversed)

reversed[0] = '1'; // Error
reversed = ['1', '2']; // Error

reversed[0] = 1; // ok
reversed = [1, 2]; // ok
複製程式碼

這個栗子中,函式reverse接受一個型別為T的陣列items:T[],返回值為型別T的一個陣列(注意:T[]),函式 reverse的返回值型別與它接受的引數型別一樣。當你傳入var sample = [1, 2, 3]時,TypeScript能推斷出 reversenumber[]型別,從而能給你型別安全。於此相似,當你傳遞一個string[]型別的陣列時,TypeScript能推斷出為string[]型別, 如:

const strArr = ['1','2']
let reversedStrs = reverse(strArr)
reversedStrs = [1, 2]; // Error
複製程式碼

如果你的陣列是const sample = [1,false,3]等同於const sample: (number | boolean)[],所以下面的程式碼也能可以的,如果是陣列裡有兩種甚至三種型別的時候,它是能夠推斷出(number | boolean)這種或的型別的。也就是下面所說的聯合型別。

function reverse<T>(items:T[]):T[]{
  const toreturn = []
  for(let i = items.length - 1;i>=0;i--){
    toreturn.push(items[i])
  }
  return toreturn
}

const sample = [1,false,3]
let reversed = reverse(sample)

console.log(reversed)

reversed[0] = true; // OK
reversed = [3, true]; // OK

reversed[0] = 1; // ok
reversed = [1, 2]; // ok
複製程式碼

8.聯合型別

聯合型別表示取值可以為多種型別中的一種。

8.1簡單的例子

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven'
myFavoriteNumber = 7
複製程式碼
let myFavoriteNumber: string | number;
myFavoriteNumber = true;

// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
//   Type 'boolean' is not assignable to type 'number'

複製程式碼

聯合型別使用|分隔每個型別。

這裡的let myFavoriteNumber: string | number;的含義是,允許myFavoriteNumber的型別是string或者number,但是不能是其他型別。

8.2 訪問聯合型別的屬性或方法

TypeScript 不確定一個聯合型別的變數到底是哪個型別的時候,我們只能訪問此聯合型別的所有型別裡的共有的屬性或方法

function getLength(something:string|number):number{
return something.length
}
// Error 型別“string | number”上不存在屬性“length”。
  型別“number”上不存在屬性“length”。
複製程式碼

上例中,length不是stringnumber的共有屬性,所以會報錯。

訪問stringnumber的共有屬性是沒問題的:

function getLength(something:string|number):string{
    return something.toString()
}
複製程式碼

聯合型別的變數在被賦值的時候,會根據型別推論的規則推斷出一個型別:

let myFavoriteNumber:string|number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length)

myFavoriteNumber = 7
console.log(myFavoriteNumber.length) // Rrror 型別“number”上不存在屬性“length”。

複製程式碼

上例中,第二行的myFavoriteNumber被推斷成了string,訪問它的length屬性不會報錯。而第四行的myFavoriteNumber被推斷成了number,訪問它的length屬性時就報錯了。

一個常見的用例是一個可以接受單個物件或者物件陣列的函式:

function formatCommandline(command: string[] | string) {
  let line = '';
  if (typeof command === 'string') {
    line = command.trim();
  } else {
    line = command.join(' ').trim();
  }

  // Do stuff with line: string
}
複製程式碼

相關文章