今天有空就把vue3文檔從頭到尾看了一遍。就當做對vue從頭開始的學習和補漏,理解了一些用法改變的原理。最明顯的地方就是響應式的改變,vue2用的是Object.defineProperty()
,vue3用的是Proxy
。vue3還新增了組合式API
等等。
這次記錄主要是記錄一些基礎的,面試常被問到的,查漏補缺。
應用&組件實例
應用實例
每個 Vue 應用都是通過用 createApp 函數創建一個新的應用實例開始的:
const app = Vue.createApp({ /* 選項 */ })
該應用實例是用來在應用中注冊“全局”組件的。我們將在后面的指南中詳細討論,簡單的例子:
const app = Vue.createApp({})
app.component('SearchInput', SearchInputComponent)
app.directive('focus', FocusDirective)
app.use(LocalePlugin)
應用實例暴露的大多數方法都會返回該同一實例,允許鏈式:
Vue.createApp({})
.component('SearchInput', SearchInputComponent)
.directive('focus', FocusDirective)
.use(LocalePlugin)
根組件實例
傳遞給 createApp 的選項用于配置根組件。當我們掛載應用時,該組件被用作渲染的起點。
一個應用需要被掛載到一個 DOM 元素中。例如,如果我們想把一個 Vue 應用掛載到 <div id="app"></div>
,我們應該傳遞 #app
:
const RootComponent = { /* 選項 */ }
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app')
與大多數應用方法不同的是,mount
不返回應用本身。相反,它返回的是根組件實例。
雖然沒有完全遵循 MVVM
模型,但是 Vue 的設計也受到了它的啟發。因此在文檔中經常會使用 vm (ViewModel 的縮寫) 這個變量名表示組件實例。
計算屬性&偵聽器
計算屬性緩存 vs 方法
你可能已經注意到我們可以通過在表達式中調用方法來達到同樣的效果:
<p>{{ calculateBooksMessage() }}</p>
// 在組件中
methods: {
calculateBooksMessage() {
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
我們可以將同一函數定義為一個方法而不是一個計算屬性。兩種方式的最終結果確實是完全相同的。然而,不同的是計算屬性是基于它們的反應依賴關系緩存的。計算屬性只在相關響應式依賴發生改變時它們才會重新求值。這就意味著只要author.books
還沒有發生改變,多次訪問 publishedBookMessage
計算屬性會立即返回之前的計算結果,而不必再次執行函數。
這也同樣意味著下面的計算屬性將不再更新,因為 Date.now () 不是響應式依賴:
computed: {
now() {
return Date.now()
}
}
相比之下,每當觸發重新渲染時,調用方法將總會再次執行函數。
我們為什么需要緩存?假設我們有一個性能開銷比較大的計算屬性 list
,它需要遍歷一個巨大的數組并做大量的計算。然后我們可能有其他的計算屬性依賴于
list
。如果沒有緩存,我們將不可避免的多次執行 list
的 getter!如果你不希望有緩存,請用 method
來替代
計算屬性的 Setter
計算屬性默認只有 getter,不過在需要時你也可以提供一個 setter:
// ...
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...
現在再運行 vm.fullName = 'John Doe'
時,setter 會被調用,vm.firstName
和 vm.lastName
也會相應地被更新。
Class與Style綁定
如果你的組件有多個根元素,你需要定義哪些部分將接收這個類。可以使用 $attrs 組件屬性執行此操作:
<div id="app">
<my-component class="baz"></my-component>
</div>
const app = Vue.createApp({})
app.component('my-component', {
template: `
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>
`
})
條件渲染
v-if
vs v-show
v-if
是“真正”的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷毀和重建。
v-if
也是惰性的:如果在初始渲染時條件為假,則什么也不做——直到條件第一次變為真時,才會開始渲染條件塊。
相比之下,v-show
就簡單得多——不管初始條件是什么,元素總是會被渲染,并且只是簡單地基于 CSS 進行切換。
一般來說,v-if
有更高的切換開銷,而 v-show
有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用 v-show
較好;如果在運行時條件很少改變,則使用 v-if
較好
v-if 與 v-for 一起使用
提示 不推薦同時使用 v-if 和 v-for
當 v-if
與 v-for
一起使用時,v-if
具有比 v-for
更高的優先級。請查閱列表渲染指南以獲取詳細信息。
當它們處于同一節點,v-if
的優先級比 v-for
更高,這意味著 v-if
將沒有權限訪問 v-for
里的變量:
<!-- This will throw an error because property "todo" is not defined on instance. -->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>
可以把 v-for 移動到 <template> 標簽中來修正:
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo }}
</li>
</template>
表單綁定輸入
基礎用法
v-model
在內部為不同的輸入元素使用不同的 property 并拋出不同的事件:
- text 和 textarea 元素使用 value property 和 input 事件;
- checkbox 和 radio 使用 checked property 和 change 事件;
- select 字段將 value 作為 prop 并將 change 作為事件。
組件基礎
基本實例
這里有一個 Vue 組件的示例:
// 創建一個Vue 應用
const app = Vue.createApp({})
// 定義一個名為 button-counter 的新全局組件
app.component('button-counter', {
data() {
return {
count: 0
}
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
})
組件的復用
你可以將組件進行任意次數的復用
每個組件都會各自獨立維護它的data
。因為你每用一次組件,就會有一個它的新組件實例被創建
在組件上使用 v-model
自定義事件也可以用于創建支持 v-model
的自定義輸入組件。記住:
<input v-model="searchText" />
等價于:
<input :value="searchText" @input="searchText = $event.target.value" />
當用在組件上時,v-model 則會這樣:
<custom-input
:model-value="searchText"
@update:model-value="searchText = $event"
></custom-input>
為了讓它正常工作,這個組件內的 <input>
必須:
將其 value
attribute 綁定到一個名叫 modelValue
的 prop 上
在其 input
事件被觸發時,將新的值通過自定義的 update:modelValue
事件拋出
寫成代碼之后是這樣的:
app.component('custom-input', {
props: ['modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
現在 v-model
就應該可以在這個組件上完美地工作起來了:
<custom-input v-model="searchText"></custom-input>
深入組件
非 Prop 的 Attribute
Attribute 繼承
當組件返回單個根節點時,非 prop attribute 將自動添加到根節點的 attribute 中。例如,在 <date-picker>
組件的實例中:
app.component('date-picker', {
template: `
<div class="date-picker">
<input type="datetime" />
</div>
`
})
如果我們需要通過 data status
property 定義 <date-picker>
組件的狀態,它將應用于根節點 (即 div.date-picker
)。
<!-- 具有非prop attribute的Date-picker組件-->
<date-picker data-status="activated"></date-picker>
<!-- 渲染 date-picker 組件 -->
<div class="date-picker" data-status="activated">
<input type="datetime" />
</div>
同樣的規則適用于事件監聽器:
<date-picker @change="submitChange"></date-picker>
app.component('date-picker', {
created() {
console.log(this.$attrs) // { onChange: () => {} }
}
})
當有一個 HTML 元素將 change 事件作為 date-picker 的根元素時,這可能會有幫助。
app.component('date-picker', {
template: `
<select>
<option value="1">Yesterday</option>
<option value="2">Today</option>
<option value="3">Tomorrow</option>
</select>
`
})
在這種情況下,change 事件監聽器從父組件傳遞到子組件,它將在原生 select 的 change 事件上觸發。我們不需要顯式地從 date-picker 發出事件:
<div id="date-picker" class="demo">
<date-picker @change="showChange"></date-picker>
</div>
const app = Vue.createApp({
methods: {
showChange(event) {
console.log(event.target.value) // 將記錄所選選項的值
}
}
})
禁用 Attribute 繼承
如果你不希望組件的根元素繼承 attribute,你可以在組件的選項中設置 inheritAttrs: false
。例如:
禁用 attribute 繼承的常見情況是需要將 attribute 應用于根節點之外的其他元素。
通過將 inheritAttrs
選項設置為 false
,你可以訪問組件的 $attrs
property,該 property 包括組件 props
和 emits
property 中未包含的所有屬性 (例如,class
、style
、v-on
監聽器等)。
app.component('date-picker', {
inheritAttrs: false,
template: `
<div class="date-picker">
<input type="datetime" v-bind="$attrs" />
</div>
`
})
有了這個新配置,data status
attribute 將應用于 input
元素!
<!-- Date-picker 組件 使用非 prop attribute -->
<date-picker data-status="activated"></date-picker>
<!-- 渲染 date-picker 組件 -->
<div class="date-picker">
<input type="datetime" data-status="activated" />
</div>
多個根節點上的 Attribute 繼承
與單個根節點組件不同,具有多個根節點的組件不具有自動 attribute 回退行為。如果未顯式綁定 $attrs,將發出運行時警告。
<custom-layout id="custom-layout" @click="changeValue"></custom-layout>
// 這將發出警告
app.component('custom-layout', {
template: `
<header>...</header>
<main>...</main>
<footer>...</footer>
`
})
// 沒有警告,$attrs被傳遞到<main>元素
app.component('custom-layout', {
template: `
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
`
})
自定義事件
處理 v-model 修飾符
在 2.x 中,我們對組件 v-model
上的 .trim
等修飾符提供了硬編碼支持。但是,如果組件可以支持自定義修飾符,則會更有用。在 3.x 中,添加到組件 v-model
的修飾符將通過 modelModifiers
prop 提供給組件:
讓我們創建一個示例自定義修飾符 capitalize
,它將 v-model
綁定提供的字符串的第一個字母大寫。
添加到組件 v-model
的修飾符將通過 modelModifiers
prop 提供給組件。在下面的示例中,我們創建了一個組件,其中包含默認為空對象的 modelModifiers
prop。
請注意,當組件的 created
生命周期鉤子觸發時,modelModifiers
prop 包含 capitalize
,其值為 true
——因為它被設置在 v-model
綁定 v-model.capitalize="bar"
。
<my-component v-model.capitalize="bar"></my-component>
app.component('my-component', {
props: {
modelValue: String,
modelModifiers: {
default: () => ({})
}
},
template: `
<input type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)">
`,
created() {
console.log(this.modelModifiers) // { capitalize: true }
}
})
現在我們已經設置了 prop,我們可以檢查 modelModifiers
對象鍵并編寫一個處理器來更改發出的值。在下面的代碼中,每當 <input/>
元素觸發 input
事件時,我們都將字符串大寫。
<div id="app">
<my-component v-model.capitalize="myText"></my-component>
{{ myText }}
</div>
const app = Vue.createApp({
data() {
return {
myText: ''
}
}
})
app.component('my-component', {
props: {
modelValue: String,
modelModifiers: {
default: () => ({})
}
},
methods: {
emitValue(e) {
let value = e.target.value
if (this.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
this.$emit('update:modelValue', value)
}
},
template: `<input
type="text"
:value="modelValue"
@input="emitValue">`
})
app.mount('#app')
對于帶參數的 v-model
綁定,生成的 prop 名稱將為 arg + "Modifiers"
:
<my-component v-model:foo.capitalize="bar"></my-component>
app.component('my-component', {
props: ['foo', 'fooModifiers'],
template: `
<input type="text"
:value="foo"
@input="$emit('update:foo', $event.target.value)">
`,
created() {
console.log(this.fooModifiers) // { capitalize: true }
}
})