Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript Handbook - 1 #352

Open
chochinlu opened this issue Jan 10, 2022 · 0 comments
Open

TypeScript Handbook - 1 #352

chochinlu opened this issue Jan 10, 2022 · 0 comments

Comments

@chochinlu
Copy link
Owner

chochinlu commented Jan 10, 2022

https://basarat.gitbook.io/typescript/getting-started/why-typescript

Types are structural

interface Point2D {
    x: number;
    y: number;
}
interface Point3D {
    x: number;
    y: number;
    z: number;
}
var point2D: Point2D = { x: 0, y: 10 }
var point3D: Point3D = { x: 0, y: 10, z: 20 }
function iTakePoint2D(point: Point2D) { /* do something */ }

iTakePoint2D(point2D); // exact match okay
iTakePoint2D(point3D); // extra information okay
iTakePoint2D({ x: 0 }); // Error: missing information `y`

如何撰寫聲明文件

最簡單的方式, 例如 jquery : (window object也可以如法泡製)

declare var $: any;
$('.awesome').show(); // Okay!

官網 TypeScript Handbook

https://www.typescriptlang.org/docs/handbook/2/basic-types.html

Anonymous Functions

匿名函數與函數聲明有點不同。當一個函數出現在 TypeScript 可以確定如何調用它的地方時,該函數的參數會自動被賦予類型。 這個過程稱為上下文類型(contextual typing),因為函數發生的上下文告知它應該具有什麼類型。

Optional properties (使用 ? )

當看到 optional properties, 就要記得處理undefined狀況

function printName(obj: { first: string; last?: string }) {
  if (obj.last !== undefined) {
    console.log(obj.last.toUpperCase()); // OK
  }
 
  // 或是用這樣, A safe alternative using modern JavaScript syntax 
  // 但是會印出undefined
  console.log(obj.last?.toUpperCase());
}

Union Types (使用 | )

兩種以上的type就可以合成一個 union type

TypeScript 僅允許您使用該聯合執行操作,前提是該操作對聯合的每個成員都有效。

例如你有 string | number 這個 union type, 那麼使用 toUpperCase() 就會產生錯誤

function printId(id: number | string) {
  console.log(id.toUpperCase());
}

就會很明白告知:

Property 'toUpperCase' does not exist on type 'string | number'.
  Property 'toUpperCase' does not exist on type 'number'.

解決方法變成也是要針對特定的type去處理(narrowing) --> 使用 typeof , 或是 Array,.isArray

Type Aliases

當然可以直接用 union types 或是直接宣告 object type內容, 但是通常我們會給一個名字

A type alias is exactly that - a name for any type.

type Point = {
    x: number;
    y: number;
};

function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 120 });

或是給union type一個 Alias:

type ID = number | string ;

alias 就只是個別名, 不是該type的不同版本

所以以下這樣也是沒問題的:

type someString = string;

function input(str: string): someString {
    return str
}

let userInput = input('sdsdsdsd')

userInput = 'new input'

someString 和 string 是完全一樣的東西

Interface

你也可以用 interface 來為 object type 命名:

interface Point {
  x: number;
  y: number;
}

 
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });

跟使用 type alias的結果是一樣的, 這樣就很像我們有一個 anonymous object type.

TypeScript 只關心 傳到 prinCord的 值的結構

只關心 type的結構和功能是我們稱 TypeScript 為結構類型類型系統(structurally typed type system)的原因。

Type Aliases 和 interfaces 的區別

兩個非常相似, 很多情況下都可以自由選擇,

關鍵差別在於 type 不可以擴展, 但 interface可以

extending an interface:

interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

const bear: Bear = {
  name: 'cccc',
  honey: true
}
        

$ 來extending a type:

type Animal = {
  name: string
}

type Bear = Animal  & {
  honey: boolean
}

const bear: Bear = {
  name: 'cccc',
  honey: true
}

只有interface可以在現有的interface加新的fields:

interface Something {
    title: string
}

interface Something {
    ts: number
}

const src: Something = {
    title: 'sdasdasdas',
    ts: 11000
}

優先用interface

Type Assertions (使用 as )

有時候你會得到關於 TypeScript 無法知道的值類型的信息。

例如你使用 document.getElementById, TypeScript 只會知道會回傳某種 HTMLElement, 但是你很明確知道你只會收到 HTMLCanvasElement,

那麼就可以使用 type assertion 來指定更明確的type :

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

除了 tsx 檔案以外, 也可以用以下方式寫 type assertion :

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

提醒:因為類型斷言在編譯時被刪除,所以沒有與類型斷言關聯的運行時(runtime)檢查。如果類型斷言錯誤,不會產生異常或 null

Literal Types

就像 JS 的 letconst 的差別

let 是可改的, 那對於 TypeScript來說, let changingString = "Hello World" 的型別宣告就會是 let changingString: string

const 就是一旦宣告後就不可改變, const constantString = "Hello World"; 的型別宣告就會是 const constantString: "Hello World"

有些時候像你只指名 string 這樣的type是不夠的,

宣告特定的literal 組合成 union type就變得很有用

例如:

function printText(s: string, alignment: 'left' | 'right' | 'center') {
    console.log(s, ' ', alignment)
}

printText('sadasdsad', 'right')
printText('sadasdasd', 'dddd') // 會告知錯誤

當然也可以設計 Numeric literal types :

function compare(a: string, b: string): -1 | 0 | 1 {
  return a === b ? 0 : a > b ? 1 : -1;
}

也可以 non-literal 和 literal types 混合:

interface Options {
    width: number;
}

function configure(x: Options | 'auto') {
    //. ...
}

configure({width: 100})
configure('auto')
configure('automatic') // error

boolean 事實上就是 true | false 這個 literal type 的 alias

null 以及 undefined

strictNullChecks off : 可能為 null 或 undefined 的值仍然可以正常訪問,並且 null 和 undefined 值可以分配給任何類型的屬性。 建議都開啟

strictNullChecks on: 在對該值使用方法或屬性之前,您需要測試這些值(明確寫出支援 null 或是 undefined)

function doSomething(x: string) {
    console.log(x.toLowerCase())
}

// Argument of type 'null' is not assignable to parameter of type 'string'
doSomething(null) 

需要改成:

function doSomething(x: string | null) {
    if (x === null) {
       // ...
    } else {
       console.log(x.toLowerCase())
    }
}

doSomething(null)

強制type assertion 為非null或undefined (使用 !)

例如:

function doSomething(x: number | null) {
    console.log(x.toFixed())
}

x 會被警告說 Object is possibly 'null'

如果你很確定 x 不會是 null 或是 undefined , 那麼加上 type assertion ! :

function doSomething(x: number | null) {
    console.log(x!.toFixed())
}

一樣, type assertion 會在 runtime的時候被移除, 除非你很確定, 不然就無法檢查 null 或 undefined了

enum

enum編譯出來的結果是雙向的object :

enum UserResponse {
  No = 0,
  Yes = 1,
}

console.log(UserResponse.No)

會編譯成:

"use strict";
var UserResponse;
(function (UserResponse) {
    UserResponse[UserResponse["No"] = 0] = "No";
    UserResponse[UserResponse["Yes"] = 1] = "Yes";
})(UserResponse || (UserResponse = {}));
console.log(UserResponse.No);

如果你加上 const enum :

const enum UserResponse {
  No = 0,
  Yes = 1,
}

console.log(UserResponse.No)

那就不會編譯出object, 只會編譯用到的結果:

"use strict";
console.log(0 /* No */);

與大多數 TypeScript 功能不同,這不是對 JavaScript 的類型級添加,而是添加到語言和運行時(runtime)的東西。

正因為如此,這是一個你應該知道存在的功能,但除非你確定,否則可能會推遲使用。

More 不推薦使用 enum, 改用其他替代方案: https://www.typescriptlang.org/docs/handbook/enums.html

Enum VS Object

在現代 TypeScript 中,當一個帶有 as const 的對象就足夠時,你可能不需要枚舉

const enum EDirection {
  Up,
  Down,
  Left,
  Right,
}
 
const ODirection = {
  Up: 0,
  Down: 1,
  Left: 2,
  Right: 3,
} as const;

// Using the enum as a parameter
function walk(dir: EDirection) {}
 
// It requires an extra line to pull out the values 如果使用object as const當參數, 那有可能要多這額外行數
type Direction = typeof ODirection[keyof typeof ODirection];
function run(dir: Direction) {}

as const

https://cumsum.wordpress.com/2021/08/28/typescript-a-spread-argument-must-either-have-a-tuple-type-or-be-passed-to-a-rest-parameter/

const args = [8, 5]

const angle = Math.atan2(...args) // atan2需要兩個參數

console.log(angle)

這樣會收到抱怨: A spread argument must either have a tuple type or be passed to a rest parameter.

原因是function parameters數量是固定的, 但是 array不是固定的有可能會發生變化

有幾種解法, 第一種就是明確指定 type為 tuple:

const args: [number, number] = [8, 5]

另外一種, 直接指定為 read only type : as const :

const args = [8, 5] as const

You can use as const to convert the entire object to be type literals

as const 後綴就像是 const 但是是給 type system用的, 確保所有屬性為literal type, 而不是更通用的版本例如string或是number

Literal Interface (as const 另外一個例子)

const req = { url: "https://example.com", method: "GET" };

handleRequest(req.url, req.method);

會報錯說 Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

因為 req.method type為 string , 有可能是 GET 或是 POST 其他的字串,

所以解法可以用 type assertion 將 method斷言為 GET type:

const req =  { url: "https://example.com", method: "GET" as "GET" };

或是乾脆宣告整個object為 type literal (read only):

const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
@chochinlu chochinlu changed the title TypeScript Deep Dive TypeScript Handbook Jan 11, 2022
@chochinlu chochinlu changed the title TypeScript Handbook TypeScript Handbook - 1 Jan 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant