Redux 中的 compose 的 source code
1 | /** |
若是沒有參數的話就回傳一個預設的函式
若是只有一個則直接回傳 function
聊到 reduce
前 可以先談談 遞回
1 | /** |
若是沒有參數的話就回傳一個預設的函式
若是只有一個則直接回傳 function
聊到 reduce
前 可以先談談 遞回
React Native version: 0.51.0
React version: 16.0.0
1 | $ react-native init myapp |
安裝 react-native-router-flux
和 styled-components
1 | $ npm install styled-components react-native-router-flux |
建立資料夾 和 檔案
1 | $ mkdir -p src ./src/components |
./src/components/Home.js
1 | import React, { Component } from "react"; |
./src/components/Counter.js
1 | import React, { Component } from "react"; |
./App.js
1 | import React, { Component } from "react"; |
1 | $ npm run run-ios //ok |
import type {
^^^^^^
SyntaxError: Unexpected token import
在package.json 中增加 transform
package.json
1 | { |
在重新執行測試的時候會產生另一個錯誤
({"Object.
因為這是在 package.jsonreact-native-router-flux
中的 Navbar 中的 png 在編譯的時候產生的錯誤1
$ npm install --save-dev identity-obj-proxy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{
...
"jest": {
"preset": "react-native",
"transform": {
"^.+\\.jsx?$": "babel-jest"
},
"transformIgnorePatterns": [
"!node_modules/react-runtime"
],
"moduleNameMapper": {
".+\\.(png|jpg|ttf|woff|woff2)$": "identity-obj-proxy"
}
}
}
JSX
是一種類似 XML 的標記性語言,可以被轉換為合法的 Javascript 因為 React
的框架而開始流行,但是也可以,但是也可以使用在其他程式中
在 TypeScript
使用 JSX
必須先做兩件事情
tsx
jsx
的功能TypeScript
有三種 JSX
的模式, preserve
, react
, react-native
會保留 JSX
提供後續轉換使用
會生成 React.createElement
在使用前不需要再轉換
相當於 preserve
但是輸出的檔案副檔名為 .js
可以在命令列中使用 --jsx
或是在 tsconfig.json
中指定模式
寫一個 class
1 | const foo = <foo>bar; |
因為 JSX
語法解析困難,所以在 TypeScript
禁止使用 <>
來宣告,所以在 tsx
中改為
1 | const foo = bar as foo; |
as
在 .ts
或是 .tsx
中都可以使用
為了理解 JSX
如何檢查類型必須要先了解原生的元件根基於值得元件有什麼不同
假如有一個元件 <expr />
可能會引用 div
或是 span
這類的標籤
React
HTML 標籤會自動生成 React.createElement('div')
但是自定義的元件部會生成 React.createElement(MyComponent)
tag
本身支援類型檢查,但是自定義的元件則需要自己定義類型檢查TypeScript
使用和 React
相同的規範來做區別
Intrinsic elements
預設是 JSX.IntrinsicElements
做類型檢查,預設是 any
1 | decalre namespace JSX { |
上面範例中 foo
可以執行,但是 bar
會報錯誤訊息,因為 bar
並沒有在 JSX.IntrinsicElements
內指定
也可以指定為所有
1 | declare namespace JSX { |
一般使用 Component
如範例
1 | import MyComponent from './MyComponent'; |
範例
1 | interface FooProp{ |
因為 SFC
是簡單的 Function 所以可以盡量的使用
這裡需要先介紹兩個新的名詞 the element class type
和 element instance type
範例
1 | class MyComponent { |
element instance type
很有趣,他必須要指定給 JSX.ElementClass
否則就會報錯
預設 JSX.ElementClass
是 {}
1 | declare namespace JSX { |
1 | function identity(arg: number): number { |
或是我們用 any
來宣告型態
1 | function identity(arg: any): any { |
使用 any
導致這個函式可以接受任何類型的 arg 參數,但是這樣會錯失一些訊息
我們需要一種方式讓 input 和 output 類型是一樣的
1 | function identity<T>(arg: T): T{ |
藉由前面輸入的 T 協助接到 input 也希望和 response 是相同的,是相同的
1 | function identity<T>(arg: T): T{ |
若是我們希望取回 arg 的長度但是並沒有指明 arg 具有 .length
這個屬性,
因為這個變量是任意類型,所以傳入的可能是字串或數字
那就沒有 length
這個屬性,我們可以先宣告陣列
1 | function loggingIdentity<T>(arg: T[]): T[]{ |
現在 loggingIdentity
的輸入直是包含了 T
屬性的陣列,回傳的也是包含了 T
的陣列
也可以改寫成為
1 | function loggingIdentity<T>(arg: Array<T>): Array<T> { |
在函式忠宣告泛型的方式如下
1 | function identity<T>(arg: T): T{ |
也可以使用不同的名稱宣告,只要變數的數量吻合就可以了
1 | function identity<T>(arg: T): T { |
也可以利用 signature
物件的方式來做宣告
1 | function identity<T>(arg: T): T { |
上述範例可以利用 interface
來做宣告
1 | interface GenericIdentityFn{ |
藉由上述範例使用泛型來做宣告的話,就可以把這個參數的型態作為函式的輸入值
例如: Dictionary
1 | interface GenericIdentityFn<T> { |
泛型類別和泛型宣告方式是一樣的,利用 <>
在 class 後面
1 | class GenericNumber<T>{ |
GenericNumber
很片面的 class,沒什麼去限制他使用 number
你可以使用字串或其他更複雜的物件
1 | let stringNumeric = new GenericNumber<string>(); |
就像 interface
一樣,泛型類別只是確認你使用的是同一個型態,而什麼型態則不是他們限制的
在之前 類別中有提到,類別有分兩個部分 靜態與實例層,泛型類別只負責實例層
靜態層不能使用 泛型類別
之前有一個簡單的範例,如果我們傳入的類別沒有 length
這個屬性就會報錯
1 | function loggingIdentity<T>(arg: T): T { |
我們不希望使用 any
來做檢查,因為這樣會沒有任何錯誤訊息,希望在傳入的屬性中一定要有一個 length
為了達到這個目的,我們宣告一個 interface
裡面有 length
的屬性,然後繼承他的屬性
1 | interface Lengthwise{ |
這時候他就不是 any
參數,而是必須擁有 length
這個屬性
Function
在 Javascript 中是很基本的型態,可以用在隱藏資訊,或攥寫模組等功用
Javascript 中基本的 Function
有兩種
1 | function add(x, y){ |
在 Javascript 中的 Function
可以使用外部的變數,這個行為叫做 capture
1 | let z = 100; |
使用 TypeScript
寫一個最基本的範例
1 | function add (x: number, y: number): number{ |
完整的 Function
Type 範例
1 | let myAdd: (x: number, y: number) => number = function(x: number, y: number): number{ |
上述範例中輸入的參數可以定義類別,同時也宣告 Function
回傳的類別,並且有兩個相同的宣告類別,
但是做事在一開始就宣告完整的 Function
類別,那之後就可以省略
範例
1 | let myAdd = function(x: number, y: number): number{ |
在 TypeScript
中指定每個參數不代表他們不能是 null
或是 undefined
1 | function buildName(firstName: string, lastName: string){ |
在 Javascript 中每一個 Function
都是非必要的,當你沒有輸入的時候值都會是 undefined
在 TypeScript
有宣告的都是必要的,但是也提供一個 ?
來宣告非必要
1 | function buildName(firstName: string, lastName?: string){ |
TypeScript
非必要參數必須要在 必要參數的後面, TypeScript
也可以提供預設值的設定
範例
1 | function buildName(firstName: string, lastName = "Smith") { |
上面的範例中可以看到,你可以先預設值給予預設值之後就不是必要參數,因為當你沒有輸入該參數的時候也會有預設輸入不會影響程式執行
在 ES6
也有一種特性 Rest
而這個特性在 TypeScript
也可以應用在 Function
之中,當你不知道之後輸入的參數值總共有幾個,可以利用這個特性將所有後面輸入的參數值組合成一個陣列
1 | function buildName4(firstName: string, ...restOfName: string[]) { |
1 | let deck = { |
上述範例是可以編譯的,但是在執行的時候會有錯誤
因為在執行照 createCadrPicker
的時候會找不到 suits
這個 Function
因為這是 this的作用域的問題
因為對 Javascript 來說 Function
也是物件,所以在上述範例中的 createCadrPicker
中的 this 是指這個 createCadrPicker
Function 本身,但是這個 Function 並沒有 suits
這個屬性,所以他會找到 undefeind
在後面 this.suits[pickedSuit]
的時候因為 undefined
在 Javascript 並不是物件,所以就會造成這個錯誤
所以我們可以把這個範例做一些修改
1 | let deck = { |
其實我們做的修改只是將 function(){}
修改為 () => {}
但是因為在 Javascript 中的 arrow function的特性 所以他找到的 this
是指 desk 這個物件,就可以指導 suits
這個屬性這個屬性
但是在上面範例中的 this 的型別依舊是 any
如果我們希望在 Function
定義 this
的型態就要將這個宣告放在 Function
的第一個參數
1 | function f(this: void){ |
加上兩個 interface
, Card
和 Deck
1 | interface Card{ |
在 interface
中的 createCardPicker
有宣告了 this 的型態是 Deck
而不是 any
所以 --noImplicitThis
不會有錯誤
你在 callback
中使用 this
的話依舊會產生一些錯誤,因為 this
會是 undefine
你可以宣告一個 interface
來避免這種錯誤
1 | interface UIElement{ |
this: void
代表 addClickListener
預計 onClick
是一個 Fucntion
並沒有 this
的類別
1 | class Handler { |
Javascript 是一個動態繼承的程式語言,一個函式藉由輸入值得到不同的回傳值是十分常見的
1 | let suits = ["hearts", "spades", "clubs", "diamonds"]; |
pickCard
會依據我們傳進去的參數不同,回傳不同的資訊這樣的話我們該如何去定義呢?
1 | let suits02 = ["hearts", "spades", "clubs", "diamonds"]; |
為了要讓 TypeScript
編譯的時候能夠選擇正確的型態,會宣告兩個不同的 PickCard1
然後分別宣告不同的 parmeter 而產生的不同的 response,然後在最後真正宣告 function pickCard1():any
設定回傳值是 any
,之後再真正使用 pickCard1
的時候就會依據不同的 parmeter 檢查不同的形態和 response
傳統的 Javascript 使用 function 加上 protyotype-based
來繼承建立元件,但是這樣的機制對程式開發者習慣 Object-oriented
的感覺很尷尬, ECMAScript 2015
與 ECMAScript6
中允許開發者使用 object-oriented class-based approach
最基本的 class-based 範例
1 | class Greeter{ |
這樣的程式對 C# 或是 Java 的開發者應該會比較親切,
宣告了一個新的 class Greeter
,這個 class 中有一個屬性是 greeting
constructor 和 greet
看到有一個關鍵字 this.
之後可以呼叫這個 class 的屬性
在 TypeScript
中可以直接使用 object-oriented patterns
,當然也可以建立一個 class 做繼承的動作,
範例
1 | class Animnal { |
上述範例中以 extends
這個關鍵字來建立一個子類, Horse
和 Snake
是繼承在 class Animal
之下的子類
在子類中的 constructor 必須使用 super()
這將會執行父類的 constructor
這個範例也示範了如何覆寫父類的 Function
在 Snake
和 Horse
都有建立一個 move
的 Function
來覆寫過 Animal
的 move
執行結果後如下
1 | Slithering... |
在我們的範例中可以自由地宣告屬性,但在其他語言(C#) 需要使用 public
這個關鍵字來規範屬性是不是可以被瀏覽
但是在 TypeScript
中 public
是預設值
但是你也可以使用 public
來宣告屬性
1 | class Animal{ |
當某個屬性使用 private
來宣告,他不能來宣告,他不能被直接呼叫
1 | class Animal{ |
TypeScript
是一個結構型態系統,我們比較兩種不同的類別,不論他們是如何產生的
只要他們的所有屬性沒有衝突,我們就可以稱這兩個類別是相容的
1 | class Animal { |
然而當兩個類別在比較的時候如果擁有 private
和 protected
屬性, 除非他們所這個 private
和 protected
繼承的是同一個父類別才會是兼容的,否則在形態上兩個都會是不同的
1 | class Animal{ |
在這個範例中我們有 Animal
和 Rhino
兩個 class, Rhino
是 Animal
的子類別
另外也有一個 Employee
他看起來和 Animal
十分相似,都有一個 private name: string
因為 Rhino
是繼承 Animal
所以 Animal
實體化後可以 assign 給 Rhino
的實體並不衝突
代表他們是相容的,而 Employee
即使有一樣的 private name: string
但是卻無法相容,
因為他們並不是在同一個父類的類別
protected
和 private
很相似,只是當你宣告為 protected
1 | class Person { |
我們沒辦法直接呼叫 name
但是可以透過 Employee
instance method 來使用,因為 Employee
繼承自 Person
我們也可以將 constructor
宣告為 protected
這代表這個 class 只能用來繼承,而無法直接產生 instance
1 | class Person { |
你可以宣告某些參數或變數是 readonly
使用 readonly
這個關鍵字來宣告,但是必須在初始化或是在 constructor 的時候進行宣告
1 | class Octopus{ |
TypeScript
支援 getters/setters 去對 Object
中的屬性進行取值或是修改
1 | class Employee{ |
我們希望使用者是有足夠的安全性,所以使用 private
宣告 fullName
然後允許使用 set
來對 fullName
來做修改
1 | let passcode = 'secret passcode'; |
command line
1 | $ tsc -t ES5 ./Accessors.ts |
有兩點需要注意
ECMAScript 5
以上才可以使用 Accessors
get
而沒有設定 set
代表這個屬性是 readonly
在這個部分我們討論的是實體的屬性,也是靜態屬性,實體的屬性,也是靜態屬性,這個屬性只能在 class 中取得,而無法被繼承
1 | class Grid { |
抽象類別就像之前的類別一樣,也許不需要實體化,使用 abstract
關鍵字來宣告抽象類別與抽象函式
1 | abstract class Animal{ |
abstract
中的函式並不會並不會包含在實體,也一定會使用 abstract
關鍵字來做宣告定義
1 | abstract class Department{ |
抽象類別無法直接使用 new
產生物件,若是在抽象類別中並沒有宣告的類別與屬性,其子類別即使寫了也無法使用
在 TypeScript
中宣告一個 class 的時候,其實你已經同時執行了多個宣告
1 | class Greeter { |
上述範例中當 let greeter: Greeter
我們將會使用 Greeter
類別的 instance
賦予 class Greeter
當我們使用 new
這個關鍵字來實體化的時候,便會執行 constructor 轉譯之後的結果如下
1 | let Greeter = (function () { |
在 let Greeter
就會準備指定給 constructor,而看到接下來的 new
關鍵字並且開始執行 constructor 就會取得一個藉由 Gretter
這個函式實體化的一個結果
在修改一下上面的範例
1 | class Greeter { |
在這個範例中我們在 Greeter
宣告了一個靜態的屬性 standardGreeting
並且給予值 Hello, there
第一步驟跟之前的範例一樣,利用 Greeter
產生了一個物件是 greeter1
然後將他的類別 assign 給 greeterMaker
並且修改了他的 standardGreeting
為 Hey there!
之後再由 greeterMaker
產生一個 greeter2 當它的 greet()
執行的時候產生的字串卻是 Hey there!
而且此時我再次執行 greeter1.greet()
的時候得到的卻也是 Hey there!
也就是當我們 可以利用 這樣的方式統一管理一個靜態屬性也會互相繼承靜態屬性
TypeScript
有一種類型宣告方式,有時候這個模式叫做 duck typing
或是 structural subtyping
, 或統稱為 interface
最簡單的的 interface
1 | function printLabel(labelledObj: {label: string}){ |
呼叫 printLabel
的時候會進行 type-check,而在 printLabel
中就有參數檢查, label
必須是 string
, 實際上可能有更多的屬性,不只是 label
, 檢查只會檢查 label
屬性是不是字串,有些狀況 TypeScript
並不寬鬆,之後會慢慢做解釋
依據上面的範例可以使用 interface
指定 label
為必要參數
1 | interface LabelledValue{ |
LabelledValue
是我們可以描述參數必要性的範例,代表輸入值必須要有一個 label
變數型態為字串,我們並不需要非常明確的指定 printLabel
這個 Function 的輸入參數,只要符合這個 interface
就會允許使用
也可以定義不一定會存在的參數
1 | interface Squareconfig{ |
有些 properties 應該只能被修改,無法整個被覆寫
1 | interface Point { |
也可以定義一個唯讀的陣列
1 | let a: number[] = [1, 2, 3, 4]; |
最後一行中,當你定義為普通 ReadArray
要 assign 給一個 Array
是不允許的
const
只是禁止你的物件被覆寫,而 readonly
則是設定你的物件中的參數被覆寫
在第一個範例中,雖然我們寫了一個 interface
是 {size: number, label: string}
但是我們真正有使用的只有 {label: string}
, 我們在剛剛也有提到 optional properties
或是稱為 option bags
但是這兩個一起使用的話也有可能產生一些問題
1 | interface SquareConfig{ |
上述範例中 creteSquare
中的 colour
拼錯了,正確應該是 color
, 並且 TypeScript
會顯示編譯錯誤,然而你可以辯解說因為 width
是正確的, color
並不存在,但是 colour
名稱的錯誤是微不足道的
1 | // error: 'colour' not expected in type 'SquareConfig' |
這時候正規的實作方式可以是
1 | interface SquareConfig { |
我們將會討論 index signatures
但是在這裡可以說 SquareConfig
可以有任意數量的 properties 不論是不是 color
或是 width
他們並不在意
另外還有一種方法,你直接宣告一個 SquareOptions
物件來放入 createSquare
中也不會有錯誤出現
1 | let squareOptions = { colour: "red", width: 100 }; |
請記得上述的範例,不應該讓這些檢查類別變得更加的複雜,你應該要持續檢查這些類型,因為大多數的錯誤都會造成 bugs。如果你允許 在 createSquare
中使用 color
或是 colour
這兩個參數,你應該修改 squareConfig
來顯示這兩種使用情境
interfaces
可以用來描述物件的輪廓,然而為了要可以描述物件的 properties
所以 interfaces
應該也是可以描述 Function types
interfaces
描述一個 function type
的時候只需要定義 parameter 列表和回傳值,每一個 parameter 都需要明確的定義名稱和類別
1 | interface SearchFunc{ |
只需要定義一次之後就可以拿這個 interface
來建立變數
1 | interface SearchFunc{ |
在宣告 Function
的時候 parameter 的名字不一定要一樣
1 | interface SearchFunc{ |
宣告也可以只宣告一次,,之後依據同類型宣告的 Function
也會依照之前宣告的 interface
做檢查,不避在重複定義。
1 | interface SearchFunc{ |
基本上我們可以用 interface
來定義 Function
也可以來定義 index
1 | interface StringArray{ |
在上方的範例中, StringArray
中有宣告一個 index type 為 number
。
基本上只有 兩種類型的 index
, 就是 number
和 string
,也可以同時支援兩種類別,但是在支援兩種類別的時候若是為 100
則必須是回傳 '100'
,也就是兩種類別必須要統一
1 | class Animal{ |
string
是非常實用的宣告 index
方式,
因為 obj.property
也可以視為 obj['property']
這一個範例因為 name
的類別並不匹配,所以在檢查類別的時候會有錯誤
1 | interface NumberDictionary{ |
最後我們試著宣告一個唯讀的 interface
1 | interface ReadonlyStringArray{ |
1 | interface ClockInterface{ |
也可以描述在 class 中的 method, 例如在 Clock
中描述一個 setTime
的 method
1 | interface ClockInterface{ |
當我們要使用 interface
來宣告 class
的時候,要記得 class
有兩種類型,一種是 public 一種是 static 當你要宣告一個 class
的 constructor 的時候會有錯誤
1 | interface ClockConstructor{ |
這是因為當一個 class
轉為 instance
的時候,只有 instance
這邊有做 typing-check 而再 static-side 並沒有包含這個檢查
所以在下面的這個範例,需要定義兩個 interface
,ClockContructor
是為了 constructor 而 ClockInterface
是為了實體化後的物件定義,而會了方便我們定義 constructor 所以又建立一個 createClock
來做這件事情
1 | interface ClockConstructor { |
因為 createClock
的第一個參數是 ClockConstructor
在 createClock(AnalogClock, 7, 21)
中檢查 Analogclock
的 constructor 是否有正確的參數類型
就像 classes
一樣 interface
可以利用繼承將他們的屬性傳給自己的 Children
1 | interface Shape { |
也允許多重繼承,建立一個集合體
1 | interface Shape { |
Javascript 常常會有很豐富的一個 多次繼承,也可以使用 Hybird Type
來做多個繼承
1 | interface Conter{ |
當一個 interface
繼承了一個 class 只是繼承了他的屬性而不是他的實體只是繼承了他的屬性而不是他的實體,這就是說當你要實踐這個 interface
的同時也必需繼承同一個 class 來實現他的所有屬性
當你有一個很大的繼承架構,但是又想要自訂一個程式碼專為某一個 subclass 中的某些屬性 又不希望她繼承所有的父輩繼承
1 | class Control { |
上述範例中 SelectableControl
包含了所有的 Control
的屬性,包含 private
的 state,這意味著之後要實現 SelectableControl
的同時只能 extends Control
一個類別去承接他的 private
的 state
在 Control
之中允許透過 SelectableControl
來取得 private
state,而 SelectableControl
就像是 Control
知道他還會有一個 function select
, Button
和 TextBox
是 SelectableContorl
的子類,因為他們都是繼承魚 Control
但是 Image
和 Location
則不是
let
和 const
是兩種 Javascript 新的宣告方式, let
和 var
比較類似
const
則是定義之後禁止之後修改(常數)
TypeScript
也有提供 let
和 const
的宣告方式,下個部分將會解釋為什麼會推薦使用 let
和 const
在 Javascript 中宣告一個變數常用的方式是
1 | var a = 10; |
在上面的例子之中,你宣告了一個變數 a
為 10
你也可以再 Function 中宣告
1 | function f(){ |
也可以允許相同的變數在不同的 Function scope
1 | function f(){ |
在上方的範例中 g()
中可以取得變數 a
得值
1 | function f(){ |
使用 var 宣告會有一些區域的規則問題
1 | function f(shouldInitialize: boolean){ |
因為 var
是在 if裡面,所以當 shouldInitialize
是 false 的話就部會執行 if裡面的程式碼
所以 x 並未宣告過,就會造成 undefined
這個規則可能會造成壹些不同種類型的錯誤,其中一種就是當你重複宣告同樣名稱的變數的時候彼此會互相覆蓋
1 | function sumMatrix(matrix: number[][]){ |
上述範例就可以發現因為 i
變數在雙迴圈中會被互相覆蓋造成程式執行上的錯誤,不會依據我們預想的去執行
1 | for(var i=0;i<10;i++){ |
但是結過卻是
1 | 10 |
但是我們希望的是
1 | 0 |
因為每次呼叫 setTimeout
會延遲一段時間後才開始執行 Function但是迴圈會不斷覆蓋掉 i
這個變數,而在延遲時間之後呼叫到的 i
則是最後覆蓋成 10 的 i
最常見要解決這件事情的方式如下
1 | for(var =0; i< 10; i++){ |
這個看起來有點奇怪的解決方式在 javascript 中卻是常見解決這個問題的方式
現在你已經知道 var
會有一些問題,所以會有一些問題,所以為什麼需要介紹 let
.let
和 var
的使用方式依樣
1 | let hello = 'hello'; |
使用 let
宣告的時候,它的作用域市 blocking-scope
。和 var
宣告的作用域不一樣,他是用大括號來做區隔
1 | function f(input: boolean){ |
上述範例中有 a
和 b
兩個變數, a
的變數範圍在整個 f()
Function 之中,而 b
只會存在 if
之中
而變數使用 try catch 宣告的範例如下
1 | try{ |
另外一個很重要的 blocking-scope
變數不能在宣告之前做任何動作
1 | a++; |
在 TypeScript
中對這樣的提前宣告較為寬鬆,你需要使用 try catch
來取得錯誤訊息
若是沒有使用 try catch
TypeScrtip
並不會顯示這個訊息,若是在 ES2015
則會顯示這個錯誤訊息
1 | function foo(){ |
若是使用 var
的方式來宣告的話,他不會在意你宣告過幾次
1 | function f(x){ |
使用 let
宣告在同一個 scope 中只能宣告一次
1 | let x = 10; |
1 | function f(x){ |
只要是在不同的 blocking-scope
就可以做同名的宣告
1 | function (condition, x){ |
宣告一個新的名稱在另外一個內嵌的 block-scoping
這個行為叫做 shadowing
,但是這樣的行為會造成一些 bugs
例如:
1 | function sumMatrix(matrix: number[][]){ |
shadowing
在攥寫程式碼的時候應該要避免的狀況之一
當我們在一個作用域中宣告一個變數與 Function ,而 Function 也是其中一個作用域,在這個 Function 使用已宣告的變數的時候,即使脫離了那個作用域,也是依舊可以使用該變數
1 | function theCityThatAlwaysSleeps(){ |
因為 city
雖然是在 if
的作用域宣告的,但是可以透過 Function 記住他的指標
即使脫離作用域之後也可以透過該 Function 做呼叫使用
回憶之前 setTimeout
的範例, let
有相當大程度的不同
1 | for(let i=0; i < 10; i++>){ |
結果為
1 | 0 |
const
是另外一種不同的宣告
1 | const numLivesForCat = 9; |
雖然看起來跟 let
宣告一樣,雖然他們有相同的 block-scoping
規則,但是還是有些不同
const
宣告的變數是 immutable
的
1 | const numLivesForCat = 9; |
除非你要整個複寫整個物件,否則還是可以修改參數值得,
也就是此物件性質為 唯讀
的,詳情參閱
為什麼需要兩個不同的語意卻擁有相同的 block-scoping
的宣告方式呢?
基於 最小權限原則
若之後變數都不需要修改或是物件僅僅提供修改參數的權限時,則使用 const
,換句話說若是變數之後有可能會被覆寫則使用 let
來宣告
ES2015
的特性在 Typescript
中依舊可以使用
1 | let input = [1, 2]; |
這個解構也可以在 Function 中使用
1 | function f([first, second]: [number, number]){ |
也可以將大量的變數指定給某一個變數
1 | let [first, ...rest] = [1, 2, 3, 4]; |
當然你也可抵應某些參數
1 | let [first] = [1, 2, 3, 4]; |
也可以單存指定某些特定的參數
1 | let [, second, , fourth ] = [1, 2, 3, 4]; |
你也可以解構 object
1 | let o = { |
你可以單純指定 a
, b
而 c
可以因為不使用而跳過
你也可以使用 ...
這個形態來指定變數
1 | let {a, ...passthrogh} = o; |
你可以對變數重新命名
1 | let {a: newName1, b: newName2} = o; |
也可以將 a:newName1
改為 a as newName1
也是一樣的效果
在 TypeScript
也是需要宣告類型
1 | let {a, b}: {a: string, b: number} = o; |
在你解構的時候也可以提供預設值
1 | function keepWholeObject({a: string, b?: number}){ |
keepWholeObject
中的 b
若是有參數則指定該參數,若是 undefined
或是 null
則指定為預設值 1001
解構依舊可以使用在宣告函式之中
1 | type C = {a: string, b?:number}; |
也可以解構時預先放入預設值
1 | function f({a, b} = {a: "", b: 0}): void{ |
參數與設定型態都可以給予預設值,但是這兩個又有什麼不同呢?
1 | function({a, b=0}: {a: ""}): void{ |
ES2015
中的 Spread
特性也是支援的
1 | let first = [1, 2]; |
Spread
也可以使用在 object
1 | let defaults = {food: 'spicy', price: '$$', ambiance: 'noisy'}; |
在上述範例中 search
會解構 defaults
而且後面的 food 因為和 defaults
中重複而且順序在 defaults
的後面,所以會被覆蓋
1 | let defaults = {food: 'spicy', price: '$$', ambiance: 'noisy'}; |
但是 Spread
只會繼承特性,部會繼承 Function
1 | class C{ |
另外 TypeScript
編譯過程並不允許 generator function
的 Spread
參數傳遞
Given a sorted linked list, delete all duplicates such that each element appear only once.+
For example,
Given 1->1->2, return 1->2.
Given 1->1->2->3->3, return 1->2->3.
改一個排序過的連結陣列,刪除重複的節點。
範例:
[1,1,2] -> return [1,2]
[1,1,2,3,3] -> return [1,2,3]
Nodelist
並不是陣列,主要的區別在於 array
有 push 和 pop, 但是 NodeList
並沒有
最簡單的範例就自愛瀏覽器使用 document.querySelectorAll('class')
回傳值就是 NodeList
因為輸入值已經是排序過後的 NodeList
,所以只需要檢查室不是下一個跟這一個事不是相等
若是相等就略過
1 | var deleteDuplicates = function(head) { |
為了要讓更清楚的使用 TypeScript
會從最基本的元件 numbers
, strings
, structures
, boolean
, values
這類的動作, TypeScript
支援宣告這類型的 types
宣告最基本的 true/false 在 TypeScript
中稱為 boolean
1 | let isDone: boolean = false; |
和 Javascript 一樣,在 TypeScript
中預設的是浮點數,而這個類別統一為 number
也支援十六進制,十進制,八進制,以及 binary
1 | let decimal: number = 6; |
另外一種工程師常用的類型是字串,這種形態使用 string
來做宣告,在 TypeScript
中都允許使用單引號(‘) 或是 雙引號(“) 來做字串的宣告
1 | let color: string = "blue"; |
你也可以使用 template strings
,宣告使用 backtick/backquote(`)
1 | let fullName: string = `Bob Bobbington`; |
這個 template strings
也可以寫成
1 | let sentence: string = "Hello, my name is " + fullName + ".\n\n" + |
TypeString
和 Javascirpt依樣允許你使用 Array, 宣告時使用 []
來做類型宣告
1 | let list: number[] = [1,2,3]; |
Tuple
允許你宣告每一個參數的類型
1 | let x : [string, number]; |
好處就是當你明確的定義類型的時候,使用的時候就會有明確的錯誤訊息
1 | let x : [string, number]; |
另外一種特別的型別事 enum
你可以定義哪些允許的類型列表
1 | enum Color {Red, Green, Blue} |
你也可以設定 enum
得值
1 | enum Color { |
也可以直接定義 key值
1 | enum Color { |
我們要描述一個變數,但是在攥寫程式的當下不知道他的類型,這個值是屬於動態的內容,所以可以利用 any
來做宣告
1 | let notSure: any = 4; |
any
類型相當的彈性,你也許會期望 object
也會有一樣的情況,但是 object
並不允許你呼叫任意的函式,即使他真的存在
1 | let notSuer: any = 4; |
void
有一點像 any
, 他並沒有任何類型,你可以在沒有回傳的函式中使用它
1 | function warnUser():void{ |
當你宣告變數為 void
的時候,你只能 assign undefined
或是 null
1 | let unsable: void = undefined; |
在 TypeScript
中 undefined
和 null
有他的類型就叫做 undefined
和 null
就像 void
,他們沒瞎小路用
1 | let u: undefined = undefined; |
預設 null
和 undefined
是各種類型的亞類型,也就是你可以 assign null
或是 undefined
給 number
類型的變數
然而若是你在 tsconfig.json
中的選項 strictNullChecks
設定為 true 則 null
和 undefined
只能 assign 給 void
和他們的各自類型。
若是一個變數有多重可能,你也可以利用 union
類型
1 | let notSure: string | null | undefined; |
never
類型代表永遠不會發生,例如你的函式確認永遠都不會回傳任何值,那麼你就可以利用 never
類型
或是某一個變數永遠都是 false 你也可以設定他為 never
1 | function error(message: string): never{ |