### GDSC NYUST x 資訊創客社 <br> ### 網頁前端開發讀書會 #### Vue3 基礎課程 <br> #### 2023/11/27 ( Mon ) 19:00 - 21:00 #### 講師:楊鈞元 Charles #### 本次課程影片:(⚒️製作中) <img src="" height="200px"> --- ## 前言 ---- ### 什麼是Vue.js? ---- <img src="https://hackmd.io/_uploads/HyVxsjxH6.png" style="width:20%" /> 一個平易近人、高效能且多功能的 漸進式JavaScript 框架 用於建立面向於 Desktop, Mobile, WebGL, Terminal 應用程式的前端 Vue.js 基於標準的 HTML、CSS 和 JavaScript 因此最廣泛應用就是用在建立網頁 ---- #### 使用 Vue.js 的主要場景 ---- - 無須任何建置就單純增強 html 檔案 - 作為 Web 元件遷入到任何頁面上 - Single-Page Application (SPA): 在使用網站時,只載入一個頁面,在網頁更新時渲染頁面,不需重新加載 - Fullstack / Server-Side Rendering (SSR): 從伺服器端取得資料,再繪製出完整的 HTML 頁面,有助提高搜尋引擎優化和初次載入性能 - Jamstack / Static Site Generation (SSG): 靜態網站生成:在部署前生成靜態 HTML,不需要在瀏覽器或服務器上進行渲染 ---- ### Vue.js的特點和優勢 ---- ![image](https://hackmd.io/_uploads/B15OIZbB6.png) ---- - 組件化架構: Vue.js 以整合 HTML、CSS、JS 的單文件元件為基礎,方便每個元件獨立測試,且程式碼較簡單易懂,有助於最佳化與除錯 - 輕量級: Vue.js 相當輕量,是加載速度較快的因素之一 - 學習曲線平緩: Vue.js 由簡入深,只要具有 HTML、CSS、JS 基礎知識就能快速學習並立即應用,能以最少的程式碼達到最大的成效 ---- - 豐富工具: 雖然 Vue 的檔案較小,但由於開發社群日益壯大,有豐富的工具能夠實現進階的需求 - 增量採用: 如果需要,Vue 可以僅使用 CDN 引入在 html 檔案,即可使用 Vue 的強大功能 - 與其他框架整合: 作為靈活的前端框架,Vue 可以輕鬆地與其他工具和框架整合,以滿足項目的具體需求 ---- - 虛擬 DOM Model 和渲染: Vue使用虛擬DOM,當頁面異動時,無需不斷重新渲染整個DOM,同時提供更好的性能和更順暢的頁面體驗。 - DOM操控優勢: 透過雙向綁定功能,更新元件和追蹤數據更新相當簡單,綁定的數據可以像 DOM Object 一樣更新,因此在應用即時更新的情況下,Vue表現特別靈活。 ---- - 速度和性能: 當比其他前端框架,Vue 有相對更加卓越的反映速度與性能表現 - 日益壯大的社區支援與相關教學: 基於 Vue 的卓越特色,深受大量前端開發社群認可,目前有越來越多基於 Vue 的 Libraries、Utilities、Framework 誕生,且 Vue 仍在非常積極的更新與優化,並相當願意與開發社群討論未來發展的方向 ---- #### 已經可見使用 Vue.js 搭建部分網頁的大型企業 ![image](https://hackmd.io/_uploads/BkAGf3lBa.png) ---- [🔗 GitLab 在7年前即全面採用 Vue.js 的原因](https://about.gitlab.com/blog/2016/10/20/why-we-chose-vue) --- ### 使用 Vite 建立 Vue 專案 #### 或 ### 使用 Online Playground 體驗 ---- ### 使用 Vite 建立 Vue 專案 ---- #### 使用環境 - VS Code - WSL Ubuntu 22.04 (或任何Linux環境) - Node.js V20.9.0 LTS (或任何其他穩定版本) - Yarn 1.22.19 (或任何穩定版本) ---- ```= bash yarn create vite ``` ![image](https://hackmd.io/_uploads/SkLz9BRXT.png) ---- ### 選擇 Vue、JavaScript ![image](https://hackmd.io/_uploads/rkwvXM-Sp.png) ![image](https://hackmd.io/_uploads/BJnO7f-BT.png) ---- ### 初始化 Yarn 專案 ![image](https://hackmd.io/_uploads/HJOXir076.png) ```= bash yarn ``` ---- ### 安裝 Prettier ```bash= yarn add --dev --exact prettier ``` ---- ### 啟動 Vite 環境 ```= bash yarn dev ``` ![image](https://hackmd.io/_uploads/ryyA7GWS6.png) ---- ### 設定 Prettier 創建 .prettierrc.js ```js= export default { semi: true, // 在語句末尾使用分號 singleQuote: true, // 使用單引號而不是雙引號 trailingComma: 'all', // 在物件和數組最後一個元素後使用逗號 printWidth: 80, // 每行最大字符數 tabWidth: 2, // 縮進使用兩個空格 endOfLine: 'lf', // 使用 Linux 系統預設的 LF 換行符 arrowParens: 'always', // 當箭頭函式只有一個參數時,依然補上括號 vueIndentScriptAndStyle: true, // 在 .vue 檔案中,縮進 <script> 和 <style> 標籤內容 }; ``` ---- ### 設定 vscode setting 在 .vscode/settings.json 寫入 ```json= { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "[html]": { "editor.defaultFormatter": "vscode.html-language-features" } } ``` ---- ### 安裝推薦VS Code Extension ---- 在 .vscode/extensions.json 寫入 ```json= { "recommendations": [ "vue.volar", "vue.vscode-typescript-vue-plugin", "formulahendry.auto-rename-tag", "vscode-icons-team.vscode-icons", "ecmel.vscode-html-css", "oderwat.indent-rainbow", "esbenp.prettier-vscode", "albert.tabout", "mhutchie.git-graph", "wallabyjs.console-ninja", "ms-vscode.vscode-typescript-next", "xabikos.javascriptsnippets", "sibiraj-s.vscode-scss-formatter" ] } ``` ---- ![image](https://hackmd.io/_uploads/rkHEUfbH6.png) --- ### 使用 Online Playground 體驗 [🔗 StackBlitz](https://stackblitz.com/edit/vitejs-vite-j1wtka?file=index.html&terminal=dev) --- ### Single-File Components ### (SFC,單檔文件) ### [🔗 官方文檔](https://vuejs.org/guide/scaling-up/sfc.html) ---- - `<template>`: 元件的 HTML 模板 - `<script>`: 主要 JavaScript / TypeScript 程式 - lang:可以指定要使用 JS 或 TS ```html <script> // 預設即JS <script lang="ts"> ``` - `<style>`: CSS 樣式 - lang:選擇指定的 css 預處理器,例如 SCSS、Less ```html <style lang="scss"> ``` - scope:讓樣式只對當前檔案生效,避免非預期的覆寫嵌套的其他 vue 檔 --- ### Composition API vs Optional API ### (組合式與選項式) ### [🔗 官方文檔](https://vuejs.org/guide/extras/composition-api-faq.html) ---- #### Optional API ```javascript= export default{ props: {}, components: {}, data() { // 所有資料 }, computed() { // 點擊增加資料 }, mounted() { // axios取得資料 }, methods() { // 刪除資料 } } ``` ---- #### Composition API ```javascript= export default { setup() { // 所有資料... // 取得資料... // 增加資料... // 刪除資料... } } ``` ---- <img src="https://hackmd.io/_uploads/ryFLAXWrp.png" width="70%"> ---- - 結論: 在 Vue 3 當中,使用 Optional API 跟 Composition API 都是可以的 但強烈推薦 **直接學習 Composition API** --- ### `<scirpt setup>` 語法糖 ### [🔗 官方文檔](https://vuejs.org/api/sfc-script-setup.html#script-setup) ---- 當我們採用 Composition API 以及 SFC 模式開發的時候 我們可以在所有的 vue 檔中 都使用 `<script setup>` 的語法糖 ---- 最傳統的 Optional API ```javascript= <script> export default { data() { return { title: 'Counter', count: 0, }; }, methods: { increment() { this.count++; }, }, }; </script> ``` ---- 使用 Composition API、搭配普通`<script>` ```javascript= <script> export default { setup() { const title = ref('Counter'); const count = ref(0); const increment = () => { count.value++; }; return { title, count, increment, }; }, }; </script> ``` ---- 👍使用 Composition API、搭配 `<script setup>` ```javascript= <script setup> const title = 'Counter'; let count = 0; const increment = () => { count++; }; </script> ``` ---- #### 使用 `<script setup>` 的好處 - 更少的制式內容,更簡潔的代碼。 - 支援純 TypeScript 聲明 props 和自定義事件 - 運行時有更好的性能 (避免不必要的渲染)。 IDE 在類型推導有更好的性能 (減少 Code 中抽取 Type 的工作) ---- #### 結論 <div style="text-align:left">舊的語法:Vue 2、Optional API、傳統 script <br> 若想成為前端工程師,建議看得懂,會修改即可<br> 且可以搭配 AI 工具很好的理解程式碼。</div> <br> <div style="text-align:left"> 而對新手來說,在學習階段建議把大量的時間放在 Vue3、Composition API、script setup <br> 因為未來更新與優化的功能、新語法<br> 必定支援Vue 3 開始的這些語法<br> 只能部分或完全不支援舊語法 </div> [🔗 舉例](https://github.com/vuejs/rfcs/discussions/503) ---- #### 補充 若你看到的範例 Code... - 是 `<script>` - 且包含 `data()、methods()、this、$ 等等` => Optional API - 只要包含 `setup()` => Composition API <br> - 是 `<script setup>` => 一定是 Composition API --- ### Reactivity API ### (響應式數據) ### [🔗 官方文檔](https://vuejs.org/api/reactivity-core.html#reactivity-api-core) ---- - ref() : 綁定 `<template>` 中 DOM 元素或子元件<br>並訪問到這些元素或組件的 prop 和 method,在 `<script>`區段中要取用該變數的數值時,<br>需要使用`.value` ```javascript= import { ref } from 'vue'; const count = ref(0) console.log(count.value) // 0 count.value = 1 console.log(count.value) // 1 ``` 注意,不要使用 let 宣告要成為 ref 的變數或物件 一律使用 const 宣告 ---- #### 自動補上 `.value` 功能 ![image](https://hackmd.io/_uploads/ByJHuHWST.png) ---- - reactive() : 將一般的 JavaScript 物件轉成響應式物件 ```javascript= <script setup> import { reactive } from 'vue'; const person = reactive({ name: 'John Doe', age: 30 }); console.log(person.age); person.age++; console.log(person.age); </script> ``` ---- #### 關於 ref() 與 reactive 取捨 <div style="text-align: left; font-size:32px"> 由於 reactive 只能針對物件進行轉換,並且不需要使用.value,<br> 這會導致在看到呼叫變數時,沒有使用.value的情況<br> 無法直接確定是一般的 JS 變數,還是具有響應式的 reactive,<br> 因此社群上有一流派是完全不用 reactive,<br> 僅使用 ref 完成所有基礎響應式宣告 </div> <br> <div style="text-align: left; font-size:32px"> 而另一派則喜歡將一般的數值、字串等使用 ref,<br> 當使用物件時,一律使用 reactive<br> 作者在接受訪談時表示,團隊可以自己選擇喜歡的風格,<br> 並在同一專案統一風格即可 </div> [🔗作者回應 ref 與 reactive 的取捨](https://www.youtube.com/watch?v=e8Wlv4AGJjk&pp=ygUHI-iwouaIkA%3D%3D) ---- - computed(): 基於數據的變化而自動更新屬性,適合同步操作 example:輸入民國年,即時自動顯示西元年 ```html= <template> <div> <label for="rocYear">輸入民國年:</label> <input type="number" v-model="rocYear" /> <p v-show="rocYear">對應的西元年為:{{ gregorianYear }}</p> </div> </template> <script setup> import { ref, computed } from 'vue'; const rocYear = ref(0); const gregorianYear = computed(() => { return rocYear.value + 1911; }); </script> ``` ---- 對比 純 HTML、JS 如何達成同等效果 ```html= <!DOCTYPE html> <html lang="zh-TW"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>民國年轉西元年</title> </head> <body> <label for="rocYear">輸入民國年:</label> <input type="number" id="rocYearInput" /> <p id="result" style="display: none;"></p> <script> document.getElementById('rocYearInput').addEventListener('input', function() { const rocYear = parseInt(this.value); if (!isNaN(rocYear)) { const gregorianYear = rocYear + 1911; document.getElementById('result').innerText = '對應的西元年為:' + gregorianYear; document.getElementById('result').style.display = 'block'; } else { document.getElementById('result').style.display = 'none'; } }); </script> </body> </html> ``` ---- - watch():監聽一個或多個數據變化,且適合異步操作 example:監聽數值變化,取得變化前後數值 ```html= <script setup> import { ref, watch } from 'vue'; // 創建一個響應式的計數器 const count = ref(0); // 監聽 count 的變化 // watch(count, val => { // console.log('計數器:', val); // }); watch(count, (newValue, oldValue) => { console.log(`計數器從 ${oldValue} 變為 ${newValue}`); }); // 定義增加計數的方法 const increment = () => { count.value++; }; </script> <template> <div> <p>當前計數: {{ count }}</p> <button @click="increment">增加計數</button> </div> </template> ``` ---- computed 跟 watch 取捨 為什麼不 watch 用到底? ---- <table style="font-size:28px"> <thead> <tr> <th style="text-align: center">Computed</th> <th style="text-align: center">Watch</th> </tr> </thead> <tbody> <tr> <td>基於現有數據計算出一個新的值</td> <td>監聽物件的每一次變化,<br>包括數組和物件的內部屬性</td> </tr> <tr> <td>保持數據的純粹性,使用同步操作</td> <td>執行異步操作或複雜邏輯,<br>並在特定數據變化時執行相應的代碼</td> </tr> <tr> <td>基於一組固定的數據屬性<br>而非外部邏輯</td> <td>需要調用API、發送請求等操作</td> </tr> </tbody> </table> ---- 如果都使用 Watch 來取代 Computed... - 過度監聽: 一律使用watch監聽所有數據變化,會讓程式碼變得難以理解和維護 - 異步操作複雜性:如果所有需要監聽變更數據後的操作都使用watch,會使程式碼更難以追蹤與除錯 - 性能考慮: computed具有自動緩存機制,只在依賴的數據發生實際變化時才重新計算。但Watch會在指定數據每次變化時都執行,過度使用 Watch 會導致不必要的效能降低。 --- ### Template Syntax ### (模板語法) ### [🔗 官方文檔](https://vuejs.org/guide/essentials/template-syntax.html#template-syntax) ---- - Text Interpolation(文本插值): 使用雙花括號`{{ }}`可以將數據綁定到模板中 ```html= <script setup> let msg = 'Hello World'; </script> <template> {{ msg }} </template> ``` ---- - JavaScript Expressions: Vue 支援在 `<template>` 使用所有的 JS 表達式<br> 例如: ```javascript= {{ number + 1 }} {{ ok ? 'YES' : 'NO' }} {{ message.split('').reverse().join('') }} <div :id="`list-${id}`"></div> ``` ---- #### Directive (指令) - Content Directives (內容指令) - Rendering Directives (渲染指令) - Attribute Directives (屬性指令) - Event Directives (事件指令) - Form Directives (表單指令) ---- #### Content Directives (內容指令) - v-text:等同 JS 的 textContent ```html= <span v-text="msg"></span> <!-- same as --> <span>{{msg}}</span> ``` - v-html:等同 JS 的 innerHTML 要注意使用,僅用於受信任的內容,切勿用於使用者提供的內容。否則容易導致 XSS 攻擊 [🔗 初探XSS: 攻擊及防禦](https://wert6410.medium.com/xss-%E5%88%9D%E6%8E%A2xss-%E6%94%BB%E6%93%8A%E5%8F%8A%E9%98%B2%E7%A6%A6-f5b9f016ff43) ---- #### Rendering Directives (渲染指令) ---- - v-for:多次渲染 element 或 template block ```html= <template> <div> <h1>購物車</h1> <ul> <li v-for="product in cart" :key="product.id"> {{ product.name }} - {{ product.price }} 元 </li> </ul> </div> </template> <script setup> import { ref } from 'vue'; const cart = ref([ { id: 1, name: '商品一', price: 100 }, { id: 2, name: '商品二', price: 150 }, { id: 3, name: '商品三', price: 200 }, ]); </script> ``` 想想看,為什麼不直接寫在 template 裡面, 而寫在 `<script setup>`,再傳遞給 `<template>`? ---- - v-if - v-else-if - v-else 同程式語言的 if...else if...else 達成指定條件才會渲染內容 ```html= <script setup> import { ref } from 'vue'; const status = ref(''); </script> <template> <div> <label for="statusSelect">選擇狀態:</label> <select id="statusSelect" v-model="status"> <option value="success">成功</option> <option value="loading">載入中</option> <option value="error">錯誤</option> </select> <p> 狀態: <span v-if="status === 'success'">✅操作成功!</span> <span v-else-if="status === 'loading'">💭載入中....</span> <span v-else>❌錯誤</span> </p> </div> </template> ``` ---- - v-show:一律渲染並隱藏,達成指定條件才會顯示 ```html= <script setup> import { ref } from 'vue'; const isVisible = ref(false); </script> <template> <div> <button @click="isVisible = !isVisible">切換顯示</button> <div v-show="isVisible"> <p>這是可切換顯示的內容。</p> </div> </div> </template> ``` ---- #### v-if 跟 v-show 的使用取捨 - v-if 在條件達成的時候才渲染內容 - v-show 先渲染好,條件達成後從隱藏改為顯示 <br> <div style="text-align:left;font-size:32px"> 預先準備好能確保使用者達成條件時馬上看到內容,<br> 但如果內容過於大量,<br> 那預先加載過多使用者不會都需要查看的內容,<br> 反而會導致浪費在預先加載的等待時間 </div> ---- #### Props Directives (屬性指令) ---- - v-bind:動態決定 element 的 props ```html= <script setup> import { ref } from 'vue'; const customColor = ref('red'); </script> <template> <input type="text" v-model="customColor" /> <p v-bind:style="`color:${customColor}`">hello</p> <p :style="`color:${customColor}`">hello</p> </template> ``` ---- #### Event Directives (事件指令) ---- - v-on:監聽 DOM 事件,觸發相應 event ```html= <script setup> const handleClick = () => { console.log('按鈕被點擊了!'); }; </script> <template> <button v-on:click="handleClick">點擊我</button> <button @click="handleClick">點擊我</button> </template> ``` ---- #### Form Directives (表單指令) ---- - v-model:實現雙向數據綁定。讓使用者介面與瀏覽器之間即時同步數據。 ```html= <script setup> import { ref } from 'vue'; const data = ref({ name: '', email: '', subscribe: false, }); </script> <template> <form @submit.prevent="submitForm" style="display: flex; flex-direction: column; align-items: flex-start" > <div> <label for="name">姓名:</label> <input id="name" v-model="data.name" required /> </div> <div> <label for="email">電子郵件:</label> <input id="email" v-model="data.email" type="email" required /> </div> <div> <label for="subscribe">訂閱最新消息</label> <input id="subscribe" v-model="data.subscribe" type="checkbox" /> </div> </form> <p> 嗨!{{ data.name }} <br /> 您的電子郵件是 {{ data.email }} <br /> 您選擇 {{ data.subscribe ? '訂閱' : '不訂閱' }} 最新消息 </p> </template> ``` ---- ### Modifiers ### (修飾符) ### [🔗 官方文檔](https://vuejs.org/guide/essentials/forms.html#modifiers) ---- Modifiers 是一種用來修改指令行為的方式 在指令後面以'.xxx'表示,用來調整指令行為或效果 ![image](https://hackmd.io/_uploads/H1nJBefrp.png) ---- 例如: ```html= <input v-model.trim="msg" /> <input v-model.number="age" /> <button @click.once="handleClick">點擊一次</button> ``` --- ## 結語和問答 ---- ### 補充學習資源 ---- ### Q & A
{"slideOptions":"{\"transition\":\"concave\",\"allottedMinutes\":100}","title":"Vue3 基礎課程","description":"用來定義一個 element 的 box model 大小計算方式","contributors":"[{\"id\":\"f8142aa2-66aa-4867-821d-2f1ffff7a7ba\",\"add\":17564,\"del\":2370}]"}
    1415 views
   owned this note