深入組件 2
-
插槽
- v-slot Vue 2.6.0 新增饮睬,取代了 slot 和 slot-scope。
- 具名插槽和作用域插槽
- Vue 實(shí)現(xiàn)了一套內(nèi)容分發(fā)的 API 散罕,將 <slot> 元素作為承載分發(fā)內(nèi)容的出口兔院。
- 如何使用 slot 棍好?看下面這個(gè)例子
定義一個(gè) <navigation-link> 的組件如下:
然后使用這個(gè)組件:<a v-bind:href="url" class="nav-link" > <slot></slot> </a>
當(dāng)組件渲染的時(shí)候,<slot></slot> 將會(huì)被替換為“Your slot”豁跑。<navigation-link url=""> Your slot </navigation-link>
- 插槽內(nèi)可以包含任何模板代碼力细,包括 HTML,甚至其它的組件雀哨。
<navigation-link url="/profile"> <!-- 添加一個(gè) Font Awesome 圖標(biāo) --> <span class="fa fa-user"></span> Your Profile </navigation-link>
<navigation-link url="/profile"> <!-- 添加一個(gè)圖標(biāo)的組件 --> <font-awesome-icon name="user"></font-awesome-icon> Your Profile </navigation-link>
- 如果 <navigation-link> 的 template 中沒有包含一個(gè) <slot> 元素磕谅,則該組件起始標(biāo)簽和結(jié)束標(biāo)簽之間的任何內(nèi)容都會(huì)被拋棄私爷。看到這膊夹,我趕緊回到前面章節(jié)測(cè)試了一下衬浑,果然,沒有 <slot> 的普通組件在使用時(shí)( HTML 中)放刨,標(biāo)簽內(nèi)包含的內(nèi)容都不會(huì)被渲染出來工秩。
- 小結(jié):插槽,顧名思義进统,是預(yù)留下的接口助币,將來會(huì)填充東西,就像占位符一樣螟碎。Vue 中的插槽還不挑插入的內(nèi)容眉菱,啥都可以插入,字符串掉分、HTML俭缓、其他組件都行。一定要在注冊(cè)組件時(shí)就在 template 中包含 <slot> 表示此處將來會(huì)有內(nèi)容叉抡,不然使用組件時(shí)沒有插槽尔崔,你拿膠布粘上去的內(nèi)容沒用。:=)
-
作用域
- 插槽跟模板的其它地方一樣可以訪問相同的實(shí)例 property (也就是相同的“作用域”)褥民,即組件的 property季春?錯(cuò)!是父組件的 property消返。也就是說把帶插槽的組件當(dāng)做普通 HTML 標(biāo)簽一樣使用载弄。
<navigation-link>{{ name }}</navigation-link> // 類似于 <a>{{ name }}</a>
- 作為一條規(guī)則,請(qǐng)記啄旒铡:父級(jí)模板里的所有內(nèi)容都是在父級(jí)作用域中編譯的宇攻;子模板里的所有內(nèi)容都是在子作用域中編譯的。
- 怎么理解倡勇?
- 插槽跟模板的其它地方一樣可以訪問相同的實(shí)例 property (也就是相同的“作用域”)褥民,即組件的 property季春?錯(cuò)!是父組件的 property消返。也就是說把帶插槽的組件當(dāng)做普通 HTML 標(biāo)簽一樣使用载弄。
-
插槽默認(rèn)內(nèi)容
- 教程中稱之為“后備內(nèi)容”逞刷。感覺別扭0.0
- 默認(rèn)內(nèi)容只會(huì)在沒有提供任何插槽內(nèi)容的時(shí)候被渲染。
template
使用:<button type="submit"> <slot>Submit</slot> </button>
渲染結(jié)果:<submit-button></submit-button>
<button type="submit"> Submit </button>
-
- 當(dāng)你在一個(gè)組件中需要多個(gè)插槽時(shí)妻熊,便要用到具名插槽了夸浅。
- <slot> 元素有一個(gè)特殊的 attribute:name。這個(gè) attribute 可以用來定義額外的插槽扔役。一個(gè)不帶 name 的 <slot> 會(huì)帶有隱含的名字“default”帆喇。
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
- 那怎么使用具名插槽?v-slot 指令登場(chǎng)亿胸!在向具名插槽提供內(nèi)容的時(shí)候坯钦,我們可以在一個(gè) <template> 元素上使用 v-slot 指令预皇,并以 v-slot 的參數(shù)的形式提供其名稱。
<base-layout> <template v-slot:header> <h1>Here might be a page title</h1> </template> <p>A paragraph for the main content.</p> <p>And another one.</p> <template v-slot:footer> <p>Here's some contact info</p> </template> </base-layout>
- 現(xiàn)在 <template> 元素中的所有內(nèi)容都將會(huì)被傳入相應(yīng)的插槽婉刀。任何沒有被包裹在帶有 v-slot 的 <template> 中的內(nèi)容都會(huì)被視為默認(rèn)插槽的內(nèi)容吟温。任何沒有包裹的內(nèi)容都是 default 插槽的?即使沒有按照順序?qū)懀?/li>
- 如果你希望更明確一些路星,仍然可以在一個(gè) <template> 中包裹默認(rèn)插槽的內(nèi)容溯街。
<base-layout> <template v-slot:header> <h1>Here might be a page title</h1> </template> <template v-slot:default> <p>A paragraph for the main content.</p> <p>And another one.</p> </template> <template v-slot:footer> <p>Here's some contact info</p> </template> </base-layout>
- 注意 v-slot 只能添加在 <template> 標(biāo)簽上 (只有一種例外情況)诱桂。
-
- 只有 <current-user> 組件可以訪問到 user 而我們提供的內(nèi)容是在父級(jí)渲染的洋丐。為了讓 user 在父級(jí)的插槽內(nèi)容中可用,我們可以將 user 作為 <slot> 元素的一個(gè) attribute 綁定上去:
<span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span>
- 綁定在 <slot> 元素上的 attribute 被稱為插槽 prop』拥龋現(xiàn)在在父級(jí)作用域中友绝,我們可以使用帶值的 v-slot 來定義我們提供的插槽 prop 的名字:
<current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> </current-user>
- 在這個(gè)例子中,我們選擇將包含所有插槽 prop 的對(duì)象命名為 slotProps肝劲,但你也可以使用任意你喜歡的名字迁客。
- 注意 slotProps 是包含所有插槽 prop 的對(duì)象。
- 獨(dú)占默認(rèn)插槽的縮寫語法辞槐。
- 前面提到 v-slot 只能添加在 <template> 標(biāo)簽上 (只有一種例外情況)掷漱。這種情況就是:當(dāng)被提供的內(nèi)容只有默認(rèn)插槽時(shí),組件的標(biāo)簽才可以被當(dāng)作插槽的模板來使用榄檬。這樣我們就可以把 v-slot 直接用在組件上
<current-user v-slot:default="slotProps"> {{ slotProps.user.firstName }} </current-user>
- 這種寫法還可以更簡(jiǎn)單卜范。就像假定未指明的內(nèi)容對(duì)應(yīng)默認(rèn)插槽一樣,不帶參數(shù)的 v-slot 被假定對(duì)應(yīng)默認(rèn)插槽
<current-user v-slot="slotProps"> {{ slotProps.user.firstName }} </current-user>
- 只要出現(xiàn)多個(gè)插槽鹿榜,請(qǐng)始終為所有的插槽使用完整的基于 <template> 的語法海雪。
- 解構(gòu)插槽 Prop
- 只有 <current-user> 組件可以訪問到 user 而我們提供的內(nèi)容是在父級(jí)渲染的洋丐。為了讓 user 在父級(jí)的插槽內(nèi)容中可用,我們可以將 user 作為 <slot> 元素的一個(gè) attribute 綁定上去:
-
動(dòng)態(tài)組件
- 在動(dòng)態(tài)組件上使用 keep-alive
- 在前面我們見過,使用 is attribute 來切換不同組件舱殿。但是每次你切換新標(biāo)簽的時(shí)候奥裸,Vue 都會(huì)創(chuàng)建一個(gè)新的組件實(shí)例。重新創(chuàng)建動(dòng)態(tài)組件的行為有的時(shí)候是有用的沪袭,但是有的時(shí)候你會(huì)想保持這些組件的狀態(tài)(希望那些標(biāo)簽的組件實(shí)例能夠被在它們第一次被創(chuàng)建的時(shí)候緩存下來)湾宙。為了解決這個(gè)問題,我們可以用一個(gè)
<keep-alive>
元素將其動(dòng)態(tài)組件包裹起來冈绊。<!-- 失活的組件將會(huì)被緩存侠鳄!--> <keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive>
- 注意這個(gè) <keep-alive> 要求被切換到的組件都有自己的名字,不論是通過組件的 name 選項(xiàng)還是局部/全局注冊(cè)焚碌。
-
- 在大型應(yīng)用中畦攘,我們可能需要將應(yīng)用分割成小一些的代碼塊,并且只在需要的時(shí)候才從服務(wù)器加載一個(gè)模塊十电。為了簡(jiǎn)化知押,Vue 允許你以一個(gè)工廠函數(shù)的方式(該函數(shù)有兩個(gè)參數(shù)叹螟,resolve 和 reject,類似 Promise台盯,你需要把組件的配置對(duì)象作為 resolve的參數(shù))定義你的組件罢绽,這個(gè)工廠函數(shù)會(huì)異步解析你的組件定義。Vue 只有在這個(gè)組件需要被渲染的時(shí)候才會(huì)觸發(fā)該工廠函數(shù)静盅,且會(huì)把結(jié)果緩存起來供未來重渲染良价。
-
- 訪問元素或組件。最好不要觸達(dá)另一個(gè)組件實(shí)例內(nèi)部或手動(dòng)操作 DOM 元素蒿叠。不過有時(shí)只能這么做明垢。
-
訪問根實(shí)例。在每個(gè) new Vue 實(shí)例的子組件中市咽,其根實(shí)例可以通過 $root property 進(jìn)行訪問痊银。
// Vue 根實(shí)例 new Vue({ data: { foo: 1 }, computed: { bar: function () { /* ... */ } }, methods: { baz: function () { /* ... */ } } }) // 獲取根組件的數(shù)據(jù) this.$root.foo // 寫入根組件的數(shù)據(jù) this.$root.foo = 2 // 訪問根組件的計(jì)算屬性 this.$root.bar // 調(diào)用根組件的方法 this.$root.baz()
- 對(duì)于 demo 或非常小型的有少量組件的應(yīng)用來說這是很方便的。不過這個(gè)模式擴(kuò)展到中大型應(yīng)用來說就不然了施绎。因此在絕大多數(shù)情況下溯革,我們強(qiáng)烈推薦使用 Vuex 來管理應(yīng)用的狀態(tài)。
-
訪問父組件谷醉。和
parent property 可以用來從一個(gè)子組件訪問父組件的實(shí)例。它提供了一種機(jī)會(huì)俱尼,可以在后期隨時(shí)觸達(dá)父級(jí)組件抖单,以替代將數(shù)據(jù)以 prop 的方式傳入子組件的方式。
- 在絕大多數(shù)情況下号显,觸達(dá)父級(jí)組件會(huì)使得你的應(yīng)用更難調(diào)試和理解臭猜,尤其是當(dāng)你變更了父級(jí)組件的數(shù)據(jù)的時(shí)候。當(dāng)我們稍后回看那個(gè)組件的時(shí)候押蚤,很難找出那個(gè)變更是從哪里發(fā)起的蔑歌。
-
訪問子組件實(shí)例或子元素。盡管存在 prop 和事件揽碘,有的時(shí)候你仍可能需要在 JavaScript 里直接訪問一個(gè)子組件次屠。為了達(dá)到這個(gè)目的,你可以通過
ref
這個(gè) attribute 為子組件賦予一個(gè) ID 引用雳刺。
這里教程中將得奇奇怪怪的劫灶。。掖桦。舉的例子只言片語本昏,看不懂,不如給一個(gè)較完整的例子枪汪。API 中簡(jiǎn)潔的描述比較容易懂涌穆。<base-input ref="usernameInput"></base-input> this.$refs.usernameInput
- ref 被用來給元素或子組件注冊(cè)引用信息怔昨。引用信息將會(huì)注冊(cè)在父組件的 $refs 對(duì)象上。如果在普通的 DOM 元素上使用宿稀,引用指向的就是 DOM 元素趁舀;如果用在子組件上,引用就指向組件實(shí)例祝沸。
<!-- `vm.$refs.p` will be the DOM node --> <p ref="p">hello</p> <!-- `vm.$refs.child` will be the child component instance --> <child-component ref="child"></child-component>
- 關(guān)于 ref 注冊(cè)時(shí)間的重要說明:因?yàn)?ref 本身是作為渲染結(jié)果被創(chuàng)建的矮烹,在初始渲染的時(shí)候你不能訪問它們 - 它們還不存在!$refs 也不是響應(yīng)式的罩锐,因此你不應(yīng)該試圖用它在模板中做數(shù)據(jù)綁定奉狈。
- 依賴注入。在訪問父級(jí)組件實(shí)例的時(shí)唯欣,使用 $parent property 無法很好的擴(kuò)展到更深層級(jí)的嵌套組件上嘹吨。這也是依賴注入的用武之地,它用到了兩個(gè)新的實(shí)例選項(xiàng):provide 和 inject境氢。provide 選項(xiàng)允許我們指定我們想要提供給后代組件的數(shù)據(jù)/方法。然后在任何后代組件里碰纬,我們都可以使用 inject 選項(xiàng)來接收指定的我們想要添加在這個(gè)實(shí)例上的 property萍聊。
- 實(shí)際上,你可以把依賴注入看作一部分“大范圍有效的 prop”悦析,除了:祖先組件不需要知道哪些后代組件使用它提供的 property寿桨;后代組件不需要知道被注入的 property 來自哪里。
-
程序化的事件偵聽器
- 注意 Vue 的事件系統(tǒng)不同于瀏覽器的 EventTarget API强戴。盡管它們工作起來是相似的亭螟,但是
on, 和 $off 并不是 dispatchEvent骑歹、addEventListener 和 removeEventListener 的別名预烙。
- 注意 Vue 的事件系統(tǒng)不同于瀏覽器的 EventTarget API强戴。盡管它們工作起來是相似的亭螟,但是
-
循環(huán)引用
- 遞歸組件。組件是可以在它們自己的模板中調(diào)用自身的道媚。
稍有不慎扁掸,遞歸組件就可能導(dǎo)致無限循環(huán):
類似上述的組件將會(huì)導(dǎo)致“max stack size exceeded”錯(cuò)誤,所以請(qǐng)確保遞歸調(diào)用是條件性的 (例如使用一個(gè)最終會(huì)得到 false 的 v-if)最域。name: 'stack-overflow', template: '<div><stack-overflow></stack-overflow></div>'
-
組件之間的循環(huán)引用谴分。兩個(gè)組件 A 和 B。模塊系統(tǒng)發(fā)現(xiàn)它需要 A镀脂,但是首先 A 依賴 B牺蹄,但是 B 又依賴 A,但是 A 又依賴 B薄翅,如此往復(fù)沙兰。這變成了一個(gè)循環(huán)虑省,不知道如何不經(jīng)過其中一個(gè)組件而完全解析出另一個(gè)組件。為了解決這個(gè)問題僧凰,我們需要給模塊系統(tǒng)一個(gè)點(diǎn)探颈,在那里“A 反正是需要 B 的,但是我們不需要先解析 B训措∥苯冢”在我們的例子中,把 <tree-folder> 組件設(shè)為了那個(gè)點(diǎn)绩鸣。我們知道那個(gè)產(chǎn)生悖論的子組件是 <tree-folder-contents> 組件怀大,所以我們會(huì)等到生命周期鉤子 beforeCreate 時(shí)去注冊(cè)它:
或者,在本地注冊(cè)組件的時(shí)候呀闻,你可以使用 webpack 的異步 import:beforeCreate: function () { this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default }
components: { TreeFolderContents: () => import('./tree-folder-contents.vue') }
- 遞歸組件。組件是可以在它們自己的模板中調(diào)用自身的道媚。
-
模板定義的替代品
- 這個(gè)翻譯有點(diǎn)奇怪哈0.0翻譯成“定義模板的其他方式”會(huì)好點(diǎn)化借?
- 內(nèi)聯(lián)模板。當(dāng) inline-template 這個(gè)特殊的 attribute 出現(xiàn)在一個(gè)子組件上時(shí)捡多,這個(gè)組件將會(huì)使用其里面的內(nèi)容作為模板蓖康,而不是將其作為被分發(fā)的內(nèi)容。內(nèi)聯(lián)模板需要定義在 Vue 所屬的 DOM 元素內(nèi)垒手。
- 不過蒜焊,inline-template 會(huì)讓模板的作用域變得更加難以理解。所以作為最佳實(shí)踐科贬,請(qǐng)?jiān)诮M件內(nèi)優(yōu)先選擇 template 選項(xiàng)或 .vue 文件里的一個(gè) <template> 元素來定義模板泳梆。
- X-Template。在一個(gè) <script> 元素中榜掌,并為其帶上 text/x-template 的類型优妙,然后通過一個(gè) id 將模板引用過去。例如:
<script type="text/x-template" id="hello-world-template"> <p>Hello hello hello</p> </script>
x-template 需要定義在 Vue 所屬的 DOM 元素外憎账。Vue.component('hello-world', { template: '#hello-world-template' })
這些可以用于模板特別大的 demo 或極小型的應(yīng)用套硼,但是其它情況下請(qǐng)避免使用,因?yàn)檫@會(huì)將模板和該組件的其它定義分離開鼠哥。 - 這兩種方式好像比較少用熟菲?
- 接觸到了再回頭來看看。
- 補(bǔ)充朴恳。今天突然又看到了這種寫法(2021年4月29日)抄罕。創(chuàng)建一個(gè)組件,template 寫在 js 中是一件難看難寫(沒有自動(dòng)補(bǔ)全自動(dòng)對(duì)齊于颖,懂的都懂)的事呆贿,所有便有了將組件模板抽離的方式。
<script type="text/x-template" id="my-component">
和<template id="my-component">
這兩種都是將組件模板從 JS 代碼抽離到 HTML 中的寫法。在以前可謂很實(shí)用了做入?不過今天是2021年了冒晰,已經(jīng)是單文件組件(.vue )的天下了!>箍椤壶运!
-
控制更新
- 強(qiáng)制更新。關(guān)于強(qiáng)制更新浪秘,Vue 提醒你“如果你發(fā)現(xiàn)你自己需要在 Vue 中做一次強(qiáng)制更新蒋情,99.9% 的情況,是你在某個(gè)地方做錯(cuò)了事耸携】醚ⅲ”你可能還沒有留意到數(shù)組或?qū)ο蟮淖兏鼨z測(cè)注意事項(xiàng),或者你可能依賴了一個(gè)未被 Vue 的響應(yīng)式系統(tǒng)追蹤的狀態(tài)夺衍。
- 如果你已經(jīng)做到了上述的事項(xiàng)仍然發(fā)現(xiàn)在極少數(shù)的情況下需要手動(dòng)強(qiáng)制更新狈谊,那么你可以通過 $forceUpdate 來做這件事。
- 通過 v-once 創(chuàng)建低開銷的靜態(tài)組件沟沙。不要過度使用這個(gè)模式河劝。當(dāng)你需要渲染大量靜態(tài)內(nèi)容時(shí),極少數(shù)的情況下它會(huì)給你帶來便利尝胆,除非你非常留意渲染變慢了丧裁,不然它完全是沒有必要的——再加上它在后期會(huì)帶來很多困惑。例如含衔,設(shè)想另一個(gè)開發(fā)者并不熟悉 v-once 或漏看了它在模板中,他們可能會(huì)花很多個(gè)小時(shí)去找出模板為什么無法正確更新二庵。