Vue3 系統學習#
1. Vue3 基礎#
1.1 循環渲染#
<div v-for="item in items" :key="item.id">
<!-- 內容 -->
</div>
講一講 key,為什麼要加 key?
key 是用於管理狀態的,就是說你 v-for 去綁定一個列表,一個數組嗎。
在默認模式中,就是不加 key 的時候如果你的數據順序發生變化,vue 不會移動 dom 流的順序,而是會重新更新每個元素。
單如果你的列表渲染的輸出結果要依賴子組件狀態或者臨時 dom 狀態(例如表單輸入值的情況。
為了給 Vue 一個提示,以便它可以跟蹤每個節點的標識,從而重用和重新排序現有的元素,你需要為每個元素對應的塊提供一個唯一的 key attribute。
1.2 事件處理#
事件處理器 (handler) 的值可以是:
- 內聯事件處理器:事件被觸發時執行的內聯 JavaScript 語句 (與 onclick 類似)。
- 方法事件處理器:一個指向組件上定義的方法的屬性名或是路徑。
什麼是內聯事件處理器和方法事件處理器?#
內聯事件處理器和方法事件處理器是 JavaScript 中用於處理 HTML 元素事件的兩種主要方式。
內聯事件處理器
內聯事件處理器直接在 HTML 元素的標籤內部指定。當用戶對該元素進行操作時(如點擊),就會觸發相應的事件。內聯事件處理器的一個典型示例是onclick
屬性,它可以直接在元素的 HTML 標籤中設置。例如:
<button onclick="alert('你點擊了按鈕!')">點擊我</button>
這段代碼中的onclick
屬性就是一個內聯事件處理器,它定義了當按鈕被點擊時應該執行的 JavaScript 代碼。
方法事件處理器
方法事件處理器則是通過 JavaScript 代碼來為元素綁定事件處理函數。這種方式通常更靈活,也更易於管理,特別是對於複雜的應用程序。你可以在 JavaScript 中使用addEventListener
方法為元素添加事件處理器。例如:
const name = ref('Vue.js')
function greet(event) {
alert(`Hello ${name.value}!`)
// `event` 是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
<!-- `greet` 是上面定義過的方法名 -->
<button @click="greet">問候</button>
這段代碼首先通過getElementById
獲取 ID 為myButton
的元素,然後使用addEventListener
為該元素添加了一個點擊事件處理器。當按鈕被點擊時,就會彈出一個警告框。
總的來說,內聯事件處理器易於快速實現但難以維護,尤其是在較大的項目中;而方法事件處理器雖然需要編寫更多的 JavaScript 代碼,但提供了更高的靈活性和可維護性。
1.3 表單輸入綁定#
v-model 綁定數值、字符串、布爾值、數組、對象、自定義組件等。
- 文本
- 字符串形式
- 多行文本
- 字符串形式
- 復選框
- 綁定單一的布爾類型值
- 多個復選框
- 多個復選框綁定到同一個數組或集合的值
<div>已選擇的名稱: {{ checkedNames }}</div> <input type="checkbox" id="jack" value="Jack" v-model="checkedNames"> <label for="jack">Jack</label> <input type="checkbox" id="john" value="John" v-model="checkedNames"> <label for="john">John</label> <input type="checkbox" id="mike" value="Mike" v-model="checkedNames"> <label for="mike">Mike</label>
- 單選按鈕
- 綁定到單一的值
<div>選擇: {{ picked }}</div> <input type="radio" id="one" value="One" v-model="picked" /> <label for="one">One</label> <input type="radio" id="two" value="Two" v-model="picked" /> <label for="two">Two</label>
- 選擇器
- 如果 v-model 表達式的初始值不匹配任何一個選擇項,
<select>
元素會渲染成一個 “未選擇” 的狀態。在 iOS 上,這將導致用戶無法選擇第一項,因為 iOS 在這種情況下不會觸發一個 change 事件。因此,我們建議提供一個空值的禁用選項,如上面的例子所示。<select v-model="selected"> <option disabled value="">請選擇一個</option> <option>A</option> <option>B</option> <option>C</option> </select> <span>選擇: {{ selected }}</span>
- 如果 v-model 表達式的初始值不匹配任何一個選擇項,
2. 組件系統學習#
2.1 組件數據傳遞#
組件與組件之間的數據傳遞有兩種方式:
- 父組件向子組件傳遞數據
通過 defineProps 去實現數據的傳遞:// 在父組件中 <template> <ChildComponent :title="title" /> </template> <script> import ChildComponent from './ChildComponent.vue' </script>
// 在子組件中 <script setup> import { defineProps } from 'vue' const props = defineProps({ title: String }) </script>
- 子組件向父組件傳遞數據
- 通過自定義事件向父組件傳遞數據
在 Vue 3 中,子組件可以通過自定義事件向父組件傳遞數據。這個過程通常涉及以下幾個步驟:
- 通過自定義事件向父組件傳遞數據
-
子組件中:使用
emit
方法觸發一個自定義事件,並將數據作為參數傳遞。```vue // 在子組件中 <!-- BlogPost.vue, 省略了 <script> --> <template> <div class="blog-post"> <h4>{{ title }}</h4> <button @click="$emit('enlarge-text')">放大文字</button> </div> </template> ```
在 Vue 中,自定義組件可以通過事件來實現數據的傳輸。在你的例子中,組件通過$emit
方法觸發了一個enlarge-text
事件。如果你需要在觸發事件的同時傳遞數據給父組件,你可以將數據作為$emit
方法的第二個參數傳遞。
下面是如何修改你的組件模板,以便在觸發enlarge-text
事件時攜帶數據:
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<!-- 添加數據作為$emit的第二個參數 -->
<button @click="$emit('enlarge-text', someData)">放大文字</button>
</div>
</template>
-
父組件中:監聽這個自定義事件,並通過事件處理函數接收數據。
```vue <BlogPost ... @enlarge-text="postFontSize += 0.1" /> ```
<!-- 父組件模板 --> <template> <div> <your-component @enlarge-text="handleEnlargeText"></your-component> </div> </template> <script> export default { methods: { // 事件處理器方法接收傳遞的數據作為參數 handleEnlargeText(data) { // 在這裡處理接收到的數據 console.log(data); } } } </script>
因為有了 @enlarge-text="postFontSize += 0.1" 的監聽,父組件會接收這一事件,從而更新 postFontSize 的值。
簡而言之就是,在vue裡key通過emit來將**自定義事件**和**操作**關聯起來,然後在父組件中**通過@監聽這個自定義** 事件,然後 **在監聽的事件中** 去處理數據。
所以如果你想在Vue裡去寫一個組件的觸發,就使用$emit去定義一個組件的觸發。
- 通過雙向綁定向父組件傳遞數據
```vue
<!-- Child.vue -->
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>父綁定的 v-model 是: {{ model }}</div>
</template>
<!-- Parent.vue -->
<Child v-model="count" />
defineModel () 返回的值是一個 ref。它可以像其他 ref 一樣被訪問以及修改,不過它能起到在父組件和當前變量之間的雙向綁定的作用:
它的 .value 和父組件的 v-model 的值同步;
當它被子組件變更了,會觸發父組件綁定的值一起更新。
v-model 接受一個參數
組件上的 v-model 也可以接受一個參數:
<UserName
v-model:first-name="first"
v-model:last-name="last"/>
<MyComponent v-model:title="bookTitle" />
在子組件中,我們可以通過將字符串作為第一個參數傳遞給 defineModel() 來支持相應的參數:
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
2.2 組件事件學習#
- 通過 $emit 的方法將事件綁定,下面這個就是將按鈕的觸碰和 someEvent 這個事件綁定了。
<!-- MyComponent -->
<button @click="$emit('someEvent')">點擊我</button>
父組件key通過@/v-on去監聽事件
<MyComponent @some-event="callback" />
<MyComponent @some-event.once="callback" />
而通過這個父組件的監聽可以實現讓子組件去觸發父組件的回調的功能。
2. 如果我想讓這個組件觸發父組件回調時帶一數據的話怎麼辦?
<button @click="$emit('increaseBy', 1)">
增加 1
</button>
<MyButton @increase-by="(n) => count += n" />
or
<MyButton @increase-by="increaseCount" />
function increaseCount(n) {
count.value += n
}
就是說在 emit 的第二個參數就是它回調會攜帶的一個數值,我們可以用一個函數參數接受,第一種方法是箭頭函數,第二種是組件方法。
所有傳入 $emit () 的額外參數都會被直接傳向監聽器。舉例來說,$emit ('foo', 1, 2, 3) 觸發後,監聽器函數將會收到這三個參數值。
然後我們可以用三個參數去接受它傳入的回調。
- 現在我想,這個事件只能通過 click 觸發是有限制的,我想通過 js 判斷條件,如果滿足再去觸發這個事件我該怎麼實現?
在子組件裡定義 defineEmits () 來聲明我要觸發的事件,然後聲明後可以通過 emit 去觸發。
<script setup>
const emit =defineEmits(['inFouse','submit'])
function buttonClick(){
emit('submit')
}
</script>
- OK, 那基本就都說清楚了,在 vue 的 temp 中觸發就
@click='$emit('名字',參數)'
, 而 defineEmit 後用 emit (' 名字 ') 就是在 js 觸發。
2.3 Props 組件數據傳遞#
Props 是父組件先子組件傳遞數據的解決方案。他的作用是顯式聲明它所接受的 props。
- 如何傳入 props?
首先應該在子組件裡去聲明,就是說一聲我這邊都需要接受什麼數據。聲明可以通過數組的方式聲明也可以通過對象的方式聲明。
<script setup>
const props = defineProps(['foo'])
const props = defineProps({
title: String,
likes: Number
})
console.log(props.foo)
</script>
父組件中如果想要傳輸數據就:
<BlogPost title="我與Vue的旅程" likes="2"/>
<BlogPost title="與Vue一起寫博客" likes="2" />
<BlogPost title="為什麼Vue這麼有趣" likes="2" />
- 傳入的數據的值能不能是動態呢,就是父組件給的值是變化的?
<!-- 根據一個變量的值動態傳入 -->
<BlogPost :title="post.title" />
<!-- 根據一個更複雜表達式的值動態傳入 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />
通過這種方式,可以將一個動態的值傳入,這個值變化,子組件的也會變化。
3. 傳遞不同類型的值,傳值可以為 number,boolean,array 等等,但是基於他的語法可能它有時是反直覺的。
```vue
傳入 Number 類型的值
<!-- 根據一個變量的值動態傳入 -->
<BlogPost :likes="post.likes" />
傳入boolean值
<!-- 僅寫上 prop 但不傳值,會隱式轉換為 `true` -->
<BlogPost is-published />
<!-- 雖然 `false` 是靜態的值,我們還是需要使用 v-bind -->
<!-- 因為這是一個 JavaScript 表達式而不是一個字符串 -->
<BlogPost :is-published="false" />
<!-- 根據一個變量的值動態傳入 -->
<BlogPost :is-published="post.isPublished" />
傳入Array值
<!-- 雖然這個數組是個常量,我們還是需要使用 v-bind -->
<!-- 因為這是一個 JavaScript 表達式而不是一個字符串 -->
<BlogPost :comment-ids="[234, 266, 273]" />
<!-- 根據一個變量的值動態傳入 -->
<BlogPost :comment-ids="post.commentIds" />
傳入Object值
<!-- 雖然這個對象字面量是個常量,我們還是需要使用 v-bind -->
<!-- 因為這是一個 JavaScript 表達式而不是一個字符串 -->
<BlogPost
:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
/>
<!-- 根據一個變量的值動態傳入 -->
<BlogPost :author="post.author" />
```
就簡而言之來說,你傳入值如果你想表達一個字符串的就用props的屬性,如果你想表達的是一個表達式就用:props的屬性。
4. 然後 props 是一個單向數據流,就是只能父組件往子組件裡傳數據,子組件沒法通過 props 向父組件傳。
但是我們很多時候其實是想要改變這個 props 的數據的,比如我想要獲得一個props 局部數據的情況/ 我想對props 的數據進行轉換的情況。
2.4 v-model 組件數據傳遞#
v-model 是一個語法糖,它可以讓我們更方便的去實現雙向綁定。
- v-model 的使用方法
<!-- Child.vue -->
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>父綁定的 v-model 是: {{ model }}</div>
</template>
<!-- Parent.vue -->
<Child v-model="count" />
defineModel () 返回的值是一個 ref。它可以像其他 ref 一樣被訪問以及修改,不過它能起到在父組件和當前變量之間的雙向綁定的作用:
它的 .value 和父組件的 v-model 的值同步;
當它被子組件變更了,會觸發父組件綁定的值一起更新。
2. v-model 接受一個參數
父組件上的 v-model 也可以接受一個參數:
<UserName
v-model:first-name="first"
v-model:last-name="last"/>
<MyComponent v-model:title="bookTitle" />
在子組件中,我們可以通過將字符串作為第一個參數傳遞給 defineModel() 來支持相應的參數:
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
- v-model 修飾符
- .lazy
默認情況下,v-model 會在每次 input 事件後更新數據 (IME 拼字階段的狀態例外)。你可以添加 lazy 修飾符來改為在每次 change 事件後更新數據:
<!-- 在 "change" 事件後同步更新而不是 "input" -->
<input v-model.lazy="msg" />
- .number
如果你想讓用戶輸入自動轉換為數字,你可以在 v-model 後添加 .number 修飾符來管理輸入:
<input v-model.number="age" />
如果該值無法被 parseFloat () 處理,那麼將返回原始值。 number 修飾符會在輸入框有 type="number" 時自動啟用。
3. .trim
如果你想自動過濾用戶輸入的首尾空白字符,可以添加 trim 修飾符到 v-model 上:
vue <input v-model.trim="msg" />
2.5 透傳 Attributes#
-
attribute 繼承
透傳 attribute 是指將父組件的 attribute 傳遞給子組件。如果這個屬性沒有被聲明為props 和 emits,那麼這個屬性會被傳遞給子組件。<Child id="foo" class="bar" />
最常見的例子就是 class、style 和 id。 當一個組件以單個元素為根作渲染時,透傳的 attribute 會自動被添加到根元素上。舉例來說,假如我們有一個 組件,它的模板長這樣:
<!-- <MyButton> 的模板 --> <button>點擊我</button> 一個父組件使用了這個組件,並且傳入了 class: <MyButton class="large" /> 最後渲染出的 DOM 結果是: <button class="large">點擊我</button>
簡單而言就是如果你在組件外添加的屬性,既不是 props 也不是 emits,這個屬性會自動加載給組件裡的最外層數據。
一般可以用來 class 和 style 的合併,就是你外邊寫了 class,那裡面也會合併過來。 -
v-on 監聽器基礎,就是說你可以在組件上添加事件監聽器,然後在組件內部去觸發這個事件。
<Child v-on:custom-event="handleCustomEvent" />
click 監聽器會被添加到
<Child>
的根元素,即那個原生的<button>
元素之上。當原生的<button>
被點擊,會觸發父組件的 onClick 方法。
同樣的,如果原生button
元素自身也通過 v-on 綁定了一個事件監聽器,則這個監聽器和從父組件繼承的監聽器都會被觸發。
所以,這個監聽其實可以打通子組件和父組件之間的聯繫。 -
透傳具有連續性
就是可以一個透傳一個,A 傳 B,B 傳 C 可以連續傳。