這里有一個 Vue 組件的示例:
// 創(chuàng)建一個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>`
})
INFO
在這里演示的是一個簡單的示例,但是在典型的 Vue 應用程序中,我們使用單個文件組件而不是字符串模板。你可以在本節(jié)找到有關它們的更多信息。
組件是可復用的組件實例,且?guī)в幸粋€名字:在這個例子中是 <button-counter>
。我們可以在一個通過 new Vue 創(chuàng)建的 Vue 根實例中,把這個組件作為自定義元素來使用:
<div id="components-demo">
<button-counter></button-counter>
</div>
app.mount('#components-demo')
因為組件是可復用的組件實例,所以它們與 new Vue 接收相同的選項,例如 data
、computed
、watch
、methods
以及生命周期鉤子等。僅有的例外是像 el
這樣根實例特有的選項。
你可以將組件進行任意次數(shù)的復用:
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
注意當點擊按鈕時,每個組件都會各自獨立維護它的 count
。因為你每用一次組件,就會有一個它的新實例被創(chuàng)建。
通常一個應用會以一棵嵌套的組件樹的形式來組織
例如,你可能會有頁頭、側邊欄、內(nèi)容區(qū)等組件,每個組件又包含了其它的像導航鏈接、博文之類的組件。
為了能在模板中使用,這些組件必須先注冊以便 Vue 能夠識別。這里有兩種組件的注冊類型:全局注冊和局部注冊。至此,我們的組件都只是通過 component
全局注冊的:
const app = Vue.createApp({})
app.component('my-component-name', {
// ... 選項 ...
})
全局注冊的組件可以在隨后創(chuàng)建的 app
實例模板中使用,也包括根實例組件樹中的所有子組件的模板中。
到目前為止,關于組件注冊你需要了解的就這些了,如果你閱讀完本頁內(nèi)容并掌握了它的內(nèi)容,我們會推薦你再回來把組件注冊讀完。
早些時候,我們提到了創(chuàng)建一個博文組件的事情。問題是如果你不能向這個組件傳遞某一篇博文的標題或內(nèi)容之類的我們想展示的數(shù)據(jù)的話,它是沒有辦法使用的。這也正是 prop 的由來。
Prop 是你可以在組件上注冊的一些自定義 attribute。當一個值傳遞給一個 prop attribute 的時候,它就變成了那個組件實例的一個 property。為了給博文組件傳遞一個標題,我們可以用一個 props 選項將其包含在該組件可接受的 prop
列表中:
const app = Vue.createApp({})
app.component('blog-post', {
props: ['title'],
template: `<h4>{{ title }}</h4>`
})
app.mount('#blog-post-demo')
一個組件默認可以擁有任意數(shù)量的 prop,任何值都可以傳遞給任何 prop。在上述模板中,你會發(fā)現(xiàn)我們能夠在組件實例中訪問這個值,就像訪問 data
中的值一樣。
一個 prop 被注冊之后,你就可以像這樣把數(shù)據(jù)作為一個自定義 attribute 傳遞進來:
<div id="blog-post-demo" class="demo">
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
</div>
然而在一個典型的應用中,你可能在 data
里有一個博文的數(shù)組:
const App = {
data() {
return {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
}
}
const app = Vue.createApp({})
app.component('blog-post', {
props: ['title'],
template: `<h4>{{ title }}</h4>`
})
app.mount('#blog-posts-demo')
并想要為每篇博文渲染一個組件:
<div id="blog-posts-demo">
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
></blog-post>
</div>
如上所示,你會發(fā)現(xiàn)我們可以使用 v-bind
來動態(tài)傳遞 prop。這在你一開始不清楚要渲染的具體內(nèi)容,是非常有用的。
到目前為止,關于 prop 你需要了解的大概就這些了,如果你閱讀完本頁內(nèi)容并掌握了它的內(nèi)容,我們會推薦你再回來把 prop 讀完。
在我們開發(fā) <blog-post>
組件時,它的一些功能可能要求我們和父級組件進行溝通。例如我們可能會引入一個輔助功能來放大博文的字號,同時讓頁面的其它部分保持默認的字號。
在其父組件中,我們可以通過添加一個 postFontSize
數(shù)據(jù) property 來支持這個功能:
const App = {
data() {
return {
posts: [
/* ... */
],
postFontSize: 1
}
}
}
它可以在模板中用來控制所有博文的字號:
<div id="blog-posts-events-demo">
<div v-bind:style="{ fontSize: postFontSize + 'em' }">
<blog-post v-for="post in posts" :key="post.id" :title="title"></blog-post>
</div>
</div>
現(xiàn)在我們在每篇博文正文之前添加一個按鈕來放大字號:
app.component('blog-post', {
props: ['title'],
template: `
<div class="blog-post">
<h4>{{ title }}</h4>
<button>
Enlarge text
</button>
</div>
`
})
問題是這個按鈕不會做任何事:
<button>
Enlarge text
</button>
當點擊這個按鈕時,我們需要告訴父級組件放大所有博文的文本。幸好組件實例提供了一個自定義事件的系統(tǒng)來解決這個問題。父級組件可以像處理 native DOM 事件一樣通過 v-on
或 @
監(jiān)聽子組件實例的任意事件:
<blog-post ... @enlarge-text="postFontSize += 0.1"></blog-post>
同時子組件可以通過調(diào)用內(nèi)建的 $emit 方法并傳入事件名稱來觸發(fā)一個事件:
<button @click="$emit('enlarge-text')">
Enlarge text
</button>
多虧了 @enlarge-text="postFontSize += 0.1"
監(jiān)聽器,父級將接收事件并更新 postFontSize
值。
點擊此處實現(xiàn)
我們可以在組件的 emits
選項中列出已拋出的事件。
app.component('blog-post', {
props: ['title'],
emits: ['enlarge-text']
})
這將允許你檢查組件拋出的所有事件,還可以選擇 validate them
有的時候用一個事件來拋出一個特定的值是非常有用的。例如我們可能想讓 <blog-post>
組件決定它的文本要放大多少。這時可以使用 $emit
的第二個參數(shù)來提供這個值:
<button @click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
然后當在父級組件監(jiān)聽這個事件的時候,我們可以通過 $event
訪問到被拋出的這個值:
<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>
或者,如果這個事件處理函數(shù)是一個方法:
<blog-post ... @enlarge-text="onEnlargeText"></blog-post>
那么這個值將會作為第一個參數(shù)傳入這個方法:
methods: {
onEnlargeText(enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
自定義事件也可以用于創(chuàng)建支持 v-model
的自定義輸入組件。記?。?/p>
<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>
WARNING
請注意,我們在這里使用的是 model value
,因為我們使用的是 DOM 模板中的 kebab-case。你可以在 DOM Template Parsing Caveats 部分找到關于 kebab cased 和 camelCased 屬性的詳細說明
為了讓它正常工作,這個組件內(nèi)的 <input>
必須:
value
attribute 綁定到一個名叫 modelValue
的 prop 上input
事件被觸發(fā)時,將新的值通過自定義的 update:modelValue
事件拋出寫成代碼之后是這樣的:
app.component('custom-input', {
props: ['modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
現(xiàn)在 v-model
就應該可以在這個組件上完美地工作起來了:
<custom-input v-model="searchText"></custom-input>
在自定義組件中創(chuàng)建 v-model
功能的另一種方法是使用 computed
property 的功能來定義 getter 和 setter。
在下面的示例中,我們使用計算屬性重構 <custom-input>
組件。
請記住,get
方法應返回 modelValue
property,或用于綁定的任何 property,set
方法應為該 property 觸發(fā)相應的 $emit
。
app.component('custom-input', {
props: ['modelValue'],
template: `
<input v-model="value">
`,
computed: {
value: {
get() {
return this.modelValue
},
set(value) { this.$emit('update:modelValue', value)
}
}
}
})
現(xiàn)在你只需要了解自定義組件事件,但一旦你讀完本頁并對其內(nèi)容還覺得不錯,我們建議你稍后再閱讀有關自定義事件
和 HTML 元素一樣,我們經(jīng)常需要向一個組件傳遞內(nèi)容,像這樣:
<alert-box>
Something bad happened.
</alert-box>
可能會渲染出這樣的東西: 點擊此處實現(xiàn)
幸好,Vue 自定義的 <slot>
元素讓這變得非常簡單:
app.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
如你所見,我們只要在需要的地方加入插槽就行了——就這么簡單!
到目前為止,關于插槽你需要了解的大概就這些了,如果你閱讀完本頁內(nèi)容并掌握了它的內(nèi)容,我們會推薦你再回來把插槽讀完。
有的時候,在不同組件之間進行動態(tài)切換是非常有用的,比如在一個多標簽的界面里: 點擊此處實現(xiàn)
上述內(nèi)容可以通過 Vue 的 <component>
元素加一個特殊的 is
attribute 來實現(xiàn):
<!-- 組件會在 `currentTabComponent` 改變時改變 -->
<component :is="currentTabComponent"></component>
在上述示例中,currentTabComponent
可以包括
你可以在這里查閱并體驗完整的代碼,或在這個版本了解綁定組件選項對象,而不是已注冊組件名的示例。
請留意,這個 attribute 可以用于常規(guī) HTML 元素,但這些元素將被視為組件,這意味著所有的 attribute 都會作為 DOM attribute 被綁定。對于像 value
這樣的 property,若想讓其如預期般工作,你需要使用 .prop 修飾器。
到目前為止,關于動態(tài)組件你需要了解的大概就這些了,如果你閱讀完本頁內(nèi)容并掌握了它的內(nèi)容,我們會推薦你再回來把動態(tài) & 異步組件讀完。
有些 HTML 元素,諸如 <ul>
、<ol>
、<table>
和 <select>
,對于哪些元素可以出現(xiàn)在其內(nèi)部是有嚴格限制的。而有些元素,諸如 <li>
、<tr>
和 <option>
,只能出現(xiàn)在其它某些特定的元素內(nèi)部。
這會導致我們使用這些有約束條件的元素時遇到一些問題。例如:
<table>
<blog-post-row></blog-post-row>
</table>
這個自定義組件 <blog-post-row>
會被作為無效的內(nèi)容提升到外部,并導致最終渲染結果出錯。幸好這個特殊的 v-is
attribute 給了我們一個變通的辦法:
<table>
<tr v-is="'blog-post-row'"></tr>
</table>
WARNING
v-is
值應為 JavaScript 字符串文本:
<!-- 錯誤的,這樣不會渲染任何東西 -->
<tr v-is="blog-post-row"></tr>
<!-- 正確的 -->
<tr v-is="'blog-post-row'"></tr>
另外,HTML 屬性名不區(qū)分大小寫,因此瀏覽器將把所有大寫字符解釋為小寫。這意味著當你在 DOM 模板中使用時,駝峰 prop 名稱和 event 處理器參數(shù)需要使用它們的 kebab-cased (橫線字符分隔) 等效值:
// 在JavaScript中的駝峰
app.component('blog-post', {
props: ['postTitle'],
template: `
<h3>{{ postTitle }}</h3>
`
})
<!-- 在HTML則是橫線字符分割 -->
<blog-post post-title="hello!"></blog-post>
需要注意的是如果我們從以下來源使用模板的話,這條限制是*不存在*的:
template: '...'
)<script type="text/x-template">
到這里,你需要了解的解析 DOM 模板時的注意事項——實際上也是 Vue 的全部必要內(nèi)容,大概就是這些了。恭喜你!接下來還有很多東西要去學習,不過首先,我們推薦你先休息一下,試用一下 Vue,自己隨意做些好玩的東西。
如果你感覺已經(jīng)掌握了這些知識,我們推薦你再回來把完整的組件&異步組件指南,包括側邊欄中組件深入章節(jié)的所有頁面讀完。
更多建議: