Typescript
===
==參考資料:==
---
https://willh.gitbook.io/typescript-tutorial/
https://www.typescriptlang.org/docs/handbook/intro.html
==原始資料型別:==
---
::: info
boolean (布林值)、number (數值)、string (字串)、null、undefined 、Symbol。
:::
- ### Boolean
```javascript=
let Simple: boolean = false;
```
- ### Number
```javascript=
let Num: number = 6;
// 支援二進位制表示法
let Num: number = 0xf00d;
// 支援二進位制表示法
let binaryLiteral: number = 0b1010;
```
- ### String
```javascript=
let myName: string = 'Zoe';
let lunch: number = 100;
// 範本字串
let Simple: string = `Hello, my name is ${myName}.
I spend ${lunch} dollars for lunch`;
```
- ### void 空值 , 用來宣告一個沒有return值的函數
```javascript=
let GreetThree :(a :string, b: string) => void ;
GreetThree = (name: string, greeting : string) => {
console.log(`${name} says ${greeting}`)
}
```
- ### Null & Undefined
- **vs void** : **undefined 和 null 是所有型別的子型別**。
- undefined 和 null 可以賦值給其他型別的變數,但void卻不行。
```javascript=
let num: number = undefined; //ok
let n :undefined;
let num: number = n; //ok
let v : void;
let num: number = v;
// Error : Type 'void' is not assignable to type 'number'.
```
<br>
<br>
==Any、Array、Tuple、Enum==
---
- ### Any
```javascript=
//使用any賦值後,可任意更改成任何type
//宣告一個變數為任意值之後,對它的任何操作,返回的內容的型別都是任意值。
let anyAge : any = 30; //number
anyAge = true; //boolen
console.log(anyAge) //true
anyAge = "hello"
console.log(anyAge) //'hello'
let mixedarray : any[] = []
mixedarray.push("30");
mixedarray.push(30);
mixedarray.push(false);
console.log(mixedarray) //['30',30,fasle]
//可呼叫任何方法,傳回的值不論任何型別都可以:
let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
//在一開始宣告變數未給定型別即視為any
let something;
something = 'seven'; //string -> ok
something = 7; //重新賦值number -> ok
```
- ### Array
- 三種宣告方式
1. dataTyp[]
2. Array<dataType>
3. [dataType]
```javascript=
const user: string[] = ["aa", "bb", "cc"];
const idNumber: Array<number> = [1, 2, 3];
const mix: [string,number] = ['hi', 3];
```
<br>
```javascript=
let number = [10,20,30]
number.push(30);
number.push("a"); // Error, 遵循第一次建立的type
let mix: Array<string | number | boolen> = [10, 'array' , true]
let str : Array<string> = ['123']
mix.push(20)
mix.push('hello') //both OK
//用泛型宣告陣列
let mixArray: Array<number> = [1, 1, 2, 3, 5];
```
- ### Tuple
```javascript=
//一旦宣告賦值需提供指定的項
let tom: [string, number];
tom = ['Tom'] //Error : required in type '[string, number]'.
tom = ['Tom', 25]; //OK
```
- ### Enum
```javascript=
//未給值狀況下可像陣列一樣的用0~最尾數去更改或讀值。
Ex1.
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Tue"] === 2); // true
console.log(Days[0] === "Sun"); // true
console.log(Days[6] === "Sat"); // true
Ex2.
//增加可讀性溝通
enum PodCast {
Success : 1,
Fail : -1,
Streaming : 0
}
const status = PodCast.Succsss
console.log(status)
```
<br>
<br>
==union types 聯合型別==
---
```javascript=
let mixed : (string|number|boolean)[] = []
mixed.push("hello") //ok
mixed.push(20) //ok
mixed.push(true) //ok
function getLength(something: string | number): number {
return something.length;
}
//Error : Property 'length' does not exist on type 'number'.
//傳進去的值可能為 string or number, length不為number的屬性
//加上判斷式
function getLength(something: string | number): number {
if(typeof something === "string"){
return something.length;
}else{
return something
}
}
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 報錯
```
<br>
<br
==型別推論(Type Inference)、型別註記(Type Annotation)和型別斷言(Type Assertion)==
---
- 型別推論 : 沒有明確註記資料型別,TS編譯器便會自動推論出資料型別。


- 型別註記 : 明確的宣告資料型別,告訴編譯器必須符合註記的型別,在開發時就抓到錯誤賦值。

==斷言 As==
---
- TS認為開發者更了解自己編寫的程式碼。因此允許開發者覆蓋它的推論。編譯器會接受開發者寫下的斷言,並且不會再送出警告錯誤。
- 兩種用法
- <型別>值
- 值 as 型別
- 不能強制斷言成原本不存在的型別,必須透過將型別斷言成unknown,再行斷言。

- 和型別推論不同之處
- ==Type annotation== makes sure that the ==exact type== match.
- ==Type assertion== makes sure that the ==subtype== match.
ex.

### 適用1. 將變數指定為更加具體的型別(when you need to narrow down the type)
```javascript=
Ex1.
function getLength(something: string | number): number {
if ((something as string)) {
return (something as string).length;
} else {
return something.toString().length;
}
}
Ex2.
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
```
Ex3.

### 適用2. 當函式回傳any type,用斷言將any轉換成一個精確的型態會是比較好的做法。
```javascript=
//old
function getCacheData(key: string): any {
return (window as any).cache[key];
}
//new
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
```
### 適用3. 使用第三方的資源(Third-party resources)、呼叫會回傳未知結果的函示等。
```javascript=
//宣告fetch回來的值的type
interface Data {
name:string,
age:number,
uid:number
}
async function getData(){
const res = await fetch('http://jsonplaceholder.typicode.com/todos/1')
const data = await res.json() as Data
}
```
### phoenix


### As 壞處
1. 編譯器不會提醒你要加入的properties
```javascript=
//use 型別註記
interface Foo {
bar: number;
bas: string;
}
var foo: Foo = {
// the compiler will provide autocomplete for
// properties of Foo
};
//use Assertion
interface Foo {
bar: number;
bas: string;
}
var foo = {} as Foo;
// ahhhh .... forget something?
```
2. 斷言取代type check,無法得知正確型態。
```javascript=
type Human = {
age: number
name: string
spokenLanguage: string
}
let human = { age: 18, spokenLanguage: "English" };
const human2 = human as Human;
console.log(human2.name.toUpperCase());
//Error: Cannot read property 'toUpperCase' of null
```
- 參考資料:
1. https://medium.com/@bsalwiczek/type-annotation-vs-assertion-in-typescript-one-important-difference-4f4df715b5fe
2. https://stackoverflow.com/questions/47994926/detailed-differences-between-type-annotation-variable-type-and-type-assertion
3. https://ithelp.ithome.com.tw/articles/10217384
4. https://basarat.gitbook.io/typescript/type-system/type-assertion
5. https://medium.com/%E5%89%B5%E9%A0%86%E7%A7%91%E6%8A%80/%E7%AD%86%E8%A8%98-typescript-assertion-18fc95b69d9a
<br>
<br>
==Type & Interface==
---
- ### Type - 可以用來宣告Primitive type及物件、函式
```javascript=
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
type User = {
name: string
age?: number
};
type SetUser = (name: string, age: number)=> void;
```
- ### Type - 可以用來宣告物件、函式
```javascript=
interface User {
name: string
age: number
}
interface SetUser {
(name: string, age: number): void;
}
interface Alarm {
alert();
}
```
- ### Type & Interface
- 繼承寫法不同
- 宣告類型差異
- https://ms0223900.medium.com/typescript-%E4%B8%8D%E8%83%BD%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84%E5%8A%9F%E8%83%BD-ed020f4f7ef3
```javascript=
//type extends type
type myName = {
name:string;
}
type myAge = myName & {
age:number;
}
//interface extends interface
interface myName {
name:string;
}
interface myAge extends myName {
age: number;
}
// type extends interface
interface myName {
name: string;
}
type myAge = myName & {
age: number;
}
//interface extends type
type myName = {
name:string;
}
interface myAge extends myName {
age:number;
}
```
```javascript=
//type 可以宣告聯合類型
interface myName {
...;
}
interface myAge {
...;
}
type Pet = myName | myAge ;
// type可以選告基本類型
type Name = string
//interface 實現擴充
interface User {
name: string
age: number
}
interface User {
sex: string
}
// 可以用 implements 被class多重實現
interface Alarm {
alert();
}
interface Light {
lightOn();
lightOff();
}
class Car implements Alarm, Light {
alert() {
...;
}
lightOn() {
...;
}
lightOff() {
...;
}
}
```
<br>
<br>
==Function==
---
- 函式表示式(Function Expression)
```javascript=
let greeting : Function
greeting = () => {
console.log("hello")
}
const add = (a :number, b :number, c :number|string = 10) => {
console.log(a+b)
console.log(c)
}
add(8,9) //17 10 沒有傳入第三個參數,預設數字為10
add(8,9,20) //17, 20
add(8,9,'20') //17 '20'
```
- 函式宣告(Function Declaration)
```javascript=
function wrapInArray(obj: string | string[]) {
if (typeof obj === "string") {
return [obj];
}else{
return obj;
}
}
console.log(wrapInArray('1223')) //['1223']
console.log(wrapInArray(['123'])) //['123']
```
- 可選(==需寫在最後一個參數==)
```javascript=
function Person(Name: string, Age: number , Gender?: string) {
if (Name) {
return Name + 'is ' + Age + 'years old';
} else {
return Name + 'is a' + Age + 'years old' + Gender;
}
}
let tomcat = Person('Tom', 10);
let tom = Person('Tom', 20, 'boy');
console.log(tomcat); //Tom is 10 years old
console.log(tom); //Tom is a 20 years old boy
```
- Function Signature : 定義function中傳進去的參數跟回傳的type屬性
```javascript=
let logDetails : (obj:{name:string, age:number}) => void;
type person = {name: string , age:number} ;
logDetails = (obj:person) => {
console.log(`${obj.name} is ${obj.age} years old`)
}
logDetails({name:"zoe", age:20}) // zoe is 20 years old
logDetails({name:"John", age:30}) // John is 30 years old
```
<br>
<br>
==Object==
---
```javascript=
let zoeOne : object;
zoeOne = {
name : "zoecheng",
gender : "female",
}
let zoeTwo : {
name : string,
age : number
}
zoeTwo = {name:"Jack", age:30}
```
<br>
<br>
==Class==
---
- 實現 interface
```javascript=
//interface file
export interface Hasformatter{
format() :string;
}
```
```javascript=
// implements to class
interface Hasformatter{
format() :string;
}
class Payment implements Hasformatter{
name : string ;
details : string;
amount : number;
constructor(a: string,b: string, c:number){
this.name =a;
this.details=b;
this.amount=c
}
format(){
return `${this.name} is spend ${this.amount} for ${this.details}`;
}
}
let a = new Payment('Zoe','shopping',300);
console.log(a.format()) //Zoe is spend 300 for shopping
```
- public : 預設,class內外皆可以存取及修改值。
```javascript=
//public
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal("Dog");
console.log(a.name); // Dog,可以在class外部存取
a.name = 'Cat';
console.log(a.name); // Cat,也可以在class外部修改值
```
<br>
- private : ==只能==在宣告它的類別的==內部==讀取但及修改。
```javascript=
//privite
class Property {
private firstName: string;
constructor() {
this.firstName = 'Jimmy';
}
}
const propertyInstance = new Property();
console.log(propertyInstance.firstName); //只能在Property中存取
```
<br>
- protected :和private區別是它在==子類別==中也是允許被讀取及修改的。
```javascript=
//protected
class Property {
protected firstName: string;
constructor() {
this.firstName = 'Jimmy';
}
}
class PropertyTwo extends Property {
sayHi(){
console.log(this.firstName)
return this.firstName
}
}
const propertyInstance = new Property();
const propertyTwoInstance = new PropertyTwo();
console.log(propertyInstance.firstName); //Error :只能在Property中存取
console.log(propertyTwoInstance.sayHi()); //Line: 13 Jimmy
```
<br>
<br>
==Generics 泛型==
---
- 定義function、interfaces或class的時候,不一開始指定傳入傳出型別,用<T>取代指定型別,在傳入時才指定型別。
```javascript=
function ArrayOne<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
console.log(ArrayOne<string>(3, 'x')) // ['x', 'x', 'x']
console.log(createArray(5, 3)); //[3,3,3,3,3]
```
```javascript=
// 泛型 + interface
interface ArrayTwo {
<T>(length: number, value: T): Array<T>;
}
let CreateArray: ArrayTwo;
CreateArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
console.log(CreateArray(3, 'x')); // ['x', 'x', 'x']
```
```javascript=
//當指定<T> 當型別時,一開始不知道卻謝型別無法使用length屬性
//需加上interface使涵蓋length屬性才適用arg.length
interface Length {
length : number
}
function StringLength <T extends Length>(arg:T): number {
console.log(arg.length) // 4
return(arg.length)
}
StringLength("test")
```
<br>
<br>