--- title: 2024程設筆記 tags: 2024 author: xCk27x --- # 第二次上課 ###### tags: `2024` - [ ] Git - [ ] TypeScript - [ ] Docker ## Git :::info Git是眾所周知的版本管理工具,最基本的操作就是push、pull、clone這些。 但如果要管理一個大型專案,只有這樣是遠遠不夠的,所以以下是一些比較進階的操作。 ::: ### 首先要普及幾個概念 1. 暫存區(Staging Area) 任何新增、修改、刪除的檔案都需要先使用 ```git add``` 送進git的暫存區,才可以被提交 -> commit。 2. 遠端 (remote) 你使用本地端使用git init時,建立的會是一個**local** repository (目錄下存在```.git```這個檔案),意思是你的commit、branch、history,全部都會存在你的電腦上。 而你在github上看到的都是**remote** repository,意思是你可以將local repository的東西**複製**一份到遠端上,這樣別人就能看到你的檔案跟git紀錄了。 :::warning 但請注意,local跟remote是不會自動同步的,所以你需要執行一些遠端的操作來同步兩者,例如push、fetch、pull。 ::: 3. main / master (主分支) 你可能會看到一些專案的主分支是master,而有些是main。實際上兩個並沒有差,只是以前預設名稱是master,而現在是main。 4. head/HEAD (當前分支) head是一個很特別的分支名稱,他永遠指向**目前所在的分支或commit位置**,概念上很像C語言的指標,```git checkout``` 基本上就是改變head指向的位置。 ### 還是先從最常用的指令講起 - **```git add -A```** -> 將所有變更都送進暫存區,請注意 **A** 要大寫。 ![image](https://hackmd.io/_uploads/rk3R-XPf0.png) 就是那個+號 - **```git commit -m "your messege"```** -> 用暫存區的變更建立一個新commit,並用-m提供commit訊息。 ![image](https://hackmd.io/_uploads/HknXQ7PGA.png) - **```git push```** -> 將當前分支的變更同步到遠端。 - **```git pull```** -> 等於 ```git fetch``` + ```git merge```,兩者都會在後面講到。 ### 接下來會複雜一些 :::info 請先下載 vscode extension: [**Git Graph**](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph) ::: - **```git fetch```** -> 將遠端所有branches的狀態更新到本地端,但注意這並不是同步! 只是讓本地端知道目前遠端總體的狀態是如何。 ![image](https://hackmd.io/_uploads/HkyeEUvfC.png) > 以此 Git Graph 舉例,```origin/oCk27o``` 就是oCk27o在遠端的狀態,但本地端的oCk27o則在其他位置。 > 而注意到main的左邊有一個圓圈,他代表head的位置。 - **```git merge```** -> 將head branch與其遠端branch合併。以上圖來看,就是將 ```main``` 與 ```origin/main```兩個分支合併。 - **```git rebase <target branch>```** -> 跟merge很類似,他會將到head分支上所有新的commit重新放到指定分支上。 > 我知道上面兩個指令說起來有點抽象,所以這裡有一個學習網站 > https://learngitbranching.js.org/?locale=zh_TW&demo= > 請將基礎篇完成,這樣你就會比較有概念了👍 - **```git reset <模式> <commitID> ```** -> 將你的head移動到先前的commit,並將此commit以後的commit取消掉。 > 把commit取消掉,新的檔案不就消失了嗎,這樣會出大事吧? 對,所以reset提供你三個模式選擇,分別是mixed(預設)、soft、hard 具體的差別請看這篇 https://ithelp.ithome.com.tw/articles/10332631 ## TypeScript :::info TypeScript 將 JavaScript 的弱型別問題做了些改善,讓debug變簡單。 但相對的,會需要額外花些時間去宣告型別,可以說是用時間換出錯機率。 ::: > 因為我們是前端是寫vue為主,而<script></script>中可以讓每個組件決定要不要寫TS,如果還是想寫JS也沒關係,但TS遲早會碰到。 > 後端嘛... 我再考慮一下到底寫JS還TS🤔 ### 下載 1. 全局下載 ```$ npm install -g typescript``` 2. 讓專案能使用TypeScript 編譯器 ```$ tsc --init``` 這時候你的目錄下會出現 ```tsconfig.json```,這是 TypeScript 編譯器的設定檔。 3. 接下來使用 ```$ tsc``` TypeScript 編譯器就會幫我們自動掃描所有 .ts 結尾的檔案並且產出 JS 檔案。 4. 最後用 ```$ node yourfile.js``` 就可以執行了 ! ### 型別 說實在,其實TS的基本型別你需要懂得還真沒幾個 1. any 2. number 3. string 4. boolean 5. null 6. undefined 7. void 8. never > 好把我改口,其實很多🥲 #### 講太長我自己都不想看了,所以就盡可能簡單解釋吧 - any -> ```let a: any```,任何型別都可能, 就是說 a 這個變數可以被assign任何型別的值。這就是JS變數的概念,也是TS想避免的事。 - number -> ```let b: number```,只能接受數字,而甚麼叫數字呢? example: 1, -12, 6.31, 2e10, -Infinite, Number.MAX_SAFE_INTEGER, NaN - string -> ```let c: string```,只能接受字串。 - boolean -> ```let d: boolean```,只能接受布林值,true 或 false。 - null -> ```let e: null```,這變數沒有存任何值。 - undefined -> ```let f: undefined```,你要找的這個值沒有定義過啊! - void -> ```function modify(): void {...}```,回傳值為空的意思,通常用在函數的回傳值宣告,告訴我們這個函數不會回傳任何值。 - never -> ```function mustError(): never {...}```,never return的意思,通常用在函數的回傳值宣告,告訴我們這個函數內存在無窮迴圈,或是一定會拋出錯誤。 ### 型別宣告 如果一個變數的值可以是number或是null,則可以這樣指定: ```let numOrNull: number | null = 123```,這樣會先將初始值設定為123,而之後使用```numOrNull = null```便不會報錯。 funciton的型別宣告也很簡單 -> function hello(a: number, b: string, c: number | null = null): string {...} hello 能傳入三個 Arguments,a 只能是 number,b 只能是 string,c 則可以是 number | null,並且預設值是null。 而他的回傳值是string。 ### type > 了解完基本型別,要來講物件的型別了 一個自定義的Object大概就是用大括弧包其來的東西,例如 ```typescript= let Jack = { id: 12, age: 20, phone: "0900112233" } ``` 那我要怎麼定義這個物件應該長甚麼樣子呢? -> 使用 type ```typescript= type Person = { id: number, age?: number, // 注意這行的?: phone: string } let Jack: Person = { id: 12, age: 20, phone: "0900112233" } ``` > 那剛剛的age?: number是甚麼意思呢? 這代表你可以不指派此物件的這個屬性,例如 ```typescript= type Person = { id: number, age?: number, // 注意這行的?: phone: string } let Dora: Person = { id: 21, phone: "0912345678" } ``` 這也是合法的,而當你試圖存取,Dora.age的話,會回傳undefined。 當然,你也可以後期再去assign這個值 ```Dora.age = 10``` 但是請注意,你不可以直接定義新的屬性到Dora裡。 ```typescript= Dora.height = 150 // 報錯: 類型 'Person' 沒有屬性 'height' ``` ### interface :::info interface 直接翻譯是「介面」,或是「藍圖」,實際用起來跟 type 差不多,甚至很難界定甚麼時候用 type 甚麼時候用 interface。 ::: interface用來定義一個 ```typescript= interface Car { // 注意到定義interface是不用 = 的 wheel: 4 windows?: 6 metal: 'iron' } ``` 目前看起來都跟 type 很像,也一樣可以使用?:,那差別是甚麼呢 下面這樣的重複定義 interface 是合法的,因為他的概念是「為藍圖加入新的功能」 ```typescript= interface Car { wheel: number, windows?: number, metal: string } interface Car { brand: string, price: number } ``` 而下面這樣會出錯,因為 type 不可重複定義。 ```typescript= type Person = { id: number, age?: number, phone: string } type Person = { // 報錯: 識別碼 'Person' 重複。 school: string, grade: number } ``` 若想要在程式碼中擴展 type ```typescript= type Person = { id: number, age?: number, phone: string } type SchoolInfo = { school: string, grade: number } type PersonInfo = Person & SchoolInfo; ``` > 詳細解釋可以參考 https://ithelp.ithome.com.tw/articles/10216794 可以發現實際上這並不算擴展,而是用兩個type重新定義了一個新的type。 :::info 結論是,如果物件的定義常需要修改或新增,則是建議用 interface,如果是固定的則用 type。 ::: ## Docker ### 簡介 Docker 簡單來說,是用來打包應用程式的,而裡面有幾個重要概念你應該先知道。 1. 每個應用會在一個container裡實際運行 2. 應用長甚麼樣子儲存在image裡 3. 儲存image的倉庫叫registry 所以我要獲得某個寫成image的應用,並實際運行起來應該經過以下流程 1. 從registry取得此應用的image 2. 根據image建立container 3. 讓container跑起來 ### 下載 Docker Desktop [官方下載 Windows 版本](https://docs.docker.com/desktop/install/windows-install/) [官方下載 MacOS 版本](https://docs.docker.com/desktop/install/mac-install/) 安裝後,在cmd執行 ```docker --version```,確認是否偵測到 docker 指令。 ``` Docker version 24.0.2, build cb74dfc ``` :::warning 因為docker的指令提供了非常多的選項,所以沒辦法在這裡一一介紹,所以以下是我自己做的筆記,大部分可能用到的命令都在裡面: https://thread-judo-0f8.notion.site/docker-5770051212e0446dabce273aed847bc2?pvs=4 ::: ### container 指令 - ```docker container run <imagePath>``` 他會先檢查你的Docker Desktop中有沒有要求的image,如果沒有就根據imagePath去registry下載image,再根據image建立一個新的container。 -> 試著實際下指令 ```docker container run diamol/ch02-hello-diamol-web``` 現在你應該可以看到docker desktop中出現了第一個container,但名字是隨機的 ![image](https://hackmd.io/_uploads/HJRQARufC.png) 如果你想要指定建立出來的container的名字,使用選項 ```--name <containerName>``` 如果應用有成功運行的話應該會長這樣 ![image](https://hackmd.io/_uploads/SJcgkkFG0.png) 這個應用其實是一個網站,但目前沒有辦法連到他的頁面,所以我們必須使用```--publish```選項來分配port ``` $ docker run --name diamol-web --publish 8088:80 diamol/ch02-hello-diamol-web ``` ![image](https://hackmd.io/_uploads/H1mkZ1Kf0.png) ```--publish 8088:80``` 的意思是,「配發本機的 port 8088 給 container 的 port 80 使用」 因此你電腦的localhost:8088就會顯示此container內運行的網站。 最後介紹一個非常常用的選項: ```--detach (-d)```,這個選項告訴你再背景執行這個container,不要占用當前的terminal。 ### image 現在點開docker desktop的**Image頁面**,你應該會看到剛剛從registry上下載的image -> diamol/ch02-hello-diamol-web > 疑 我知道container是由image建立的,那image是怎麼建立的呢? 剛剛說過,image的目的是儲存應用程式的資料,所以我們應該在撰寫專案時使用dockerfile來建立image ### Dockerfile :::info 範例程式碼 https://drive.google.com/drive/folders/1I02zHegNQN8WYUKKPI5p24WNq3ax2Jmv?usp=drive_link ::: ![image](https://hackmd.io/_uploads/B1a7ByFGR.png) 1. FROM ex: FROM diamol/golang 每個docker image都必須以其他image作為基礎 2. WORKDIR ex: WORKDIR web 建立一個目錄,並將其設定為工作目錄 3. COPY ex: COPY index.html . 將本機的檔案從本機路徑複製到目標路徑,這邊是指將index.html複製到web資料夾中。 4. RUN ex: RUN go build -o /web/server 從image建置container時會執行的命令 5. CMD ex: CMD ["web/server"] 當container啟動時會執行的命令 6. ENV ex: ENV USER=sixeyed" 用來設定環境變數 7. EXPOSE ex: EXPOSE 80 定義容器執行時要監聽的端口 ### Registry registry是用來存放image的倉庫,預設是Docker Hub 而在尋找image的過程中,會使用「image referance」 ex: docker.io/diamol/golang:latest - docker.io 代表registry的網址,而這就是預設的Docker Hub的。 - diamol 是image擁有者的帳戶名稱。 - golang 是指repository的名稱,裡面會有image的不同版本。 - latest 是指使用repository中image的最新版本,latest是預設值。