--- tags: Node.js 直播班 - 2023 春季班 --- # TypeScript 暖身讀書會 - 上半場 * [3/10(五) 20:00 線上 ZOOM 網址](https://us06web.zoom.us/j/84593105307) ## 環境建置 1. 安裝 VSCode 編輯器 2. [安裝 Node.js ](https://nodejs.org/zh-tw/download/) 3. 安裝 TypeScript `npm install -g typescript` 4. 新增專案資料夾後,打開終端機或命令提示字元視窗,進入您要建立專案的資料夾,然後輸入指定`tsc --init` 5. 新增 `hello.ts`,寫 `let sayHello: string = 'Hello World!';` 6. 執行 `tsc hello.ts` 來輸出 7. 也可用 `tsc --watch` 來進行監控 > 踩雷故事:不宣告 --init 時,會有重複宣告狀況 ## 型別介紹 好處:編譯前就可以先幫你檢查 ```=Typescript // 字串型別 let name: string = 'Tom'; // 數值型別 let age: number = 18; // 布林型別 let isStudent: boolean = true; // 陣列型別 let hobbies: string[] = ['reading', 'coding', 'music']; // 物件型別,後面會提到 interFace const obj = { num:3 } obj.num = 3; // 函式型別 function add(x: number, y: number): number { return x + y; } // 任意型別 let data: any = 'Hello, world!'; data = 123; data = true; // 空值型別 function sayHello(): void { console.log('Hello, world!'); } // 永遠不型別 function throwError(): never { throw new Error('Something went wrong!'); } // 列舉型別 enum Gender { Male, Female, Other } let gender: Gender = Gender.Male; ``` ## 瀏覽器 DOM 與 Event 特殊型別 * HTMLElement:代表 `HTML` 元素節點。 * HTMLDivElement:代表 `<div>` 元素節點。 * HTMLSpanElement:代表 ``<span>`` 元素節點。 * HTMLAnchorElement:代表 `<a>` 元素節點。 * HTMLButtonElement:代表 `<button>` 元素節點。 * HTMLInputElement:代表 `<input>` 元素節點。 * HTMLTextAreaElement:代表 `<textarea>` 元素節點。 * HTMLSelectElement:代表 `<select>` 元素節點。 * HTMLTableElement:代表 `<table>` 元素節點。 * HTMLTableRowElement:代表 `<tr>` 元素節點。 * HTMLTableDataCellElement:代表 `<td>` 元素節點。 * MouseEvent:代表滑鼠事件。 * KeyboardEvent:代表鍵盤事件。 * TouchEvent:代表觸控事件。 * PointerEvent:代表指標事件。 * DragEvent:代表拖放事件。 * FocusEvent:代表焦點事件。 * InputEvent:代表輸入事件。 ## interface 介紹 * [interface 指南導讀](https://willh.gitbook.io/typescript-tutorial/basics/type-of-object-interfaces) TypeScript 中的 interface 是一種用來描述 JavaScript 中物件形狀的方式。它可以用來定義一個物件必須具備的屬性以及其類型,以及可以有哪些可選屬性和任意屬性等。 Person 範例: ```=typescript interface Person { firstName: string; lastName: string; age: number; email?: string; } ``` 在上面的範例中,我們定義了一個名為 Person 的 interface,它包含了 firstName、lastName、age 這三個必須的屬性,分別是 string、string、number 類型的資料。另外,我們還定義了一個可選屬性 email,它的資料類型是 string。 使用 interface 可以讓程式碼更加明確和易於維護,讓開發者可以更清晰地知道物件的形狀和屬性的類型,有助於降低開發時出錯的風險。 :::spoiler 範例程式碼 ```=typescript interface User { id: number; name: string; age: number; email: string; } const users: User[] = [ { id: 1, name: 'Alice', age: 25, email: 'alice@example.com' }, { id: 2, name: 'Bob', age: 30, email: 'bob@example.com' }, { id: 3, name: 'Charlie', age: 35, email: 'charlie@example.com' } ]; function addUser(newUser: User): void { users.push(newUser); } function getUserById(userId: number): User | undefined { return users.find(user => user.id === userId); } ``` ::: ## todolist 範例 :::spoiler index.html ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <input type="text" class="txt" placeholder="請輸入待辦事項"> <input type="button" class="save" value="儲存待辦"> <ul class="list"></ul> <script src="hello.js"></script> </body> </html> ``` ::: :::spoiler todo.ts ```=typescript const txt = document.querySelector('.txt') as HTMLInputElement; const save = document.querySelector('.save') as HTMLButtonElement; const list = document.querySelector('.list') as HTMLUListElement; interface ToDoItem { content: string; } let data: ToDoItem[] = []; function renderData(): void { let str = ''; data.forEach(function (item, index) { str += `<li>${item.content} <input class="delete" type="button" data-num="${index}" value="刪除待辦"></li>`; }); list.innerHTML = str; } // 新增待辦功能 save.addEventListener('click', function(e: MouseEvent) { if (txt.value == '') { alert('請輸入內容'); return; } let obj: ToDoItem = { content: txt.value }; data.push(obj); renderData(); }); // 刪除待辦功能 list.addEventListener('click', function(e: MouseEvent) { if (!(e.target instanceof HTMLElement) || e.target.getAttribute('class') !== 'delete') { return; } let num = parseInt(e.target.getAttribute('data-num')!); console.log(num); data.splice(num, 1); alert('刪除成功!'); renderData(); }); ``` ::: ## ts.config 設定檔 以下是 `ts.config` 設定檔中 `compilerOptions` 裡面的設定項目,以表格形式進行詳細解釋。表格中包括了每個設定檔的屬性名稱、屬性介紹、預設值以及有效值。 * [Day 31. 戰線擴張・專案監控 X 編譯設定 - TypeScript Compiler Compile Configurations](https://ithelp.ithome.com.tw/articles/10222025) * [TypeScript 編譯設定 - tsconfig.json](https://ithelp.ithome.com.tw/articles/10216636) | 屬性名稱 | 屬性介紹 | 預設值 | 有效值 | | :-- | --- | --- | --- | | target | 編譯目標 | es2016 | ES3, ES5, ES6/ES2015, ES2016, ES2017, ES2018, ES2019, ES2020, ESNext | | module | 生成模組規範 | CommonJS | ES2015, ES2020, AMD, System, UMD, CommonJS, None | | lib | 編譯過程中需要包含的標準庫 | 無 | dom, dom.iterable, webworker, webworker.importscripts, es5, es6, es2015, es2016, es2017, es2018, es2019, es2020, esnext | | allowJs | 允許編譯 JavaScript 文件 | false | true, false | | checkJs | 是否檢查 JavaScript 文件的型別 | false | true, false | | jsx | 設置 JSX 語法支援 | Preserve | Preserve, React, React-Native | | declaration | 是否生成對應的 .d.ts 文件 | false | true, false | | sourceMap | 是否生成對應的 .map 文件 | false | true, false | | **outDir** | 編譯後的文件輸出目錄 | 無 | 相對路徑或絕對路徑 | | **rootDir** | 編譯時需要被編譯的根目錄 | 無 | 相對路徑或絕對路徑 | | removeComments | 是否移除註釋 | false | true, false | | noEmit | 是否生成編譯後的文件 | false | true, false | | strict | 是否開啟嚴格模式 | false | true, false | | noImplicitAny | 是否禁止 any 型別 | false | true, false | | strictNullChecks | 是否開啟嚴格的空值檢查 | false | true, false | | noImplicitThis | 是否禁止 this 的隱式型別 | false | true, false | | alwaysStrict | 是否在輸出文件中包含 "use strict" | false | true, false | | noUnusedLocals | 是否檢查未使用的局部變數 | false | true, false | | noUnused<br>Parameters | 是否檢查未使用的形參 | false | true, false | | noImplicitReturns | 是否禁止缺少返回值的函數 | false | true, false | | noFallthrough<br>CasesInSwitch | 是否禁止 switch 語句中的 fallthrough | false | true, false | | baseUrl | 指定專案的根目錄,用來解析非相對模組名 | 無 | 相對路徑或絕對路徑 | | paths | 設定模組的路徑映射 | 無 | 物件 | | experimentalDecorators | 是否啟用實驗性的裝飾器語法 | false | true, false | | emitDecorator<br>Metadata | 是否生成裝飾器元數據 | false | true, false | | allowSynthetic<br>DefaultImports | 是否允許 import 默認為 export default 的模組 | false | true, false | | noStrict<br>GenericChecks | 是否關閉泛型的嚴格型別檢查 | false | true, false | | forceConsistent <br>CasingInFileNames | 強制文件名大小寫一致 | false | true, false | | esModuleInterop | 是否允許 import 默認為 export 的模組 | false | true, false | | preserveSymlinks | 是否保留符號鏈接 | false | true, false | | skipLibCheck | 是否跳過標準庫檢查 | false | true, false | 透過這些設定,我們可以更好地控制 TypeScript 編譯器的行為,以符合不同的專案需求。如果有任何問題或需要進一步的解釋,請隨時提出。 ## Vue、React、Angular 代辦事項差異 ### Vue3 1. 安裝 Node.js 2. 執行環境 `npm create vite@latest todo-app -- --template vue-ts` 3. 在 `components` 資料夾載入 `TodoList.vue 元件`,範例碼如下 4. 在 `app.vue` 加入 `TodoList.vue 元件` 5. `npm run dev` 開啟伺服器 :::spoiler Todolist.vue ```=javascript <template> <div> <h2>代辦清單</h2> <div> <input type="text" v-model="inputText" /> <button @click="addTodo">新增</button> </div> <ul> <li v-for="(todo, index) in todos" :key="index"> {{ todo.text }} <button @click="removeTodo(index)">刪除</button> </li> </ul> </div> </template> <script lang="ts"> interface TodoItem { text: string; } export default { data() { return { inputText: '', todos: [] as TodoItem[], }; }, methods: { addTodo() { if (this.inputText.trim() === '') { return; } this.todos.push({ text: this.inputText.trim() }); this.inputText = ''; }, removeTodo(index: number) { this.todos.splice(index, 1); }, }, }; </script> ``` ::: ### React 1. 安裝 Node.js 2. 執行環境 `npm create vite@latest react-todo-app -- --template react-ts` 3. 在 `components` 資料夾載入 `TodoList.tsx 元件`,範例碼如下 4. 在 `App.tsx` 加入 `TodoList.tsx 元件` 5. `npm run dev` 開啟伺服器 :::spoiler TodoList.tsx ```javascript import React, { useState } from 'react'; interface ToDoItem { content: string; } const App: React.FC = () => { const [inputValue, setInputValue] = useState<string>(''); const [toDoList, setToDoList] = useState<ToDoItem[]>([]); const handleAddToDo = (): void => { if (!inputValue.trim()) { alert('請輸入內容'); return; } const newItem: ToDoItem = { content: inputValue, }; setToDoList([...toDoList, newItem]); setInputValue(''); }; const handleDeleteToDo = (index: number): void => { const newToDoList = [...toDoList]; newToDoList.splice(index, 1); setToDoList(newToDoList); }; return ( <div> <h1>代辦清單</h1> <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> <button onClick={handleAddToDo}>新增待辦</button> <ul> {toDoList.map((item, index) => ( <li key={index}> {item.content}{' '} <button onClick={() => handleDeleteToDo(index)}>刪除待辦</button> </li> ))} </ul> </div> ); }; export default App; ``` ::: ### Angular 1. 安裝 angular CLI,`npm install -g @angular/cli` 2. 執行環境 `ng new my-app` 3. 調整 `app.componens.ts`、`app.modules.ts`,範例碼如下 4. 執行 `ng serve --open` 運作伺服器 :::spoiler app.componens.ts ``` =javascript import { Component } from '@angular/core'; interface Todo { content: string; } @Component({ selector: 'app-root', template: ` <div> <h1>Todo List</h1> <form (submit)="addTodo()"> <input name="newTodo" type="text" [(ngModel)]="newTodo" placeholder="Add new todo" /> <button type="submit">Add</button> </form> <ul> <li *ngFor="let todo of todos; let i = index"> {{ todo.content }} <button (click)="removeTodo(i)">Remove</button> </li> </ul> </div> `, styleUrls: ['./app.component.scss'] }) export class AppComponent { newTodo: string = ''; todos: Todo[] = []; addTodo() { if (this.newTodo.trim()) { this.todos.push({ content: this.newTodo }); this.newTodo = ''; } } removeTodo(index: number) { this.todos.splice(index, 1); } } ``` ::: :::spoiler app.modules.ts ```=javascript import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule,FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } ``` ::: ### Node.js 1. 開新資料執行 `npm init` 後,安裝 `npm install --save-dev @types/uuid uuid ts-node` 2. 安裝 `ts-node` 套件 `npm install -g typescript ts-node` 3. 執行 `tsc --init` 3. 新增一個 `server.ts` 檔案,程式碼如下 3. `ts-node server.ts` :::spoiler server.ts 程式碼 ```=typescript import http from 'http'; import { v4 as uuidv4 } from 'uuid'; interface Todo { title: string; id: string; } const todos: Todo[] = []; const requestListener = (req: http.IncomingMessage, res: http.ServerResponse) => { const headers = { 'Access-Control-Allow-Headers': 'Content-Type, Authorization, Content-Length, X-Requested-With', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'PATCH, POST, GET,OPTIONS,DELETE', 'Content-Type': 'application/json' }; let body = ''; req.on('data', chunk => { body += chunk; }); if (req.url === '/todos' && req.method === 'GET') { res.writeHead(200, headers); res.write(JSON.stringify({ 'status': 'success', 'data': todos, })); res.end(); } else if (req.url === '/todos' && req.method === 'POST') { req.on('end', () => { try { const title = JSON.parse(body).title; if (title !== undefined) { const todo: Todo = { 'title': title, 'id': uuidv4(), }; todos.push(todo); res.writeHead(200, headers); res.write(JSON.stringify({ 'status': 'success', 'data': todos, })); res.end(); } else { res.writeHead(400, headers); res.write(JSON.stringify({ 'status': 'false', 'message': '欄位未填寫正確,或無此 todo id', })); res.end(); } } catch (error) { res.writeHead(400, headers); res.write(JSON.stringify({ 'status': 'false', 'message': '欄位未填寫正確,或無此 todo id', })); res.end(); } }); } else if (req.method === 'OPTIONS') { res.writeHead(200, headers); res.end(); } else { res.writeHead(404, headers); res.write(JSON.stringify({ 'status': 'false', 'message': '無此網站路由', })); res.end(); } }; const server = http.createServer(requestListener); server.listen(3005); ``` ::: ## 導入結論 1. 先導入型別與 `interface` 試試手感 2. 用 chatGPT 協助轉檔,等熟悉後再來手寫 3. 要注意 chatGPT 他的模型只有學到 2021 ## 洧杰學習資源 1. [Discord 同學的分享](https://discord.com/channels/801807326054055996/1077558592401588234) 2. [TypeScript 新手指南](https://willh.gitbook.io/typescript-tutorial/) 3. ChatGPT [對話一](https://sharegpt.com/c/R1BdXYm) 4. [保哥](https://www.youtube.com/watch?v=SCLKlc6G0_k)與[其他講者](https://www.youtube.com/watch?v=EEdd8zov4-w)的劍與盾 5. ChatGPT [對話二](https://sharegpt.com/c/KYfEt9I) :::spoiler 團隊間先不要用太複雜的 typescript ![](https://i.imgur.com/LffIEuG.png) ![](https://i.imgur.com/dgnOm4k.png) :::