什么是組件?
組件(Component)是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素, Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以是原生 HTML 元素的形式,以 is 特性擴展。
之前說過,我們可以通過以下方式創(chuàng)建一個 Vue 實例:
new Vue({ el: '#some-element', // 選項 })
要注冊一個全局組件,你可以使用 Vue.component(tagName, options)。 例如:
Vue.component('my-component', { // 選項 })
對于自定義標簽名,Vue.js 不強制要求遵循 W3C規(guī)則 (小寫,并且包含一個短杠),盡管遵循這個規(guī)則比較好。
組件在注冊之后,便可以在父實例的模塊中以自定義元素 的形式使用。要確保在初始化根實例之前注冊了組件:
// 注冊 Vue.component('my-component', { template: 'A custom component!' }) // 創(chuàng)建根實例 new Vue({ el: '#example' })
渲染為:
A custom component!
不必在全局注冊每個組件。通過使用組件實例選項注冊,可以使組件僅在另一個實例/組件的作用域中可用:
var Child = { template: 'A custom component!' } new Vue({ // ... components: { // 將只在父模板可用 'my-component': Child } })
這種封裝也適用于其它可注冊的 Vue 功能,如指令。
當使用 DOM 作為模版時(例如,將 el 選項掛載到一個已存在的元素上), 你會受到 HTML 的一些限制,因為 Vue 只有在瀏覽器解析和標準化 HTML 后才能獲取模版內(nèi)容。尤其像這些元素
在自定義組件中使用這些受限制的元素時會導(dǎo)致一些問題,例如:
...
自定義組件 被認為是無效的內(nèi)容,因此在渲染的時候會導(dǎo)致錯誤。變通的方案是使用特殊的 is 屬性:
應(yīng)當注意,如果您使用來自以下來源之一的字符串模板,這些限制將不適用:
因此,有必要的話請使用字符串模版。
使用組件時,大多數(shù)選項可以被傳入到 Vue 構(gòu)造器中,有一個例外: data 必須是函數(shù)。 實際上,如果你這么做:
Vue.component('my-component', { template: '{{ message }}', data: { message: 'hello' } })
那么 Vue 會在控制臺發(fā)出警告,告訴你在組件中 data 必須是一個函數(shù)。最好理解這種規(guī)則的存在意義。
var data = { counter: 0 } Vue.component('simple-counter', { template: '{{ counter }}', // data 是一個函數(shù),因此 Vue 不會警告, // 但是我們?yōu)槊恳粋€組件返回了同一個對象引用 data: function () { return data } }) new Vue({ el: '#example-2' })
由于這三個組件共享了同一個 data , 因此增加一個 counter 會影響所有組件!我們可以通過為每個組件返回新的 data 對象來解決這個問題:
data: function () { return { counter: 0 } }
現(xiàn)在每個 counter 都有它自己內(nèi)部的狀態(tài)了
組件意味著協(xié)同工作,通常父子組件會是這樣的關(guān)系:組件 A 在它的模版中使用了組件 B 。它們之間必然需要相互通信:父組件要給子組件傳遞數(shù)據(jù),子組件需要將它內(nèi)部發(fā)生的事情告知給父組件。然而,在一個良好定義的接口中盡可能將父子組件解耦是很重要的。這保證了每個組件可以在相對隔離的環(huán)境中書寫和理解,也大幅提高了組件的可維護性和可重用性。
在 Vue.js 中,父子組件的關(guān)系可以總結(jié)為 props down, events up 。父組件通過 props 向下傳遞數(shù)據(jù)給子組件,子組件通過 events 給父組件發(fā)送消息??纯此鼈兪窃趺垂ぷ鞯?。
組件實例的作用域是孤立的。這意味著不能并且不應(yīng)該在子組件的模板內(nèi)直接引用父組件的數(shù)據(jù)??梢允褂?props 把數(shù)據(jù)傳給子組件。
prop 是父組件用來傳遞數(shù)據(jù)的一個自定義屬性。子組件需要顯式地用 props 選項 聲明 “prop”:
Vue.component('child', { // 聲明 props props: ['message'], // 就像 data 一樣,prop 可以用在模板內(nèi) // 同樣也可以在 vm 實例中像 “this.message” 這樣使用 template: '{{ message }}' })
然后向它傳入一個普通字符串:
結(jié)果:
hello!
HTML 特性不區(qū)分大小寫。當使用非字符串模版時,prop的名字形式會從 camelCase 轉(zhuǎn)為 kebab-case(短橫線隔開):
Vue.component('child', { // camelCase in JavaScript props: ['myMessage'], template: '{{ myMessage }}' })
再次說明,如果你使用字符串模版,不用在意這些限制。
類似于用 v-bind 綁定 HTML 特性到一個表達式,也可以用 v-bind 動態(tài)綁定 props 的值到父組件的數(shù)據(jù)中。每當父組件的數(shù)據(jù)變化時,該變化也會傳導(dǎo)給子組件:
使用 v-bind 的縮寫語法通常更簡單:
初學(xué)者常犯的一個錯誤是使用字面量語法傳遞數(shù)值:
因為它是一個字面 prop ,它的值以字符串 "1" 而不是以實際的數(shù)字傳下去。如果想傳遞一個實際的 JavaScript 數(shù)字,需要使用 v-bind ,從而讓它的值被當作 JavaScript 表達式計算:
prop 是單向綁定的:當父組件的屬性變化時,將傳導(dǎo)給子組件,但是不會反過來。這是為了防止子組件無意修改了父組件的狀態(tài)——這會讓應(yīng)用的數(shù)據(jù)流難以理解。
另外,每次父組件更新時,子組件的所有 prop 都會更新為最新值。這意味著你不應(yīng)該在子組件內(nèi)部改變 prop 。如果你這么做了,Vue 會在控制臺給出警告。
通常有兩種改變 prop 的情況:
更確切的說這兩種情況是:
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
注意:在 JavaScript 中對象和數(shù)組是引用類型,指向同一個內(nèi)存空間,如果 prop 是一個對象或數(shù)組,在子組件內(nèi)部改變它會影響父組件的狀態(tài)。
組件可以為 props 指定驗證要求。如果未指定驗證要求,Vue 會發(fā)出警告。當組件給其他人使用時這很有用。
prop 是一個對象而不是字符串數(shù)組時,它包含驗證要求:
Vue.component('example', { props: { // 基礎(chǔ)類型檢測 (`null` 意思是任何類型都可以) propA: Number, // 多種類型 propB: [String, Number], // 必傳且是字符串 propC: { type: String, required: true }, // 數(shù)字,有默認值 propD: { type: Number, default: 100 }, // 數(shù)組/對象的默認值應(yīng)當由一個工廠函數(shù)返回 propE: { type: Object, default: function () { return { message: 'hello' } } }, // 自定義驗證函數(shù) propF: { validator: function (value) { return value > 10 } } } })
type 可以是下面原生構(gòu)造器:
type 也可以是一個自定義構(gòu)造器,使用 instanceof 檢測。
當 prop 驗證失敗了, Vue 將拒絕在子組件上設(shè)置此值,如果使用的是開發(fā)版本會拋出一條警告。
我們知道,父組件是使用 props 傳遞數(shù)據(jù)給子組件,但如果子組件要把數(shù)據(jù)傳遞回去,應(yīng)該怎樣做?那就是自定義事件!
每個 Vue 實例都實現(xiàn)了事件接口(Events interface),即:
Vue的事件系統(tǒng)分離自瀏覽器的EventTarget API。盡管它們的運行類似,但是$on和 $emit 不是 addEventListener 和 dispatchEvent 的別名。
另外,父組件可以在使用子組件的地方直接用 v-on 來監(jiān)聽子組件觸發(fā)的事件。
下面是一個例子:
{{ total }}
Vue.component('button-counter', { template: '{{ counter }}', data: function () { return { counter: 0 } }, methods: { increment: function () { this.counter += 1 this.$emit('increment') } }, }) new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal: function () { this.total += 1 } } })
在本例中,子組件已經(jīng)和它外部完全解耦了。它所做的只是觸發(fā)一個父組件關(guān)心的內(nèi)部事件。
有時候,你可能想在某個組件的根元素上監(jiān)聽一個原生事件??梢允褂?nbsp;.native 修飾 v-on 。例如:
自定義事件也可以用來創(chuàng)建自定義的表單輸入組件,使用 v-model 來進行數(shù)據(jù)雙向綁定。牢記:
僅僅是一個語法:
所以在組件中使用時,它相當于下面的簡寫:
所以要讓組件的 v-model 生效,它必須:
一個非常簡單的貨幣輸入:
Vue.component('currency-input', { template: '\ \ $\ \ \ ', props: ['value'], methods: { // Instead of updating the value directly, this // method is used to format and place constraints // on the input's value updateValue: function (value) { var formattedValue = value // Remove whitespace on either side .trim() // Shorten to 2 decimal places .slice(0, value.indexOf('.') + 3) // If the value was not already normalized, // manually override it to conform if (formattedValue !== value) { this.$refs.input.value = formattedValue } // Emit the number value through the input event this.$emit('input', Number(formattedValue)) } } })
上面的實現(xiàn)方式太過理想化了。 比如,用戶甚至可以輸入多個小數(shù)點或句號 - 呸!因此我們需要一個更有意義的例子,下面是一個更加完善的貨幣過濾器:
這個接口不僅僅可以用來連接組件內(nèi)部的表單輸入,也很容易集成你自己創(chuàng)造的輸入類型。想象一下:
有時候非父子關(guān)系的組件也需要通信。在簡單的場景下,使用一個空的 Vue 實例作為中央事件總線:
var bus = new Vue()
// 觸發(fā)組件 A 中的事件 bus.$emit('id-selected', 1)
// 在組件 B 創(chuàng)建的鉤子中監(jiān)聽事件 bus.$on('id-selected', function (id) { // ... })
在更多復(fù)雜的情況下,你應(yīng)該考慮使用專門的狀態(tài)管理模式.
在使用組件時,常常要像這樣組合它們:
注意兩點:
為了讓組件可以組合,我們需要一種方式來混合父組件的內(nèi)容與子組件自己的模板。這個過程被稱為內(nèi)容分發(fā) (或 “transclusion” 如果你熟悉 Angular)。Vue.js 實現(xiàn)了一個內(nèi)容分發(fā) API ,參照了當前 Web組件規(guī)范草案,使用特殊的 元素作為原始內(nèi)容的插槽。
在深入內(nèi)容分發(fā) API 之前,我們先明確內(nèi)容的編譯作用域。假定模板為:
{{ message }}
message 應(yīng)該綁定到父組件的數(shù)據(jù),還是綁定到子組件的數(shù)據(jù)?答案是父組件。組件作用域簡單地說是:
父組件模板的內(nèi)容在父組件作用域內(nèi)編譯;子組件模板的內(nèi)容在子組件作用域內(nèi)編譯。
一個常見錯誤是試圖在父組件模板內(nèi)將一個指令綁定到子組件的屬性/方法:
假定 someChildProperty 是子組件的屬性,上例不會如預(yù)期那樣工作。父組件模板不應(yīng)該知道子組件的狀態(tài)。
如果要綁定子組件內(nèi)的指令到一個組件的根節(jié)點,應(yīng)當在它的模板內(nèi)這么做:
Vue.component('child-component', { // 有效,因為是在正確的作用域內(nèi) template: 'Child', data: function () { return { someChildProperty: true } } })
類似地,分發(fā)內(nèi)容是在父組件作用域內(nèi)編譯。
除非子組件模板包含至少一個 插口,否則父組件的內(nèi)容將會被丟棄。當子組件模板只有一個沒有屬性的 slot 時,父組件整個內(nèi)容片段將插入到 slot 所在的 DOM 位置,并替換掉 slot 標簽本身。
最初在 標簽中的任何內(nèi)容都被視為備用內(nèi)容。備用內(nèi)容在子組件的作用域內(nèi)編譯,并且只有在宿主元素為空,且沒有要插入的內(nèi)容時才顯示備用內(nèi)容。
假定 my-component 組件有下面模板:
I'm the child title 如果沒有分發(fā)內(nèi)容則顯示我。
父組件模版:
I'm the parent title This is some original content This is some more original content
This is some original content
This is some more original content
渲染結(jié)果:
I'm the parent title I'm the child title This is some original content This is some more original content
元素可以用一個特殊的屬性 name 來配置如何分發(fā)內(nèi)容。多個 slot 可以有不同的名字。具名 slot 將匹配內(nèi)容片段中有對應(yīng) slot 特性的元素。
仍然可以有一個匿名 slot ,它是默認 slot ,作為找不到匹配的內(nèi)容片段的備用插槽。如果沒有默認的 slot ,這些找不到匹配的內(nèi)容片段將被拋棄。
例如,假定我們有一個 app-layout 組件,它的模板為:
Here might be a page title A paragraph for the main content. And another one. Here's some contact info
A paragraph for the main content.
And another one.
Here's some contact info
渲染結(jié)果為:
Here might be a page title A paragraph for the main content. And another one.
在組合組件時,內(nèi)容分發(fā) API 是非常有用的機制。
多個組件可以使用同一個掛載點,然后動態(tài)地在它們之間切換。使用保留的 元素,動態(tài)地綁定到它的 is 特性:
var vm = new Vue({ el: '#example', data: { currentView: 'home' }, components: { home: { /* ... */ }, posts: { /* ... */ }, archive: { /* ... */ } } })
也可以直接綁定到組件對象上:
var Home = { template: 'Welcome home!' } var vm = new Vue({ el: '#example', data: { currentView: Home } })
Welcome home!
如果把切換出去的組件保留在內(nèi)存中,可以保留它的狀態(tài)或避免重新渲染。為此可以添加一個 keep-alive 指令參數(shù):
在 API 參考查看更多 的細節(jié)。
在編寫組件時,記住是否要復(fù)用組件有好處。一次性組件跟其它組件緊密耦合沒關(guān)系,但是可復(fù)用組件應(yīng)當定義一個清晰的公開接口。
Vue 組件的 API 來自三部分 - props, events 和 slots :
使用 v-bind 和 v-on 的簡寫語法,模板的縮進清楚且簡潔:
Hello!
盡管有 props 和 events ,但是有時仍然需要在 JavaScript 中直接訪問子組件。為此可以使用 ref 為子組件指定一個索引 ID 。例如:
var parent = new Vue({ el: '#parent' }) // 訪問子組件 var child = parent.$refs.profile
當 ref 和 v-for 一起使用時, ref 是一個數(shù)組或?qū)ο螅鄳?yīng)的子組件。
$refs 只在組件渲染完成后才填充,并且它是非響應(yīng)式的。它僅僅作為一個直接訪問子組件的應(yīng)急方案——應(yīng)當避免在模版或計算屬性中使用 $refs 。
在大型應(yīng)用中,我們可能需要將應(yīng)用拆分為多個小模塊,按需從服務(wù)器下載。為了讓事情更簡單, Vue.js 允許將組件定義為一個工廠函數(shù),動態(tài)地解析組件的定義。Vue.js 只在組件需要渲染時觸發(fā)工廠函數(shù),并且把結(jié)果緩存起來,用于后面的再次渲染。例如:
Vue.component('async-example', function (resolve, reject) { setTimeout(function () { resolve({ template: 'I am async!' }) }, 1000) })
工廠函數(shù)接收一個 resolve 回調(diào),在收到從服務(wù)器下載的組件定義時調(diào)用。也可以調(diào)用 reject(reason) 指示加載失敗。這里 setTimeout 只是為了演示。怎么獲取組件完全由你決定。推薦配合使用 :Webpack 的代碼分割功能:
Vue.component('async-webpack-example', function (resolve) { // 這個特殊的 require 語法告訴 webpack // 自動將編譯后的代碼分割成不同的塊, // 這些塊將通過 Ajax 請求自動下載。 require(['./my-async-component'], resolve) })
你可以使用 Webpack 2 + ES2015 的語法返回一個 Promise resolve 函數(shù):
Vue.component( 'async-webpack-example', () => System.import('./my-async-component') )
如果你是 Browserify 用戶,可能就無法使用異步組件了,它的作者已經(jīng)表明Browserify 是不支持異步加載的。如果這個功能對你很重要,請使用 Webpack。
當注冊組件(或者 props)時,可以使用 kebab-case ,camelCase ,或 TitleCase 。Vue 不關(guān)心這個。
// 在組件定義中 components: { // 使用 camelCase 形式注冊 'kebab-cased-component': { /* ... */ }, 'camelCasedComponent': { /* ... */ }, 'TitleCasedComponent': { /* ... */ } }
在 HTML 模版中,請使用 kebab-case 形式:
當使用字符串模式時,可以不受 HTML 的 case-insensitive 限制。這意味實際上在模版中,你可以使用 camelCase 、 PascalCase 或者 kebab-case 來引用你的組件和 prop:
如果組件未經(jīng) slot 元素傳遞內(nèi)容,你甚至可以在組件名后使用 / 使其自閉合:
當然,這只在字符串模版中有效。因為自閉的自定義元素是無效的 HTML ,瀏覽器原生的解析器也無法識別它。
組件在它的模板內(nèi)可以遞歸地調(diào)用自己,不過,只有當它有 name 選項時才可以:
name: 'unique-name-of-my-component'
當你利用Vue.component全局注冊了一個組件, 全局的ID作為組件的 name 選項,被自動設(shè)置。
Vue.component('unique-name-of-my-component', { // ... })
如果你不謹慎, 遞歸組件可能導(dǎo)致死循環(huán):
name: 'stack-overflow', template: ''
上面組件會導(dǎo)致一個錯誤 “max stack size exceeded” ,所以要確保遞歸調(diào)用有終止條件 (比如遞歸調(diào)用時使用 v-if 并讓他最終返回 false )。
如果子組件有 inline-template 特性,組件將把它的內(nèi)容當作它的模板,而不是把它當作分發(fā)內(nèi)容。這讓模板更靈活。
These are compiled as the component's own template. Not parent's transclusion content.
These are compiled as the component's own template.
Not parent's transclusion content.
但是 inline-template 讓模板的作用域難以理解。最佳實踐是使用 template 選項在組件內(nèi)定義模板或者在 .vue 文件中使用 template 元素。
另一種定義模版的方式是在 JavaScript 標簽里使用 text/x-template 類型,并且指定一個id。例如:
Vue.component('hello-world', { template: '#hello-world-template' })
這在有很多模版或者小的應(yīng)用中有用,否則應(yīng)該避免使用,因為它將模版和組件的其他定義隔離了。
使用 v-once 的低級靜態(tài)組件(Cheap Static Component)
盡管在 Vue 中渲染 HTML 很快,不過當組件中包含大量靜態(tài)內(nèi)容時,可以考慮使用 v-once將渲染結(jié)果緩存起來,就像這樣:
Vue.component('terms-of-service', { template: '\ \ Terms of Service\ ... a lot of static content ...\ \ ' })
公司名稱:四川綿陽平武網(wǎng)站建設(shè)工作室 聯(lián)系電話:18980820575
網(wǎng)站備案號:蜀ICP備2024061352號-3
四川平武建站 四川平武網(wǎng)站建設(shè) 四川平武網(wǎng)站設(shè)計 四川平武網(wǎng)站制作 成都做網(wǎng)站