# Vue 3 介紹 ![vue-js](https://hackmd.io/_uploads/r1LWSYBVA.png) 1. 快速建構<font color="#FF7F50">網頁應用</font>的<font color="#1E90FF">前端</font>框架 2. 語法簡潔、容易學習、社群龐大 ## 簡介 **Vue 3** 是一個用來構建網頁應用的前端框架。 屬於漸進式框架(Progressive Framework) 它可以幫助我們更簡單、更高效地開發互動性強的網站。 即使你沒有程式設計的背景,也可以理解它的一些基本概念和優點。 ### 為什麼要使用 Vue 3? 1. **簡單易學**:Vue 3 的設計讓它非常容易學習和使用。即使是新手,也能快速上手,開始構建漂亮的網頁。 2. **高效**:它可以幫助我們更高效地開發應用,減少手動操作和重複勞動。這意味著我們可以用更少的代碼做更多的事情。 3. **靈活**:Vue 3 非常靈活,適用於從簡單的單頁應用到複雜的大型專案。你可以根據需要選擇使用哪些功能,不需要一次性學習全部內容。 ### Vue 3 的基本概念 1. **反應式資料綁定**:這是 Vue 最重要的特性之一。當資料發生變化時,界面會自動更新。比如,你改變了用戶名,那麼顯示用戶名的地方會自動變成新的名字。 2. **組件**:Vue 應用是由一個個組件組成的。每個組件都是獨立的小程式,可以包含自己的資料、模板和邏輯。這讓我們可以把應用拆分成很多小部分,每個部分各自負責一個功能,這樣開發和維護起來就更簡單。 3. **指令**:Vue 提供了一些特殊的標記,叫做指令,用來在 HTML 標籤上添加特定的功能。比如 `v-if` 用來條件顯示某個元素,`v-for` 用來循環渲染列表。 ### 簡單範例 下面是一個簡單的 Vue 3 應用範例,它顯示了一個可以點擊的按鈕,點擊後計數會增加。 ```vue <template> <div> <p>你已經點擊了 {{ count }} 次</p> <button @click="increment">點擊我</button> </div> </template> <script setup> import { ref } from 'vue'; const count = ref(0); function increment() { count.value++; } </script> <style> button { font-size: 16px; padding: 10px 20px; } </style> ``` ### 範例解釋 1. **模板 (template)**: - 這裡定義了 HTML 結構,包括一段顯示點擊次數的文字和一個按鈕。 - 使用 `{{ count }}` 來顯示計數變量 `count` 的值。 - 使用 `@click="increment"` 來綁定按鈕的點擊事件,當按鈕被點擊時,會調用 `increment` 函數。 2. **腳本 (script setup)**: - 使用 `import { ref } from 'vue';` 引入 Vue 的 `ref` 函數,用來創建一個反應式變量 `count`。 - 定義了一個 `increment` 函數,每次調用這個函數都會讓 `count` 的值增加 1。 3. **樣式 (style)**: - 定義了一些基本的按鈕樣式,使按鈕看起來更美觀。 ### 總結 Vue 3 是一個強大且易於使用的前端框架,可以幫助我們更高效地構建互動性強的網頁應用。它通過反應式資料綁定、組件化開發和簡單的指令,讓我們可以更輕鬆地處理複雜的網頁邏輯。即使你沒有程式設計背景,也能夠理解並開始使用 Vue 3 來創建自己的網頁應用。 ## 認識網頁應用程式(Web Application) & 前端框架 > 網頁應用程式是通過網頁瀏覽器訪問和使用的應用軟體,不需要在本地設備上安裝。 ![Web Application Diagram](https://shourai.io/wp-content/uploads/2020/07/kindpng_1272110.png) ### 主要特點 1. **瀏覽器使用**: - 打開瀏覽器,輸入網址即可使用應用程式。 ![Browser](https://marketing.create-cdn.net/assets/browserlogos2021.png) 2. **不需安裝**: - 無需在設備上安裝或更新,只需訪問網址即可使用最新版本。 ![No Installation](https://static.thenounproject.com/png/1640816-200.png) 3. **跨平台**: - 可以在不同設備(電腦、手機、平板)和操作系統(Windows、macOS、Android、iOS)上使用。 ![Cross Platform](https://cdn.cleancommit.io/blog/2022/12/cross-app.png) ### 常見範例 - **Gmail**:用來收發電子郵件。 ![Gmail](https://upload.wikimedia.org/wikipedia/commons/thumb/7/7e/Gmail_icon_%282020%29.svg/100px-Gmail_icon_%282020%29.svg.png) - **Facebook**:用來社交和分享信息。 ![Facebook](https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Facebook.svg/200px-Facebook.svg.png) - **Google Docs**:用來在線編寫和儲存文檔。 ![Google Docs](https://upload.wikimedia.org/wikipedia/commons/a/a6/Google_Docs_Editors_logo.png) ### 總結 ![Web Application](https://hackmd.io/_uploads/Hylas9S4C.png) :::info 舉例:User 在網頁的搜尋內打上「蘋果」並按下送出,此時前端會將尋找「蘋果」這件事告訴後端,而後端就要根據指令告訴資料庫去尋找「蘋果」,資料庫找到「蘋果」後再給後端,後端再告訴前端,前端就會顯示跟「蘋果」相關的資料給 User。 ::: # Vue3 + Vite 專案建置 & 套件 ## 前置 > 版本請依當下的版本為原則 1. [VsCode](https://code.visualstudio.com/download) ![vscode](https://upload.wikimedia.org/wikipedia/commons/1/1c/Visual_Studio_Code_1.35_icon.png) 2. [Vetur](https://marketplace.visualstudio.com/items?itemName=Vue.volar) :VsCode內的套件 ![image](https://hackmd.io/_uploads/HkwpRqrNR.png) 3. [Vue Devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd):Chrome的插件 ![image](https://hackmd.io/_uploads/rJy8ksrVC.png) 4. [NodeJS](https://nodejs.org/en) ![image](https://miro.medium.com/v2/resize:fit:1400/1*AYoRr9oEPzZb6Lfpoy-yRQ.png) ## 專案建置 :::success 此文件版本: Node.js:v20.13.1 npm:v10.5.2 Vue:^3.4.21 Vite:^5.2.0 ::: 1. 在桌面建立專案資料夾並在此資料夾位置內使用以下指令[(參考官網)](https://cn.vuejs.org/guide/quick-start.html) ``` npm create vite@latest ``` :::warning @latest:指的是安裝當下現行最新版本 ::: 2. 執行後會出現以下選項(若選項與下方不同請依官網為主) ```cmd! ✔ Project name: … <專案名稱> ✔ Select a framework: >> Use arrow-keys. Return to submit. // 這裡是選擇該專案要應用的前端框架 vanilla > vue react preact lit-element svelte ✔ Select a variant? >> Use arrow-keys. Return to submit. // 使用什麼樣的開發語言? > vue vue-ts // 如果會 TypeScript 可以選這個 Scaffolding project in ./<專案名稱>... Done. cd <專案名稱> npm install npm run dev ``` 完成後終端機範例畫面如下: ![image](https://hackmd.io/_uploads/SkLVriBNA.png) ## 專案目錄結構 > 當你使用 Vue 3 和 Vite 創建一個專案時,會生成一個預設的目錄結構。 > 這個目錄結構看起來可能有點複雜,但其實每個文件和資料夾都有其特定的用途。 ![image](https://hackmd.io/_uploads/rksA9oHER.png) ```! my-vue3-vite-app/ <- 專案資料夾名稱 ├── .vscode/ <- 針對此專案資料夾在 VsCode 內的設定,因每人設定不同故不另外說明。 ├── node_modules/ ├── public/ │ └── favicon.ico ├── src/ │ ├── assets/ │ ├── components/ │ ├── views/ <- 如果沒有這資料夾可自行創建 │ ├── App.vue │ ├── main.js │ └── router.js ├── .gitignore ├── index.html ├── package.json ├── README.md <- 可自行決定是否刪除 ├── vite.config.js └── yarn.lock (or package-lock.json) <- 在專案執行 npm install 就會自行建置 ``` ### 各部分說明 1. **node_modules/**: - 這裡存放所有專案的相依套件(dependencies)。你不用手動修改這裡的內容,這是由 `npm` 或 `yarn` 自動生成的。 2. **public/**: - 這個資料夾裡面的文件會被原樣複製到最終生成的應用程式中。通常用來存放靜態資源,比如網站的 `favicon.ico`。 3. **src/**: - 這是你主要工作的地方。所有的 Vue 組件、JavaScript 文件、CSS 文件等都放在這裡。 - **assets/**: - 放置靜態資源,如圖片、字體等。 - **components/**: - 存放 Vue 的組件。組件是應用的可重用部分,比如按鈕、表單等。 - **views/**: - 放置應用的主要視圖(頁面)。每個視圖代表一個頁面,比如首頁、關於頁等。 - **App.vue**: - 主應用組件,是整個應用的根組件。 - **main.js**: - 應用的入口文件。這裡是應用開始運行的地方,通常會在這裡創建 Vue 實例並掛載到 DOM 上。 - **router.js**: - 設定應用的路由(如果使用 Vue Router)。這裡定義了應用中不同 URL 對應的視圖。 4. **.gitignore**: - 設定哪些文件和資料夾在使用 Git 進行版本控制時應該被忽略。比如 `node_modules/` 通常會被忽略,因為它們可以通過 `package.json` 重新生成。 5. **index.html**: - 應用的主 HTML 文件。這裡定義了應用的基本結構,Vue 會在這裡插入渲染後的內容。 6. **package.json**: - 專案的配置文件,包含專案名稱、版本、依賴套件、腳本等信息。 7. **README.md**: - 專案的說明文件,通常用來描述專案的用途、安裝和使用方法。 8. **vite.config.js**: - Vite 的配置文件,用來配置 Vite 的各種選項,比如別名、插件等。 9. **yarn.lock(或 package-lock.json)**: - 鎖定文件,記錄了當前專案中所有安裝的相依套件的具體版本,確保每次安裝時都一致。 ### 總結 - **`src/`** 是你主要編寫程式碼的地方。 - **`node_modules/`** 和 **`package.json`** 管理你的依賴包。 - **`public/`** 用來存放不需要編譯的靜態文件。 - **配置文件**(如 **`.gitignore`**、**`vite.config.js`**)用來配置你的專案。 :::success #### `package.json` 內容說明 當你打開一個使用 npm(Node Package Manager)或 yarn 管理的 JavaScript 專案時,你會看到一個名為 `package.json` 的文件。這個文件包含了專案的重要資訊和設定。以下是對 `package.json` 文件內容的簡單白話解釋: ```json { "name": "my-vue3-vite-app", "version": "1.0.0", "description": "A simple Vue 3 + Vite project", "main": "index.js", "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview" }, "dependencies": { "vue": "^3.2.0" }, "devDependencies": { "vite": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0" }, "license": "MIT" } ``` #### 每個部分的解釋 1. **`name`**: - 專案的名稱。這個名稱應該是唯一的,通常使用小寫字母和連字符(-)。 ```json "name": "my-vue3-vite-app" ``` 2. **`version`**: - 專案的版本號。通常遵循語義化版本規範(semver),比如 `1.0.0`。 ```json "version": "1.0.0" ``` 3. **`description`**: - 專案的簡短描述,說明這個專案是做什麼的。 ```json "description": "A simple Vue 3 + Vite project" ``` 4. **`main`**: - 專案的入口文件,通常是應用的主文件。對於大多數應用來說,這一行可以忽略。 ```json "main": "index.js" ``` 5. **`scripts`**: - 定義一些快捷命令,你可以在命令行中運行這些命令來執行常見的任務,比如啟動開發伺服器、打包應用等。 - 例如:在終端機內打上 `npm run dev` ,就會執行`scripts`內的`dev`動作。 ```json "scripts": { "dev": "vite", // 啟動開發伺服器 "build": "vite build", // 打包應用 "serve": "vite preview" // 預覽打包後的應用 } ``` 6. **`dependencies`**: - 專案的生產環境依賴包。這些是應用在運行時需要的套件。 ```json "dependencies": { "vue": "^3.2.x" // Vue 3 框架 } ``` 7. **`devDependencies`**: - 專案的開發環境依賴包。這些是開發應用時需要的工具和庫,但在應用最終運行時並不需要。 ```json "devDependencies": { "vite": "^4.0.0", // Vite 構建工具 "@vitejs/plugin-vue": "^4.0.0" // Vite 的 Vue 插件 } ``` 8. **`license`**: - 專案的許可證,說明這個專案的版權和使用條款。`MIT` 是一種常見的開源許可證。 ```json "license": "MIT" ``` #### 總結 - **`name`** 和 **`version`**:專案的名稱和版本號。 - **`description`**:簡單介紹專案。 - **`main`**:專案的入口文件(通常可以忽略)。 - **`scripts`**:一些快捷命令,方便你執行常見任務。 - **`dependencies`** 和 **`devDependencies`**:專案需要的相依套件,分為運行時和開發時的依賴。 - **`license`**:專案的許可證。 ::: ## Vue 組件架構 ![image](https://hackmd.io/_uploads/SyZf7hSV0.png) ### `xxx.vue` 文件結構 一個 Vue 組件文件通常包含三個主要部分:`template`、`script` 和 `style`。以下是一個簡單的例子: ```vue <template> <div class="example"> <h1>{{ message }}</h1> <button @click="updateMessage">Click me</button> </div> </template> <script> export default { data() { return { message: 'Hello, Vue 3!' }; }, methods: { updateMessage() { this.message = 'You clicked the button!'; } } }; </script> <style> .example { text-align: center; } h1 { color: blue; } </style> ``` ### 每個部分的解釋 1. **`<template>` 部分**: - 這部分定義了組件的 HTML 結構,也就是這個組件在頁面上會如何顯示。 - 使用雙花括號 `{{ }}` 來插入變量或表達式的值。 - 使用 Vue 指令(如 `@click`)來添加事件處理。 ```vue <template> <div class="example"> <h1>{{ message }}</h1> <button @click="updateMessage">Click me</button> </div> </template> ``` - **`<div class="example">`**:這是一個包含了其他元素的容器,並且有一個名為 `example` 的 CSS 類。 - **`<h1>{{ message }}</h1>`**:這個 `h1` 標籤會顯示 `message` 變量的內容。 - **`<button @click="updateMessage">Click me</button>`**:這個按鈕在被點擊時會調用 `updateMessage` 方法。 2. **`<script>` 部分**: - 這部分定義了組件的邏輯,包括數據、方法、生命周期鉤子等。 - 使用 `export default` 將組件的配置對象導出。 ```vue <script> export default { data() { return { message: 'Hello, Vue 3!' }; }, methods: { updateMessage() { this.message = 'You clicked the button!'; } } }; </script> ``` - **`data()`**:這是一個函數,返回一個包含組件狀態的對象。在這個例子中,狀態中有一個 `message` 變量。 - **`methods`**:這裡定義了組件中的方法。在這個例子中,有一個 `updateMessage` 方法,當按鈕被點擊時,它會更新 `message` 的值。 3. **`<style>` 部分**: - 這部分定義了組件的樣式,使用標準的 CSS 語法。 - 樣式可以是全局的或是只針對這個組件的。 ```vue <style> .example { text-align: center; } h1 { color: blue; } </style> ``` - **`.example`**:這個類選擇器應用於所有具有 `example` 類的元素。在這個例子中,它會使容器的文本居中。 - **`h1`**:這個元素選擇器應用於所有 `h1` 標籤,將它們的文字顏色設為藍色。 ### 總結 - **`<template>`**:定義組件的 HTML 結構,描述組件如何顯示。 - **`<script>`**:定義組件的邏輯和數據,描述組件如何運作。 - **`<style>`**:定義組件的 CSS 樣式,描述組件如何呈現。 # 一、建立組件與基本資料的綁定:v-model、ref ## 建立組件 & 引入 1. 在 /components 內建立 componentsA.vue 並打上快捷鍵 `vue` + Tab(或者 Enter) ![image](https://hackmd.io/_uploads/SkBg1pHEA.png) 2. 在 App.vue 內引入 componentsA.vue ![image](https://hackmd.io/_uploads/BJIK-6B4A.png) ## 基本資料的綁定 > 當使用 Vue 3 來開發應用時,會遇到一些重要的概念,例如資料綁定(單向和雙向)、`v-model` 和 `ref`。 > 這些概念能讓你更有效率地操作和管理資料。 ### 資料綁定(單向綁定) **單向綁定**是一種從資料到視圖的資料流動方式。這意味著資料從 JavaScript 邏輯(通常在 `data` 或 `computed` 屬性中)流向 HTML 模板,用於顯示或渲染。 **範例:** ```vue! <template> <div> <p>資料綁定(單向綁定)</p> {{ data }} </div> </template> <script setup> // 定義 data 變數且這裡的 data 是一個常數,不可更改 const data = 'Hello World'; </script> ``` **說明:** - 在這個例子中,`data` 資料從 JavaScript 流向模板,顯示在 `{{ data }}` 標籤中。 - 這是一種單向資料綁定,因為資料只從 JavaScript 流向模板,不能反過來修改。 ### 資料綁定(雙向綁定) `v-model` **雙向綁定**允許資料在 JavaScript 和 HTML 模板之間雙向流動。使用 `v-model` 指令,可以實現輸入元素(如輸入框)和 JavaScript 資料之間的雙向綁定。 **範例:** ```vue <template> <div> <p>資料綁定(雙向綁定)</p> <input type="text" v-model="text" /> <p>{{ text }}</p> </div> </template> <script setup> import { ref } from '@vue/reactivity'; // 引入 ref // 使用 ref 宣告 text 變數 const text = ref('初始值'); // 使用 ref 宣告 text 變數 </script> ``` **說明:** - 在這個例子中,`v-model` 綁定 `input` 元素和 `message` 資料。 - 當你在輸入框中輸入內容時,`message` 資料會自動更新,並顯示在 `<p>` 標籤中。 - 這是一種雙向資料綁定,因為資料可以在 JavaScript 和模板之間來回流動。 ### `ref` **`ref`** 是 Vue 中用來直接訪問 DOM 元素或組件實例的一種方法。它可以讓你在模板中標記某個元素,然後在 JavaScript 中輕鬆地操作這個元素。 **範例:** ```vue <template> <div> <input ref="inputElement" type="text" /> <button @click="focusInput">Focus Input</button> </div> </template> <script> export default { methods: { focusInput() { this.$refs.inputElement.focus(); } } }; </script> ``` **說明:** - 在這個例子中,`ref="inputElement"` 將 `input` 元素標記為 `inputElement`。 - 使用 `this.$refs.inputElement` 可以在 JavaScript 中訪問這個 `input` 元素。 - 當按下按鈕時,`focusInput` 方法會被調用,使 `input` 元素獲得焦點。 ### 總結 - **單向綁定**:資料從 JavaScript 流向模板(例如 `{{ message }}`)。 - **雙向綁定(v-model)**:資料在 JavaScript 和模板之間雙向流動(例如 `v-model="message"`)。 - **`ref`**:用來在 JavaScript 中直接訪問 DOM 元素或組件實例。 這些概念使得 Vue 3 能夠更靈活和強大地操作和管理資料,讓你的應用更加動態和互動。 :::success 在 Vue 3 中,`v-model` 和 `ref` 是常用的工具,然而在特定情境下,有一些方法可以進一步優化效能和代碼可讀性。 兩種情境模擬:帳號登入和基本資料輸入,並展示如何使用更優化的方法。 ### 情境一:帳號登入 在帳號登入的情況下,`v-model` 非常方便,因為你需要雙向綁定用戶輸入的帳號和密碼。然而,如果希望進一步優化效能,特別是對於大型表單,可以考慮僅在必要時更新資料,例如使用事件處理而不是雙向綁定。 #### 使用 `v-model` ```vue <template> <form @submit.prevent="login"> <input v-model="username" type="text" placeholder="Username" /> <input v-model="password" type="password" placeholder="Password" /> <button type="submit">Login</button> </form> </template> <script setup> import { ref } from 'vue'; const username = ref(''); const password = ref(''); const login = () => { console.log('Logging in with', username.value, password.value); // 實際的登入邏輯 }; </script> <style> /* 樣式 */ </style> ``` #### 使用事件處理器(優化效能) ```vue <template> <form @submit.prevent="login"> <input @input="updateUsername" type="text" placeholder="Username" /> <input @input="updatePassword" type="password" placeholder="Password" /> <button type="submit">Login</button> </form> </template> <script setup> import { ref } from 'vue'; const username = ref(''); const password = ref(''); const updateUsername = (event) => { username.value = event.target.value; }; const updatePassword = (event) => { password.value = event.target.value; }; const login = () => { console.log('Logging in with', username.value, password.value); // 實際的登入邏輯 }; </script> <style> /* 樣式 */ </style> ``` ### 情境二:基本資料輸入 對於基本資料輸入表單,`v-model` 也是非常方便的選擇。不過,如果表單數據非常多,使用 `ref` 來手動管理資料更新,並僅在需要時更新資料,可以提高效能。 #### 使用 `v-model` ```vue <template> <form @submit.prevent="saveData"> <input v-model="firstName" type="text" placeholder="First Name" /> <input v-model="lastName" type="text" placeholder="Last Name" /> <input v-model="email" type="email" placeholder="Email" /> <button type="submit">Save</button> </form> </template> <script setup> import { ref } from 'vue'; const firstName = ref(''); const lastName = ref(''); const email = ref(''); const saveData = () => { console.log('Saving data:', { firstName: firstName.value, lastName: lastName.value, email: email.value }); // 實際的保存邏輯 }; </script> <style> /* 樣式 */ </style> ``` #### 使用 `ref` 和事件處理器(優化效能) ```vue! <template> <form @submit.prevent="saveData"> <input ref="firstNameInput" @input="updateData('firstName')" type="text" placeholder="First Name" /> <input ref="lastNameInput" @input="updateData('lastName')" type="text" placeholder="Last Name" /> <input ref="emailInput" @input="updateData('email')" type="email" placeholder="Email" /> <button type="submit">Save</button> </form> </template> <script setup> import { ref } from 'vue'; const firstName = ref(''); const lastName = ref(''); const email = ref(''); const firstNameInput = ref(null); const lastNameInput = ref(null); const emailInput = ref(null); const updateData = (field) => (event) => { if (field === 'firstName') firstName.value = event.target.value; if (field === 'lastName') lastName.value = event.target.value; if (field === 'email') email.value = event.target.value; }; const saveData = () => { console.log('Saving data:', { firstName: firstName.value, lastName: lastName.value, email: email.value }); // 實際的保存邏輯 }; </script> <style> /* 樣式 */ </style> ``` ### 差異和原因 - **`v-model` 簡單直接**:適合簡單和中等大小的表單,因為它提供了簡單的雙向綁定,易於閱讀和編寫。 - **事件處理器優化效能**:在大型表單或性能敏感的情況下,使用事件處理器來手動更新資料,可以減少不必要的資料綁定操作,提高應用效能。 - **`ref` 精細控制**:使用 `ref` 可以精細控制 DOM 元素,有助於在特定情況下(如焦點管理或非綁定更新)提高性能和靈活性。 ### 什麼是 `ref` 和 `reactive`? 在 Vue 3 中,`ref` 和 `reactive` 是用來創建響應式資料的兩種方法。這些方法可以讓我們的資料變得“活”起來,當資料改變時,自動更新使用這些資料的地方。 #### 簡單比喻 - **`ref`** 就像是用來存放單個值的小盒子,裡面的東西變了,盒子就會告訴我們變了什麼。 - **`reactive`** 就像是用來存放整個物件的大盒子,物件中的任何屬性變了,這個大盒子也會告訴我們變了什麼。 ### `ref` 和 `reactive` 的基本用法 #### `ref` - 用來創建單個響應式變量。 - 當你有一個數字、字串、布林值或其他簡單資料類型時,使用 `ref`。 **範例**: ```vue <template> <div> <p>計數: {{ count }}</p> <button @click="increment">增加</button> </div> </template> <script setup> import { ref } from 'vue'; const count = ref(0); function increment() { count.value++; } </script> ``` **解釋**: - `ref(0)` 創建了一個初始值為 0 的響應式變量 `count`。 - 當我們點擊按鈕時,`count` 的值增加,頁面上的顯示會自動更新。 #### `reactive` - 用來創建響應式物件。 - 當你有一個包含多個屬性的物件時,使用 `reactive`。 **範例**: ```vue <template> <div> <p>用戶名: {{ user.name }}</p> <button @click="changeName">改變名字</button> </div> </template> <script setup> import { reactive } from 'vue'; const user = reactive({ name: 'John', age: 30 }); function changeName() { user.name = 'Jane'; } </script> ``` **解釋**: - `reactive({ name: 'John', age: 30 })` 創建了一個包含 `name` 和 `age` 屬性的響應式物件 `user`。 - 當我們改變 `user.name` 的值時,頁面上的顯示會自動更新。 ### 使用差異 1. **資料類型**: - `ref`:用於單個值(數字、字串、布林值等)。 - `reactive`:用於物件或陣列。 2. **語法**: - `ref` 創建的變量需要通過 `.value` 來訪問和修改。 - `reactive` 創建的物件直接訪問和修改其屬性。 **範例比較**: - **`ref`**: ```javascript const count = ref(0); console.log(count.value); // 訪問值 count.value = 1; // 修改值 ``` - **`reactive`**: ```javascript const user = reactive({ name: 'John', age: 30 }); console.log(user.name); // 訪問屬性 user.name = 'Jane'; // 修改屬性 ``` ### 總結 - 使用 `ref` 來創建單個響應式變量,適用於簡單的資料類型。 - 使用 `reactive` 來創建響應式物件,適用於包含多個屬性的物件或陣列。 - `ref` 變量需要通過 `.value` 來訪問和修改,而 `reactive` 則直接訪問和修改其屬性。 這些工具讓我們在 Vue 3 中更方便地管理和響應資料變化,使得應用更加動態和互動。 這些方法可以根據具體情況靈活選擇,以達到最佳的效能和代碼可讀性。 ::: # 二、條件渲染顯示與點擊事件綁定 v-if、v-else、v-show ## 什麼是條件渲染顯示與點擊事件綁定 >當你在用 Vue.js 開發一個網站或應用程式時,可能需要根據不同的>情況來顯示或隱藏某些部分。 >這種根據條件來顯示或隱藏內容的功能稱為**條件渲染**。 >Vue.js 提供了一些簡單的工具來實現這種功能,比如 `v-if`、`v-else` 和 `v-show`。 ### 什麼是條件渲染? **條件渲染**就像在做選擇題時,如果符合某個條件,才會顯示特定的內容。 舉個例子:如果今天是星期一,你會顯示「今天是星期一」,否則就顯示「今天不是星期一」。 ### `v-if` 和 `v-else` - **`v-if`**:如果條件成立(為真),就顯示這部分內容;如果條件不成立(為假),這部分內容就不會出現在頁面上。 - **`v-else`**:和 `v-if` 搭配使用,當 `v-if` 條件不成立時,`v-else` 的內容才會顯示出來。 #### `v-if` 範例: ```vue! <template> <div> <button @click="changeDay(1)">切換星期</button> <button v-on:click="changeDay(2)">切換星期</button> <p v-if="isMonday === 1">今天是星期一</p> <p v-if="isMonday === 2">今天不是星期一</p> </div> </template> <script setup> import { ref } from 'vue'; // 將 isMonday 初始值設定為1。 const isMonday = ref(1); // 使用 @click 事件後使 changeDay(1 or 2) 傳到 index 內並依參數內容發生變化 const changeDay = (index) => { isMonday.value = index } </script> <style> /* 樣式 */ </style> ``` :::success `@click=` 和 `v-on:click` 是同樣的 ::: #### `v-if` 和 `v-else` 範例: ```vue <template> <div> <button @click="isMonday = !isMonday">切換星期</button> <p v-if="isMonday">今天是星期一</p> <p v-else>今天不是星期一</p> </div> </template> <script setup> import { ref } from 'vue'; const isMonday = ref(true); </script> <style> /* 樣式 */ </style> ``` **說明:** - 當 `isMonday` 為 `true` 時,顯示「今天是星期一」。 - 當 `isMonday` 為 `false` 時,顯示「今天不是星期一」。 - 點擊按鈕會切換 `isMonday` 的值。 ### `v-show` - **`v-show`**:根據條件顯示或隱藏內容。和 `v-if` 不同的是,`v-show` 不會從 DOM 中刪除元素,只是透過 CSS 的 `display` 屬性來隱藏或顯示元素。 #### 範例: ```vue <template> <div> <button @click="changeDay(1)">星期一</button> <button v-on:click="changeDay(2)">星期二</button> <button v-on:click="changeDay(3)">星期三</button> <p v-show="isMonday === 1">今天是星期一</p> <p v-show="isMonday === 2">今天是星期二</p> <p v-show="isMonday !== 1 && isMonday !== 2">今天是星期三</p> </div> </template> <script setup> import { ref } from 'vue'; // 將 isMonday 初始值設定為1。 const isMonday = ref(1); // 使用 @click 事件後使 changeDay(1 or 2) 傳到 index 內並依參數內容發生變化 const changeDay = (index) => { isMonday.value = index; }; </script> <style></style> ``` **說明:** - 當 `isVisible` 為 `true` 時,文字顯示。 - 當 `isVisible` 為 `false` 時,文字隱藏。 - 點擊按鈕會切換 `isVisible` 的值。 ### 總結 - **`v-if`**:根據條件來添加或刪除 DOM 元素。用於元素在 DOM 中的添加和移除。 - **`v-else`**:和 `v-if` 搭配使用,當 `v-if` 條件不成立時顯示。 - **`v-show`**:根據條件來顯示或隱藏元素,但不會從 DOM 中移除元素,只是改變 CSS `display` 屬性。 :::success ## 最後複習 v-if、v-else、v-show 以及實際應用 > 當你在設計一個網站或應用時,有時你需要根據某些條件來決定是否顯示特定的內容。 ### 差異: - **`v-if`**:當條件為真時,會將元素添加到 DOM 中;當條件為假時,會從 DOM 中刪除元素。它是一個完全的切換,元素存在與否完全取決於條件。 - **`v-else`**:和 `v-if` 一起使用,用於當 `v-if` 條件不成立時,顯示另外一個元素。 - **`v-show`**:不論條件是真還是假,元素始終存在於 DOM 中,但是通過 CSS 控制其 `display` 屬性來顯示或隱藏它。即使條件為假,元素也只是隱藏了,但仍然存在於 DOM 中。 ### 實際應用: - **`v-if` 的實際應用**:當你有一些很重要的內容,只在特定情況下才想顯示時,可以使用 `v-if`。 - 例如:當用戶登入後才顯示其個人資訊。 - **`v-else` 的實際應用**:當你想在 `v-if` 條件不成立時顯示另外一個元素時,可以使用 `v-else`。 - 例如:當用戶未登入時顯示登入表單,登入後顯示用戶資訊。 - **`v-show` 的實際應用**:當你想在某些情況下隱藏元素,但仍然希望它佔用空間,可以使用 `v-show`。 - 例如:在點擊按鈕後顯示或隱藏一個下拉菜單。 ### 總結: - **`v-if`**:完全地添加或刪除元素,適用於元素在 DOM 中的添加和移除。 - **`v-else`**:和 `v-if` 搭配使用,在 `v-if` 條件不成立時顯示另外一個元素。 - **`v-show`**:元素始終存在於 DOM 中,通過 CSS 控制其顯示或隱藏。適用於需要頻繁顯示或隱藏的元素,但不希望影響其在 DOM 中的佔用空間。 這些工具讓你能夠根據不同的條件來動態地顯示或隱藏內容,從而為用戶提供更好的使用體驗。 ::: # 三、迴圈渲染與 key 屬性 v-for ## 什麼是迴圈渲染與 key 屬性 > 當我們使用 Vue.js 來建構應用程式時,`v-for` 是一個非常有用的指令,允許我們輕鬆地迭代並渲染列表或物件中的項目。 > `key` 屬性則有助於 Vue 快速有效地更新渲染的元素。 ### 範例 1: 使用陣列與 `v-for` 和 `key` 假設我們有一個名字的列表,我們想要顯示這些名字。 範例代碼: ```vue! <template> <ul> <li v-for="(name, index) in names" :key="index">{{index}}. {{ name }}</li> </ul> </template> <script setup> const names = ['Alice', 'Bob', 'Charlie']; </script> <style></style> ``` 畫面結果: ![image](https://hackmd.io/_uploads/SJhwp98N0.png) #### 說明 - **v-for**: 我們使用 `v-for` 指令來迭代 `names` 陣列中的每個名字。 - **key**: `key` 屬性設置為每個項目的索引,這樣可以幫助 Vue 識別每個項目並有效地更新 DOM。 ### 範例 2: 使用物件與 `v-for` 和 `key` 如果我們有一個物件,其中包含一些項目並且每個項目都有一個<font color="red">唯一的 ID 或者值</font>,我們可以這樣做: 範例代碼: ```vue! <template> <ul> <li v-for="item in items" :key="item.id">{{item.id}} {{ item.name }}</li> </ul> </template> <script setup> const items = [ { id: 1, name: 'Item 1' }, { id: 2, name: 'Item 1' }, { id: 3, name: 'Item 2' }, { id: 4, name: 'Item 3' }, ]; </script> <style></style> ``` 畫面結果: ![image](https://hackmd.io/_uploads/r1ZeC5UVC.png) #### 說明 - **v-for**: 我們使用 `v-for` 指令來迭代 `items` 物件中的每個項目。 - **key**: `key` 屬性設置為每個項目的 ID,以幫助 Vue 有效地識別和更新項目。 ### 範例 3: 結合 `v-if` 和 `v-for` 有時候,我們可能需要在渲染列表前做一些條件判斷。 假設我們只想顯示名字長度大於 3 的名字: 範例代碼: ```vue! <template> <ul> <li v-for="(name, index) in names" :key="index"> <span v-if="name.length > 3">{{ name }}</span> </li> </ul> </template> <script setup> const names = ['Ann', 'Alice', 'Bob', 'Charlie']; </script> <style></style> ``` 畫面結果: ![image](https://hackmd.io/_uploads/HkAs09L4A.png) #### 說明 - **v-if**: 我們在 `v-for` 的每個項目中使用 `v-if` 指令來判斷是否渲染該項目。只有名字長度大於 3 的名字會被顯示。 ### 範例 4: `v-if` + `v-else` 結合 `v-for` 結合 v-if 和 v-for,以條件判斷來顯示列表項目。 範例代碼: ```vue <template> <div> <ul> <li v-for="(item, index) in items" :key="index"> <span v-if="item.isAvailable">{{ item.name }}</span> <span v-else>商品已售空</span> </li> </ul> </div> </template> <script setup> const items = [ { name: 'Apple', isAvailable: true }, { name: 'Banana', isAvailable: false }, { name: 'Cherry', isAvailable: true } ] </script> <style></style> ``` 畫面結果: ![image](https://hackmd.io/_uploads/rJSAeiIVA.png) #### 說明 - 在這個例子中,我們渲染了一個包含物件的陣列,並根據每個項目的 isAvailable 屬性來決定顯示項目的名稱還是顯示 "商品已售空"。 ### 範例 5: 結合 `v-show` 和 `v-for` `v-show` 和 `v-if` 不同,它只會簡單地隱藏或顯示元素,而不會完全移除或添加到 DOM 中。假設我們有一個條件來顯示或隱藏某些名字: 範例代碼: ```vue! <template> <ul> <li v-for="(name, index) in names" :key="index" v-show="name.includes('a')">{{ name }}</li> </ul> </template> <script setup> const names = ['Ann', 'Alice', 'Bob', 'Charlie']; </script> <style></style> ``` 畫面結果: ![image](https://hackmd.io/_uploads/ryCsZjUVC.png) #### 說明 - **v-show**: 我們在 `v-for` 的每個項目中使用 `v-show` 指令來判斷是否顯示該項目。只有包含字母 'a' 的名字會被顯示出來,但那些不符合條件的項目仍在 DOM 中,只是被隱藏了。 這些範例展示了如何使用 `v-for` 和 `key` 屬性來渲染列表,並結合條件渲染指令(`v-if`、`v-else` 和 `v-show`)來控制顯示的邏輯。希望這能幫助你更好地理解 Vue.js 中的這些概念! # 四、總複習 示範如何利用 `v-model`、`ref`、`v-for`、`key`、`v-if + v-else` 和 `v-show` 來動態渲染和控制一個表格。 ### 示例代碼 ```vue! <template> <div> <table border="1"> <thead> <tr> <th>名稱</th> <th>年齡</th> <th>職業</th> <th>狀態</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="person in people" :key="person.id"> <td>{{ person.name }}</td> <td>{{ person.age }}</td> <td>{{ person.occupation }}</td> <td> <span v-if="person.isActive">Active</span> <span v-else>Inactive</span> </td> <td> <button @click="toggleStatus(person)">切換狀態</button> </td> </tr> </tbody> </table> <div> <label> 顯示訊息: <input type="checkbox" v-model="showMessage"> </label> <div v-show="showMessage">這是一個 v-show 範例的訊息</div> </div> <div> <label> 新增人員: <input type="text" v-model="newPerson.name" placeholder="名稱" ref="nameInput"> <input type="number" v-model="newPerson.age" placeholder="年齡"> <input type="text" v-model="newPerson.occupation" placeholder="職業"> <button @click="addPerson">新增</button> </label> </div> </div> </template> <script setup> import { ref } from 'vue'; const people = ref([ { id: 1, name: 'John Doe', age: 30, occupation: 'Developer', isActive: true }, { id: 2, name: 'Jane Smith', age: 25, occupation: 'Designer', isActive: false }, { id: 3, name: 'Sam Johnson', age: 40, occupation: 'Manager', isActive: true } ]); const showMessage = ref(true); const newPerson = ref({ id: null, name: '', age: null, occupation: '', isActive: true }); const toggleStatus = (person) => { person.isActive = !person.isActive; }; const addPerson = () => { if (newPerson.value.name && newPerson.value.age && newPerson.value.occupation) { newPerson.value.id = people.value.length + 1; people.value.push({ ...newPerson.value }); newPerson.value.name = ''; newPerson.value.age = null; newPerson.value.occupation = ''; newPerson.value.isActive = true; ref('nameInput').value.focus(); } }; </script> <style> table { width: 100%; border-collapse: collapse; } th, td { padding: 8px; text-align: left; } </style> ``` ### 邏輯說明 1. **表格結構**: - 使用 `<table>` 元素來建立表格,並使用 `<thead>` 和 `<tbody>` 來分別定義表頭和表格內容。 2. **v-for 與 key**: - 在 `<tbody>` 中,使用 `v-for` 指令來循環渲染 `people` 陣列中的每個物件。 - 使用 `:key="person.id"` 來唯一標識每個行,幫助 Vue 追蹤每個項目,從而優化重新渲染的過程。 3. **v-if + v-else**: - 在每一行的狀態欄位中,使用 `v-if` 和 `v-else` 指令根據 `person.isActive` 的值來顯示 "Active" 或 "Inactive"。 4. **v-show**: - 在表格下方,使用 `v-show="showMessage"` 來控制訊息的顯示。當 `showMessage` 為 `true` 時,訊息會顯示,否則訊息會被隱藏(但仍然在 DOM 中)。 5. **v-model**: - 使用 `v-model` 雙向綁定來綁定顯示訊息的 checkbox 和新人的輸入字段。 6. **ref**: - 使用 `ref` 來引用新增人員名稱輸入字段,以便在新增人員後自動聚焦該字段。 7. **切換狀態**: - 在每一行的操作欄位中,設置一個按鈕,點擊時會調用 `toggleStatus` 函數切換 `person.isActive` 的狀態。 8. **新增人員**: - 新增人員的輸入字段使用 `v-model` 進行綁定,點擊新增按鈕時會調用 `addPerson` 函數將新的人員加入 `people` 陣列中。 這個示例展示了如何結合 `v-model`、`ref`、`v-for`、`key`、`v-if + v-else` 和 `v-show` 來動態渲染和控制表格內容及其他元素的顯示狀態,並實現新增和狀態切換功能。 # 五、計算屬性 Computed ## 計算屬性 (computed) 簡單說明 在 Vue.js 中,計算屬性 (computed properties) 是一種根據已有的資料來自動計算出新的資料的屬性。這些屬性會根據依賴的資料自動更新,並且會被緩存 (只有當依賴的資料改變時才重新計算)。這樣可以讓我們的代碼更清晰、更簡潔,並避免不必要的重複計算。 ### 範例代碼 這裡有幾個範例,示範如何使用計算屬性來處理不同的情況。 #### 基本範例:顯示全名 ```vue <template> <div> <p>全名: {{ fullName }}</p> </div> </template> <script setup> import { ref, computed } from 'vue'; const firstName = ref('John'); const lastName = ref('Doe'); const fullName = computed(() => { return `${firstName.value} ${lastName.value}`; }); </script> <style></style> ``` **邏輯說明**: - `firstName` 和 `lastName` 是兩個反應式變量,分別代表名字和姓氏。 - `fullName` 是一個計算屬性,它根據 `firstName` 和 `lastName` 的值來自動生成全名。 #### 處理資料過於龐大的情況 ```vue <template> <div> <p>總數: {{ total }}</p> </div> </template> <script setup> import { ref, computed } from 'vue'; const items = ref(Array.from({ length: 10000 }, (_, i) => i + 1)); const total = computed(() => { return items.value.reduce((sum, item) => sum + item, 0); }); </script> <style></style> ``` **邏輯說明**: - `items` 是一個包含 10000 個數字的陣列。 - `total` 是一個計算屬性,用來計算 `items` 中所有數字的總和。計算屬性會自動更新並且只在 `items` 改變時重新計算,從而提升性能。 #### 處理空值的情況 ```vue! <template> <div> <p>有效項目數: {{ validItemCount }}</p> </div> </template> <script setup> import { ref, computed } from 'vue'; const items = ref([1, null, 3, undefined, 5, '', 7]); const validItemCount = computed(() => { return items.value.filter(item => item !== null && item !== undefined && item !== '').length; }); </script> <style></style> ``` **邏輯說明**: - `items` 是一個包含數字和空值的陣列。 - `validItemCount` 是一個計算屬性,用來計算 `items` 中有效項目的數量(過濾掉 `null`、`undefined` 和空字符串)。 #### 處理資料類型問題的情況 ```vue <template> <div> <p>數字總和: {{ totalSum }}</p> </div> </template> <script setup> import { ref, computed } from 'vue'; const mixedItems = ref(['1', 2, '3', 4, '5']); const totalSum = computed(() => { return mixedItems.value.reduce((sum, item) => sum + Number(item), 0); }); </script> <style></style> ``` **邏輯說明**: - `mixedItems` 是一個包含數字和字串的陣列。 - `totalSum` 是一個計算屬性,用來計算 `mixedItems` 中所有項目的數字總和(將字串轉換為數字)。 #### 簡單說明 想像你有一本帳本,你每天都記錄收入和支出。你可以隨時計算出總餘額,但每次都要重新計算。計算屬性就像是幫你自動計算總餘額的工具,只要你更新收入或支出的資料,它就會自動更新總餘額,而你不需要每次手動計算。 #### 使用 get() 和 set() 的範例 在 Vue.js 中,計算屬性可以不僅用來讀取資料,還可以用來寫入資料,這時我們會使用 `get` 和 `set` 方法。 #### 範例:雙向綁定的全名 ```vue <template> <div> <p>全名: {{ fullName }}</p> <p> 修改名字: <input v-model="firstName" placeholder="名字"> <input v-model="lastName" placeholder="姓氏"> </p> </div> </template> <script setup> import { ref, computed } from 'vue'; const firstName = ref('John'); const lastName = ref('Doe'); const fullName = computed({ get() { return `${firstName.value} ${lastName.value}`; }, set(newValue) { const parts = newValue.split(' '); firstName.value = parts[0]; lastName.value = parts[1] || ''; } }); </script> <style></style> ``` #### 邏輯說明 1. **初始變數**: - `firstName` 和 `lastName` 是兩個反應式變數,分別代表名字和姓氏。 2. **計算屬性 fullName**: - `fullName` 是一個計算屬性,包含 `get` 和 `set` 方法。 3. **get 方法**: - 當你讀取 `fullName` 的時候,`get` 方法會返回名字和姓氏的組合,即 `${firstName.value} ${lastName.value}`。 4. **set 方法**: - 當你修改 `fullName` 的時候,`set` 方法會被調用。這個方法接收新的全名作為參數,並把它拆分成名字和姓氏,然後分別更新 `firstName` 和 `lastName`。 #### 簡單總結 - **計算屬性**:用來根據其他資料的變化自動計算出新的資料,並且只有在依賴的資料改變時才重新計算。 - **get 方法**:用來定義計算屬性的讀取邏輯。 - **set 方法**:用來定義計算屬性的寫入邏輯,使得計算屬性可以雙向綁定。 這樣的設計讓我們可以在頁面上顯示和更新綁定的資料,而不需要手動處理複雜的計算和更新邏輯,使得程式碼更簡潔、易讀和可維護。 ### 總結 - 計算屬性可以幫助我們從已有的資料中自動計算出新的資料。 - 它們會根據依賴的資料自動更新,並且會被緩存,只在依賴的資料改變時重新計算。 - 這讓我們的代碼更簡潔、更高效,<font color="red">避免不必要的重複計算</font>。 :::danger Computed 只執行純計算,不要發出異步請求或在計算的 Getter 中改變 DOM! 它唯一的職責只有:<font color="red">**計算並返回該值。**</font> ::: 這些範例展示了計算屬性在處理不同情況下的應用,包括處理大量資料、處理空值、以及處理不同資料類型。通過這些範例,可以幫助理解如何在 Vue.js 中使用計算屬性來簡化和優化我們的代碼。 # 額外學習 - 副作用 Side-Effect ## 什麼是 Side-Effect **Side-effect**,中文可以翻譯為「副作用」。 在程式設計中,副作用是指一段程式碼執行後,不僅僅是返回結果,還會影響外部的狀態或環境。這些外部影響就稱為副作用。 ### 簡單說明 假設你在廚房煮飯,煮飯這個動作會有預期的結果,就是你會得到一鍋煮好的飯。但是,如果在煮飯的過程中,蒸汽弄濕了廚房的窗戶,這個弄濕窗戶的現象就是一個「副作用」。煮飯的主要目的是得到飯,而弄濕窗戶則是意料之外的影響。 ### 副作用的應用 #### 例子 1:修改全局變數 假設我們有一個計數器函數,每次調用這個函數都會讓計數器加一。 ```javascript let counter = 0; function incrementCounter() { counter += 1; // 這裡就是副作用,因為它修改了外部變數 counter } incrementCounter(); console.log(counter); // 輸出 1 ``` **應用場景**: 這種修改全局變數的副作用在許多情況下是必需的,比如在網頁應用中,當用戶點擊按鈕時,我們需要更新顯示在頁面上的點擊次數。 #### 例子 2:API 請求 假設我們有一個函數,用來向伺服器發送資料並獲取回應。 ```javascript function fetchData() { fetch('https://api.example.com/data') .then(response => response.json()) .then(data => { console.log(data); // 這裡就是副作用,因為它處理了外部的 API 回應 }); } fetchData(); ``` **應用場景**: 這種副作用在需要與外部系統或服務交互時非常常見,比如從伺服器獲取資料、向伺服器發送表單資料等。 ### 副作用的優點與缺點 **優點**: 1. **實用性**:副作用讓程式可以與外部世界互動,完成各種實用功能,比如更新網頁內容、與伺服器通訊、記錄日誌等。 2. **必要性**:許多功能依賴副作用,比如用戶輸入處理、狀態更新、計數等。 **缺點**: 1. **難以追蹤**:副作用會影響外部狀態,可能導致程式的行為難以預測和理解,特別是在大型系統中。 2. **測試困難**:有副作用的程式碼通常比較難以測試,因為它依賴於外部狀態或環境,測試時需要模擬這些外部條件。 3. **可重入性問題**:有副作用的函數在多次調用時,可能產生不同的結果,這會使程式的行為不穩定。 ### 總結 副作用在程式設計中是不可避免的,因為我們常常需要程式與外部環境交互。理解和管理副作用是寫出可靠、可維護程式碼的重要技能。透過適當的設計模式和工具(如 Vue.js 的組件生命周期方法),我們可以有效地控制和測試副作用,從而提高程式的質量和可預測性。 # 六、監聽器 Watch、WatchEffect ## 什麼是監聽器 (Watch) 和 WatchEffect? 在 Vue 3 中,**監聽器 (watch)** 和 **WatchEffect** 是用來監控資料變化並對變化做出反應的工具。這些工具讓我們可以在資料發生改變時自動執行特定的操作。 ### Watch 監聽器 `watch` 主要用來監聽指定的資料或計算屬性,當它們改變時執行一些操作。 ### WatchEffect `watchEffect` 會立即執行一次它所包含的函數,並自動追蹤函數內部所使用的所有反應式資料,當任何這些資料發生變化時再次執行這個函數。 ### 業界常用範例及邏輯 #### 範例 1:表單驗證 (Watch) 假設我們有一個註冊表單,需要實時驗證用戶輸入的郵箱是否有效。 ```vue <template> <div> <input v-model="email" placeholder="輸入你的郵箱"> <p v-if="emailError">{{ emailError }}</p> </div> </template> <script setup> import { ref, watch } from 'vue'; const email = ref(''); const emailError = ref(''); watch(email, (newEmail) => { if (!newEmail.includes('@')) { emailError.value = '郵箱格式不正確'; } else { emailError.value = ''; } }); </script> ``` **邏輯**: - 監聽 `email` 變量,當用戶輸入新的郵箱時,檢查是否包含 '@' 符號。 - 如果格式不正確,設定錯誤訊息,否則清除錯誤訊息。 **為什麼要使用**: - 可以實時檢查用戶輸入,提供即時反饋,提升用戶體驗。 #### 範例 2:資料同步 (WatchEffect) 假設我們需要同步一個反應式變量到本地儲存 (localStorage)。 ```vue <template> <div> <input v-model="username" placeholder="輸入你的名字"> </div> </template> <script setup> import { ref, watchEffect } from 'vue'; const username = ref(localStorage.getItem('username') || ''); watchEffect(() => { localStorage.setItem('username', username.value); }); </script> ``` **邏輯**: - 在 `username` 變量改變時,自動將新值儲存到 `localStorage`。 - 初始值從 `localStorage` 中讀取。 **為什麼要使用**: - 簡化代碼,不需要明確指定要監聽哪些變量,只需在函數內使用即可自動監聽。 ### 比較其他監聽方式 #### 1. `watch` vs `watchEffect` - **`watch`**: - 只監聽指定的變量。 - 需要明確指定監聽的變量。 - 適合監聽特定變量的變化,並執行複雜的操作。 - **`watchEffect`**: - 自動監聽函數內使用的所有反應式變量。 - 立即執行一次。 - 適合快速反應的簡單操作,不需要明確指定變量。 **優缺點**: - **`watch`** 的優點是精確控制,缺點是需要手動指定變量且若是遇到是物件是監聽不到的,因為他只能監聽到第一層。 監聽不到的情況: - ```vue! <template> <div> <input type="text" v-model="email.levels.email" > </div> </template> <script setup> import { ref } from 'vue'; import { watch } from '@vue/runtime-core'; const email = ref({ levels:{ email: '', // 會監聽不到 } }); watch(email, (newEmail) => { console.log(email, newEmail); }); </script> ``` - 使其可監聽到的情況(增加 deep 屬性): ```vue! <template> <div> <input type="text" v-model="email.levels.email" > </div> </template> <script setup> import { ref } from 'vue'; import { watch } from '@vue/runtime-core'; const email = ref({ levels:{ email: '', } }); watch( email, (newEmail) => { console.log(email, newEmail); }, { deep: true }); // 增加 deep 屬性 </script> ``` 但回傳的會是整個物件而不是只單純監聽 email 這個值,更好作法如下: ```vue! <template> <div> <input type="text" v-model="email.levels.email" > </div> </template> <script setup> import { ref } from 'vue'; import { watch } from '@vue/runtime-core'; const email = ref({ levels:{ email: '', } }); watch( () => email.value.levels.email, // <- 這樣寫之後 (newEmail) => { console.log(email, newEmail); }, { deep: true }); // <- 就不必增加 deep 屬性 </script> ``` - **`watchEffect`** 的優點是簡便易用,缺點是可能不夠精確,對於複雜操作可能不夠靈活。 ### 哪個比較常被使用? - 在需要監控特定變量時,`watch` 比較常被使用,因為它提供了更精確的控制。 - 在需要快速反應並自動追蹤所有相關變量時,`watchEffect` 比較常被使用,因為它簡化了代碼。 ### 其他替代方案 #### 1. Vuex Vuex 是 Vue 的狀態管理模式,可以集中管理應用的所有狀態,並提供更複雜的狀態監控和變更機制。 **差異**: - Vuex 更適合大型應用,需要集中管理狀態。 - `watch` 和 `watchEffect` 更適合小範圍的監控和反應。 **優缺點**: - Vuex 的優點是集中化管理,缺點是學習成本較高。 - `watch` 和 `watchEffect` 的優點是簡單易用,缺點是在大型應用中可能不夠強大。 ### 總結 - **`watch`**:用來監聽特定變量的變化,適合精確控制和複雜操作。 - **`watchEffect`**:自動監聽函數內使用的變量,適合簡便快速反應的操作。 - **Vuex**:用於大型應用的集中狀態管理,更強大但學習成本高。 不同的工具有不同的適用場景,選擇合適的工具可以讓我們的開發工作更加高效和可維護。 :::success ## 進階補充 ### 什麼是 Vuex? **Vuex** 是 Vue.js 的狀態管理庫,用於集中管理應用中的所有組件的狀態。它提供了一個全局的 store 對象,可以用來存儲共享的狀態,並提供了一些方法來操作這些狀態,使得狀態的變更和管理更加容易。 ### 為什麼要使用 Vuex? 1. **集中管理**:Vuex 提供了一個集中的數據存儲,方便管理所有組件共享的狀態,使得應用的狀態管理更加清晰。 2. **組件通信**:通過 Vuex,組件之間可以方便地共享狀態,不需要通過 props 和事件來進行繁瑣的傳遞。 3. **便於調試**:Vuex 的狀態是響應式的,任何修改都會被監聽到,方便進行調試和追蹤。 ### Vuex 的基本概念 1. **State**:存儲應用中的所有狀態,可以通過 `this.$store.state` 訪問。 2. **Getter**:類似於計算屬性,用來從狀態中計算出新的值,可以通過 `this.$store.getters` 訪問。 3. **Mutation**:唯一可以修改狀態的地方,但是必須是同步函數,可以通過 `this.$store.commit` 調用。 4. **Action**:用來執行非同步操作,可以通過 `this.$store.dispatch` 調用,可以提交 Mutation 來修改狀態。 5. **Module**:將 Store 拆分成多個模塊,每個模塊擁有自己的 state、getters、mutations 和 actions。 ### 業界常用範例及邏輯 #### 範例 1:購物車管理 假設我們有一個購物車功能,需要在多個組件中共享購物車的狀態。 ```javascript // store/index.js import { createStore } from 'vuex'; export default createStore({ state: { cart: [] }, mutations: { addToCart(state, product) { state.cart.push(product); }, removeFromCart(state, productId) { state.cart = state.cart.filter(item => item.id !== productId); } }, actions: { addToCart({ commit }, product) { commit('addToCart', product); }, removeFromCart({ commit }, productId) { commit('removeFromCart', productId); } }, getters: { cartItemCount: state => state.cart.length } }); ``` **邏輯**: - 在 state 中定義了一個 `cart` 陣列,用來存儲購物車中的商品。 - 通過 mutations 定義了兩個方法:`addToCart` 和 `removeFromCart`,用來增加和移除商品。 - 通過 actions 定義了兩個方法,用來提交 mutations。 - 通過 getters 定義了 `cartItemCount` 方法,用來計算購物車中的商品數量。 #### 範例 2:用戶登錄狀態管理 假設我們需要管理用戶的登錄狀態,並在多個組件中顯示不同的內容。 ```javascript // store/index.js import { createStore } from 'vuex'; export default createStore({ state: { user: null }, mutations: { setUser(state, user) { state.user = user; } }, actions: { login({ commit }, user) { // 認證用戶,並獲取用戶信息 commit('setUser', user); }, logout({ commit }) { // 登出,清空用戶信息 commit('setUser', null); } }, getters: { isLoggedIn: state => !!state.user } }); ``` **邏輯**: - 在 state 中定義了一個 `user` 變量,用來存儲用戶信息。 - 通過 mutations 定義了一個 `setUser` 方法,用來設置用戶信息。 - 通過 actions 定義了兩個方法:`login` 和 `logout`,分別用來處理用戶登錄和登出的操作。 - 通過 getters 定義了 `isLoggedIn` 方法,用來判斷用戶是否已經登錄。 ### 替代方式 #### 1. Composition API Vue 3 的 Composition API 提供了一種新的方式來組織組件的邏輯。它允許我們將組件的邏輯按照功能切分成多個獨立的片段,並在需要時將它們組合在一起。Composition API 與 Vuex 不同,它更加靈活,可以更好地管理組件的狀態和行為。 **差異**: - Vuex 是一個狀態管理庫,用於集中管理應用的狀態。 - Composition API 是 Vue 3 中的一個特性,用於組織組件的邏輯,包括狀態管理、生命週期鉤子等。 **優缺點**: - Vuex 適用於大型應用 - Composition API:適合中小型應用的靈活組件邏輯管理,易於使用,但在管理全局狀態時可能不夠強大。 選擇合適的工具取決於應用的規模和需求,對於大型應用,Vuex 提供了強大的集中管理能力,而對於中小型應用,Composition API 提供了更靈活的狀態和邏輯管理方式。 ::: # 七、父子組件溝通、傳資料 ## 什麼是父子組件溝通、傳資料 當我們在講父子組件之間的溝通和傳遞資料的時候,可以想像成父母和孩子之間的對話和互動。這裡有幾個重要的概念: ### 父組件給子組件傳遞資料 這就像父母給孩子一些指示或信息。具體來說,這叫做 "props"(屬性)。父母(父組件)可以通過 `props` 來給孩子(子組件)傳遞信息。例如,父母可能會告訴孩子今天的作業是什麼,這些作業就是用 `props` 傳遞的。 #### 實際應用 - **情境**:你和爸爸在家裡。爸爸給你一個籃子,裡面裝著蘋果。你拿到籃子後,可以看見裡面的蘋果。 父組件: ```html <template> <ChildComponent :apples="appleCount" /> </template> <script setup> import ChildComponent from './ChildComponent.vue'; // 爸爸準備的蘋果數量 const appleCount = 5; </script> <style scoped> </style> ``` 子組件: ```html <template> <div>爸爸給了我 {{ apples }} 個蘋果。</div> </template> <script setup> import { defineProps } from 'vue'; // 接收爸爸傳來的蘋果數量 const props = defineProps({ apples: Number }); </script> <style scoped> </style> ``` - **說明**: - 父組件:爸爸準備了5個蘋果,並將它們放入籃子(appleCount)。 - 子組件:你接收到籃子,並可以看到裡面有5個蘋果(透過defineProps來接收apples)。 ### 子組件給父組件傳遞資料 這就像孩子向父母匯報情況或要求幫助。子組件通過觸發事件(emitting events)來告訴父組件一些信息。這些事件就像孩子的喊話或通知,父母聽到後可以做出反應。 #### 實際應用 - **情境**:你在外面買了一些東西,想告訴爸爸你買了什麼。 子組件: ```html <template> <button @click="buyItem">買東西</button> </template> <script setup> import { defineEmits } from 'vue'; const emit = defineEmits(['itemBought']); const buyItem = () => { const item = '蘋果'; emit('itemBought', item); }; </script> <style scoped> </style> ``` 父組件: ```html <template> <ChildComponent @itemBought="handleItemBought" /> </template> <script setup> import ChildComponent from './ChildComponent.vue'; const handleItemBought = (item) => { console.log('孩子買了:', item); }; </script> <style scoped> </style> ``` - **說明**: - 父組件:爸爸等待你的消息,並在你告訴他後(透過handleItemBought),記錄你買了什麼。 - 子組件:你買了一個蘋果,並透過defineEmits告訴爸爸你買了什麼(itemBought事件)。 ### 父子組件雙向綁定 在 Vue 3 中,雙向綁定可以通過 v-model 來實現,就像父母和孩子之間的互動是雙向的。例如,孩子在選擇的過程中,不僅需要父母給予建議,父母也需要及時知道孩子的進展。 #### 實際應用 - **情境**:你和爸爸討論要不要買某個東西,爸爸給你建議,你也告訴爸爸你的想法。 子組件: ```html <template> <input v-model="localItem" @input="notifyParent" /> </template> <script setup> import { defineProps, defineEmits, ref, watch } from 'vue'; const props = defineProps({ suggestedItem: String }); const emit = defineEmits(['updateItem']); const localItem = ref(props.suggestedItem); watch(() => props.suggestedItem, (newVal) => { localItem.value = newVal; }); const notifyParent = () => { emit('updateItem', localItem.value); }; </script> <style scoped> </style> ``` 父組件: ```html <template> <ChildComponent :suggestedItem="item" @updateItem="updateItem" /> </template> <script setup> import ChildComponent from './ChildComponent.vue'; import { ref } from 'vue'; const item = ref('書包'); const updateItem = (newItem) => { item.value = newItem; }; </script> <style scoped> </style> ``` - **說明**: - 父組件:爸爸給你一個建議(item),並等待你的反饋。 - 子組件:你根據爸爸的建議,提出了自己的意見(localItem),並通知爸爸(updateItem事件)。 - 雙向傳遞:爸爸和你之間能夠互相影響彼此的決定。 ### 總結 1. **父組件給子組件傳資料**:通過 `props`,像父母給孩子指示。 2. **子組件給父組件傳資料**:通過觸發事件 `emit`,像孩子向父母匯報。 3. **雙向綁定**:通過 `v-model`,實現父母和孩子之間的雙向互動。 **這些機制讓組件之間能夠方便地進行溝通和協作,就像家庭成員之間的交流一樣。** :::success ### 小補充: `defineProps` 和 `defineEmits`。 ### defineProps #### 簡易說明 `defineProps` 就像是爸爸給你一個禮物盒,裡面有東西(資料)。你可以打開禮物盒,拿到裡面的東西。 #### 比喻 - **情境**:爸爸把一個裝滿糖果的禮物盒給你。 - **解釋**:`defineProps` 就是用來接收父組件(爸爸)傳給子組件(你)的資料(糖果)。 ### defineEmits #### 簡易說明 `defineEmits` 就像是你在家裡,告訴爸爸你做了某件事。這樣爸爸就知道發生了什麼。 #### 比喻 - **情境**:你告訴爸爸你完成了作業。 - **解釋**:`defineEmits` 就是用來讓子組件(你)通知父組件(爸爸)某件事情已經發生(你完成了作業)。 ### 具體範例 利用上面的比喻轉換成代碼。 #### defineProps 例子 **父組件 (ParentComponent.vue)** ```html <template> <ChildComponent :candies="5" /> </template> <script setup> import ChildComponent from './ChildComponent.vue'; </script> <style scoped> </style> ``` **子組件 (ChildComponent.vue)** ```html <template> <div>爸爸給了我 {{ candies }} 顆糖果。</div> </template> <script setup> import { defineProps } from 'vue'; // 接收爸爸傳來的糖果數量 const props = defineProps({ candies: Number }); </script> <style scoped> </style> ``` - **說明**:爸爸(父組件)給了你(子組件)5顆糖果,子組件透過 `defineProps` 接收這些糖果。 #### defineEmits 例子 **父組件 (ParentComponent.vue)** ```html <template> <ChildComponent @homeworkDone="handleHomeworkDone" /> </template> <script setup> import ChildComponent from './ChildComponent.vue'; const handleHomeworkDone = () => { console.log('孩子完成了作業'); }; </script> <style scoped> </style> ``` **子組件 (ChildComponent.vue)** ```html <template> <button @click="finishHomework">完成作業</button> </template> <script setup> import { defineEmits } from 'vue'; // 告訴爸爸作業完成了 const emit = defineEmits(['homeworkDone']); const finishHomework = () => { emit('homeworkDone'); }; </script> <style scoped> </style> ``` - **說明**:你(子組件)完成了作業,並透過 `defineEmits` 通知爸爸(父組件)作業完成了。 ::: # 八、組件生命週期 ![組件生命週期圖](https://cn.vuejs.org/assets/lifecycle_zh-CN.W0MNXI0C.png) ## 什麼是組件的生命週期 > 想像你在種一棵樹,這棵樹從種子到成長,再到結果,最後到落葉凋零,整個過程就像是組件的生命週期。 ### 1. **種子階段:`setup`** - **比喻**:這是你剛剛把種子種到土裡,開始準備養分和水分。 - **解釋**:這個階段是組件初始化的階段,我們在這裡設置初始資料和需要的變數。 ### 2. **發芽階段:`onBeforeMount`** - **比喻**:這是種子開始發芽,但還沒長出地面。 - **解釋**:組件即將被掛載到頁面,但還沒有真正顯示出來。 ### 3. **成長階段:`onMounted`** - **比喻**:這是小樹苗破土而出,開始在陽光下生長。 - **解釋**:組件已經被掛載到頁面,已經可以看到並開始與用戶互動。 ### 4. **開花階段:`onBeforeUpdate`** - **比喻**:樹苗開始長出花苞,但花還沒有完全開放。 - **解釋**:組件中的數據發生變化,DOM 即將更新,但還沒有開始更新。 ### 5. **結果階段:`onUpdated`** - **比喻**:花苞開放並結出了果實。 - **解釋**:組件的DOM已經更新完成,界面顯示出最新的數據和狀態。 ### 6. **落葉階段:`onBeforeUnmount`** - **比喻**:果實被採摘,葉子開始掉落。 - **解釋**:組件即將從頁面上被移除,這是做一些清理工作的好時機。 ### 7. **凋零階段:`onUnmounted`** - **比喻**:樹木已經凋零,回歸大地。 - **解釋**:組件已經從頁面上被移除,所有的事件監聽和資料都被清理掉。 ### 具體使用方式 在 Vue 中,這些生命週期方法可以通過 `setup` 函數來使用,例如: ```javascript import { onMounted, onBeforeUnmount } from 'vue'; export default { setup() { onMounted(() => { console.log('樹苗已經長出來了'); }); onBeforeUnmount(() => { console.log('樹苗即將凋零'); }); } }; ``` ### 總結 - **種子階段 (`setup`)**:初始化組件。 - **發芽階段 (`onBeforeMount`)**:組件即將掛載。 - **成長階段 (`onMounted`)**:組件已掛載。 - **開花階段 (`onBeforeUpdate`)**:組件數據即將更新。 - **結果階段 (`onUpdated`)**:組件數據已更新。 - **落葉階段 (`onBeforeUnmount`)**:組件即將卸載。 - **凋零階段 (`onUnmounted`)**:組件已卸載。 這些階段就像一棵樹從種子到凋零的過程,對應著Vue3組件的誕生、成長和結束。 生命週期詳細說明以及其他Hook應用請參考官網: 1. [Lifecycle Hooks](https://zh-hk.vuejs.org/api/composition-api-lifecycle.html) 2. [Composition API: Lifecycle Hooks for Vue3](https://zh-hk.vuejs.org/api/composition-api-lifecycle.html) :::success ## **額外小補充**:Vue3 和 Vue2 的差異,以及它們在應用和邏輯上的不同之處。 ### Vue3 vs Vue2 #### 1. 設計和結構 - **Vue2**:就像一個老房子,所有房間和功能都已經固定,你只能在特定的地方做改動。你需要遵循特定的規則來添加或修改房間(功能)。 - **Vue3**:就像一個新建的模塊化房子,你可以自由選擇和組裝不同的模塊,根據需要來定制你的房子(功能)。這使得設計和擴展變得更靈活。 #### 2. Composition API - **Vue2**:使用的是 Options API,就像是按照菜譜(options)來做菜,你需要按順序把材料放入指定的位置。 - **Vue3**:引入了 Composition API,這就像是你自己決定如何組合材料來做出你想要的菜,讓你可以更靈活地組織代碼和功能。 #### 3. 性能提升 - **Vue2**:性能已經不錯,但在大型應用中可能會有些瓶頸,就像一輛普通的轎車,平時開起來很舒服,但在需要高性能時可能不夠快。 - **Vue3**:性能進一步優化,尤其是初始化和更新速度,就像是一輛跑車,無論是日常使用還是高性能需求都能應對自如。 #### 4. TypeScript 支持 - **Vue2**:支持 TypeScript,但需要額外的配置,就像是你可以在普通的房子里安裝智能家居系統,但需要一些改造。 - **Vue3**:原生支持 TypeScript,這就像是新房子一開始就設計了智能家居系統,你只需要直接使用即可。 ### 實際應用和邏輯 #### 1. 組件的組織 **Vue2**: ```javascript export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } } } ``` **Vue3**: ```javascript import { ref } from 'vue' export default { setup() { const count = ref(0) const increment = () => { count.value++ } return { count, increment } } } ``` - **說明**: - Vue2:你需要把所有的資料和方法放在特定的區域(`data` 和 `methods`)。 - Vue3:你可以直接在 `setup` 函數中定義資料和方法,這樣使得代碼更加集中和易於理解。 #### 2. 更好的邏輯重用 **Vue2**: 使用 Mixins 來重用邏輯,但可能會出現命名衝突。 **Vue3**: 使用 Composition API 來重用邏輯,可以創建可重用的函數組合,避免了命名衝突。 - **比喻**: - Vue2:像是在做菜時,把相同的調料放在不同的地方,可能會出現混淆。 - Vue3:你可以創建一個調料包,隨時可以拿出來用,避免混淆。 #### 3. 減少了大量的 Vue 特定語法 **Vue2**: ```javascript! export default { name: 'App', //組件名稱 components: {}, // 其他直屬組件 data: () => ({}), // 組件內的資料(Data)變數有哪些 props: { msg: String }, computed: { reversedMsg() { return this.msg.split('').reverse().join('') } }, // 存放計算屬性 methods: {}, // 存放function } ``` **Vue3**: ```javascript! import { defineProps, computed } from 'vue' export default { setup() { const props = defineProps(['msg']) const reversedMsg = computed(() => props.msg.split('').reverse().join('')) return { reversedMsg } } } ``` - **說明**: - Vue2:需要用 Vue 特定的語法來定義 `props` 和 `computed`。 - Vue3:使用 JavaScript 的語法,更加簡潔和直觀。 ### 總結 - **靈活性**:Vue3 提供了更靈活的 Composition API,讓開發者可以更自由地組織代碼。 - **性能**:Vue3 在性能上有顯著提升,適合大型應用。 - **TypeScript 支持**:Vue3 原生支持 TypeScript,對於需要嚴格類型檢查的項目更加友好。 - **可重用性**:Vue3 的 Composition API 提供了更好的邏輯重用方式,避免了 Vue2 中的命名衝突問題。 這些改進使得 Vue3 比 Vue2 更加適應現代前端開發需求,並且更容易維護和擴展。 ::: # 九、插槽 Slot ## Slot 插槽的比喻 想像你在家裡舉辦一個派對,邀請了很多朋友。你準備了一個裝飾品架子(Slot),但是你讓每個朋友自由地決定要放什麼東西在架子上。這樣每個朋友都可以展示他們帶來的特別物品。 ### Slot 插槽的概念 - **父組件**:派對的主辦者(你),準備了架子(插槽)。 - **子組件**:你的朋友,他們帶來了自己的物品並放到架子上。 ### 基本插槽(Default Slot) #### 情境 你準備了一個空白的架子(插槽),任何人都可以把任何東西放上去。 #### 例子 **父組件 (ParentComponent.vue)** ```html <template> <div> <h1>歡迎來到我的派對</h1> <Decoration> <p>這是朋友A帶來的花瓶</p> </Decoration> </div> </template> <script setup> import Decoration from './Decoration.vue'; </script> ``` **子組件 (Decoration.vue)** ```html <template> <div class="decoration"> <slot></slot> </div> </template> ``` #### 邏輯 - **父組件**:你(主辦者)準備了一個架子(Decoration),並讓朋友A放上了一個花瓶(`<p>這是朋友A帶來的花瓶</p>`)。 - **子組件**:架子(Decoration)上有一個插槽(`<slot></slot>`),可以顯示父組件放入的任何內容。 ### 具名插槽(Named Slot) #### 情境 你準備了一個架子,上面有兩個標籤:「左邊」和「右邊」,讓朋友決定要在左邊還是右邊放東西。 #### 例子 **父組件 (ParentComponent.vue)** ```html <template> <div> <h1>歡迎來到我的派對</h1> <Decoration> <template v-slot:left> <p>這是朋友A帶來的花瓶</p> </template> <template v-slot:right> <p>這是朋友B帶來的畫</p> </template> </Decoration> </div> </template> <script setup> import Decoration from './Decoration.vue'; </script> ``` **子組件 (Decoration.vue)** ```html <template> <div class="decoration"> <div class="left"> <slot name="left"></slot> </div> <div class="right"> <slot name="right"></slot> </div> </div> </template> ``` #### 邏輯 - **父組件**:你準備了一個架子(Decoration),並決定了兩個區域:「左邊」和「右邊」。 - 在「左邊」放上了朋友A的花瓶(`<template v-slot:left>...</template>`)。 - 在「右邊」放上了朋友B的畫(`<template v-slot:right>...</template>`)。 - **子組件**:架子上有兩個具名插槽(`<slot name="left"></slot>` 和 `<slot name="right"></slot>`),顯示父組件指定的內容。 ### 作用域插槽(Scoped Slot) #### 情境 你準備了一個架子,並且給每個來參加派對的朋友一個標籤,讓他們知道他們可以在架子的特定區域展示他們的東西。 #### 例子 **父組件 (ParentComponent.vue)** ```html <template> <div> <h1>歡迎來到我的派對</h1> <Decoration> <template v-slot:default="slotProps"> <p>這是朋友{{ slotProps.name }}帶來的{{ slotProps.item }}</p> </template> </Decoration> </div> </template> <script setup> import Decoration from './Decoration.vue'; </script> ``` **子組件 (Decoration.vue)** ```html <template> <div class="decoration"> <slot :name="friendName" :item="friendItem"></slot> </div> </template> <script setup> import { ref } from 'vue'; // 模擬的數據 const friendName = ref('A'); const friendItem = ref('花瓶'); </script> ``` #### 邏輯 - **父組件**:你準備了一個架子(Decoration),並透過作用域插槽(`<template v-slot:default="slotProps">...</template>`)獲取來自子組件的資料(`slotProps`),然後決定顯示的內容。 - 使用這些資料來展示朋友A帶來的花瓶(`這是朋友{{ slotProps.name }}帶來的{{ slotProps.item }}`)。 - **子組件**:架子上有一個插槽,並提供了資料(`friendName` 和 `friendItem`),讓父組件決定如何顯示這些資料。 ### 總結 - **基本插槽(Default Slot)**:父組件決定放入什麼內容,子組件只提供一個空間來顯示這些內容。 - **具名插槽(Named Slot)**:父組件可以指定不同的內容放到子組件的不同區域,子組件有多個命名的空間來顯示這些內容。 - **作用域插槽(Scoped Slot)**:子組件提供資料給父組件,父組件根據這些資料決定顯示的內容。 這些插槽就像是派對上的架子,你可以決定架子上的哪些區域展示什麼物品,或是根據朋友帶來的物品來決定如何展示。 # 十、v-bind指令 ### 什麼是 v-bind? 在 Vue.js 中,`v-bind` 是一個指令,用來綁定元素的屬性或 HTML 特性(attribute)與 Vue 實例的數據。這樣可以讓數據驅動的應用更靈活和動態。 #### 簡單的說: `v-bind` 就像是把程式中的變數(數據)和 HTML 元素的屬性連接在一起。這樣當變數的值改變時,HTML 元素也會自動更新,顯示最新的值。 ### v-bind 的語法 基本語法是 `v-bind:attribute="expression"`,其中 `attribute` 是 HTML 元素的屬性,`expression` 是 Vue 實例中的數據或變數。 ### 舉例說明 #### 例子1:動態改變圖片的來源 假設我們有一個圖片的 URL 存在 Vue 的數據中,我們可以用 `v-bind` 來動態改變這個圖片的來源。 ```html <!DOCTYPE html> <html> <head> <title>v-bind 範例</title> <script src="https://cdn.jsdelivr.net/npm/vue@2"></script> </head> <body> <div id="app"> <img v-bind:src="imageUrl" alt="Dynamic Image"> <!-- 也可以使用:src="imageUrl" --> </div> <script> new Vue({ el: '#app', data: { imageUrl: 'https://example.com/image1.jpg' } }); </script> </body> </html> ``` 在這個例子中,當 `imageUrl` 變數的值改變時,圖片的來源也會自動更新。 #### 例子2:動態設置按鈕的禁用狀態 我們可以使用 `v-bind` 來根據某個條件動態設置按鈕的禁用狀態。 ```html <!DOCTYPE html> <html> <head> <title>v-bind 範例</title> <script src="https://cdn.jsdelivr.net/npm/vue@2"></script> </head> <body> <div id="app"> <button v-bind:disabled="isButtonDisabled">Click Me!</button> <button @click="toggleButton">Toggle Button State</button> </div> <script> new Vue({ el: '#app', data: { isButtonDisabled: true }, methods: { toggleButton() { this.isButtonDisabled = !this.isButtonDisabled; } } }); </script> </body> </html> ``` 在這個例子中,`isButtonDisabled` 變數控制著按鈕是否被禁用。點擊 "Toggle Button State" 按鈕可以切換按鈕的狀態。 ### 總結 `v-bind` 是 Vue.js 提供的一個指令,用來將 Vue 實例中的數據綁定到 HTML 元素的屬性上。這樣當數據改變時,HTML 元素也會自動更新顯示最新的值。透過 `v-bind`,我們可以讓應用更具動態和交互性。 # 整份自學筆記學習來源:[Vue3 基礎教學](https://youtube.com/playlist?list=PLSCgthA1AnifSzKdpV4FWq1pLVF4FbZ4K&si=IodxlYKxTc5k013Y) - [老師:Proladon](https://www.youtube.com/@Proladon)