亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

處理邊界情況

這里記錄的都是和處理邊界情況有關(guān)的功能,即一些需要對(duì) Vue 的規(guī)則做一些小調(diào)整的特殊情況。


該頁(yè)面假設(shè)你已經(jīng)閱讀過(guò)了組件基礎(chǔ)。如果你還對(duì)組件不太了解,推薦你先閱讀它。

這里記錄的都是和處理邊界情況有關(guān)的功能,即一些需要對(duì) Vue 的規(guī)則做一些小調(diào)整的特殊情況。不過(guò)注意這些功能都是有劣勢(shì)或危險(xiǎn)的場(chǎng)景的。我們會(huì)在每個(gè)案例中注明,所以當(dāng)你使用每個(gè)功能的時(shí)候請(qǐng)稍加留意。


目錄


訪(fǎng)問(wèn)元素 & 組件


在絕大多數(shù)情況下,我們最好不要觸達(dá)另一個(gè)組件實(shí)例內(nèi)部或手動(dòng)操作 DOM 元素。不過(guò)也確實(shí)在一些情況下做這些事情是合適的。


訪(fǎng)問(wèn)根實(shí)例

在每個(gè) new Vue 實(shí)例的子組件中,其根實(shí)例可以通過(guò) $root 屬性進(jìn)行訪(fǎng)問(wèn)。例如,在這個(gè)根實(shí)例中:

// Vue 根實(shí)例
new Vue({
  data: {
    foo: 1
  },
  computed: {
    bar: function () { /* ... */ }
  },
  methods: {
    baz: function () { /* ... */ }
  }
})

所有的子組件都可以將這個(gè)實(shí)例作為一個(gè)全局 store 來(lái)訪(fǎng)問(wèn)或使用。

// 獲取根組件的數(shù)據(jù)
this.$root.foo

// 寫(xiě)入根組件的數(shù)據(jù)
this.$root.foo = 2

// 訪(fǎng)問(wèn)根組件的計(jì)算屬性
this.$root.bar

// 調(diào)用根組件的方法
this.$root.baz()

對(duì)于 demo 或非常小型的有少量組件的應(yīng)用來(lái)說(shuō)這是很方便的。不過(guò)這個(gè)模式擴(kuò)展到中大型應(yīng)用來(lái)說(shuō)就不然了。因此在絕大多數(shù)情況下,我們強(qiáng)烈推薦使用 Vuex 來(lái)管理應(yīng)用的狀態(tài)。


訪(fǎng)問(wèn)父級(jí)組件實(shí)例

$root 類(lèi)似,$parent 屬性可以用來(lái)從一個(gè)子組件訪(fǎng)問(wèn)父組件的實(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ā)起的。

另外在一些可能適當(dāng)?shù)臅r(shí)候,你需要特別地共享一些組件庫(kù)。舉個(gè)例子,在和 JavaScript API 進(jìn)行交互而不渲染 HTML 的抽象組件內(nèi),諸如這些假設(shè)性的 Google 地圖組件一樣:

<google-map>
  <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
</google-map>

這個(gè) <google-map> 組件可以定義一個(gè) map 屬性,所有的子組件都需要訪(fǎng)問(wèn)它。在這種情況下 <google-map-markers> 可能想要通過(guò)類(lèi)似 this.$parent.getMap 的方式訪(fǎng)問(wèn)那個(gè)地圖,以便為其添加一組標(biāo)記。你可以在這里查閱這種模式。

請(qǐng)留意,盡管如此,通過(guò)這種模式構(gòu)建出來(lái)的那個(gè)組件的內(nèi)部仍然是容易出現(xiàn)問(wèn)題的。比如,設(shè)想一下我們添加一個(gè)新的 <google-map-region> 組件,當(dāng) <google-map-markers> 在其內(nèi)部出現(xiàn)的時(shí)候,只會(huì)渲染那個(gè)區(qū)域內(nèi)的標(biāo)記:

<google-map>
  <google-map-region v-bind:shape="cityBoundaries">
    <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
  </google-map-region>
</google-map>

那么在 <google-map-markers> 內(nèi)部你可能發(fā)現(xiàn)自己需要一些類(lèi)似這樣的 hack:

var map = this.$parent.map || this.$parent.$parent.map

很快它就會(huì)失控。這也是我們針對(duì)需要向任意更深層級(jí)的組件提供上下文信息時(shí)推薦依賴(lài)注入的原因。


訪(fǎng)問(wèn)子組件實(shí)例或子元素

盡管存在 prop 和事件,有的時(shí)候你仍可能需要在 JavaScript 里直接訪(fǎng)問(wèn)一個(gè)子組件。為了達(dá)到這個(gè)目的,你可以通過(guò) ref 特性為這個(gè)子組件賦予一個(gè) ID 引用。例如:

<base-input ref="usernameInput"></base-input>

現(xiàn)在在你已經(jīng)定義了這個(gè) ref 的組件里,你可以使用:

this.$refs.usernameInput

來(lái)訪(fǎng)問(wèn)這個(gè) <base-input> 實(shí)例,以便不時(shí)之需。比如程序化地從一個(gè)父級(jí)組件聚焦這個(gè)輸入框。在剛才那個(gè)例子中,該 <base-input> 組件也可以使用一個(gè)類(lèi)似的 ref 提供對(duì)內(nèi)部這個(gè)指定元素的訪(fǎng)問(wèn),例如:

<input ref="input">

甚至可以通過(guò)其父級(jí)組件定義方法:

methods: {
  // 用來(lái)從父級(jí)組件聚焦輸入框
  focus: function () {
    this.$refs.input.focus()
  }
}

這樣就允許父級(jí)組件通過(guò)下面的代碼聚焦 <base-input> 里的輸入框:

this.$refs.usernameInput.focus()

當(dāng) refv-for 一起使用的時(shí)候,你得到的引用將會(huì)是一個(gè)包含了對(duì)應(yīng)數(shù)據(jù)源的這些子組件的數(shù)組。

$refs 只會(huì)在組件渲染完成之后生效,并且它們不是響應(yīng)式的。這僅作為一個(gè)用于直接操作子組件的“逃生艙”——你應(yīng)該避免在模板或計(jì)算屬性中訪(fǎng)問(wèn) $refs。


依賴(lài)注入

在此之前,在我們描述訪(fǎng)問(wèn)父級(jí)組件實(shí)例的時(shí)候,展示過(guò)一個(gè)類(lèi)似這樣的例子:

<google-map>
  <google-map-region v-bind:shape="cityBoundaries">
    <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
  </google-map-region>
</google-map>

在這個(gè)組件里,所有 <google-map> 的后代都需要訪(fǎng)問(wèn)一個(gè) getMap 方法,以便知道要跟哪個(gè)地圖進(jìn)行交互。不幸的是,使用 $parent 屬性無(wú)法很好的擴(kuò)展到更深層級(jí)的嵌套組件上。這也是依賴(lài)注入的用武之地,它用到了兩個(gè)新的實(shí)例選項(xiàng):provideinject。

provide 選項(xiàng)允許我們指定我們想要提供給后代組件的數(shù)據(jù)/方法。在這個(gè)例子中,就是 <google-map> 內(nèi)部的 getMap 方法:

provide: function () {
  return {
    getMap: this.getMap
  }
}

然后在任何后代組件里,我們都可以使用 inject 選項(xiàng)來(lái)接收指定的我們想要添加在這個(gè)實(shí)例上的屬性:

inject: ['getMap']

你可以在這里看到完整的示例。相比 $parent 來(lái)說(shuō),這個(gè)用法可以讓我們?cè)谌我夂蟠M件中訪(fǎng)問(wèn) getMap,而不需要暴露整個(gè) <google-map> 實(shí)例。這允許我們更好的持續(xù)研發(fā)該組件,而不需要擔(dān)心我們可能會(huì)改變/移除一些子組件依賴(lài)的東西。同時(shí)這些組件之間的接口是始終明確定義的,就和 props 一樣。

實(shí)際上,你可以把依賴(lài)注入看作一部分“大范圍有效的 prop”,除了:

  • 祖先組件不需要知道哪些后代組件使用它提供的屬性

  • 后代組件不需要知道被注入的屬性來(lái)自哪里

然而,依賴(lài)注入還是有負(fù)面影響的。它將你應(yīng)用程序中的組件與它們當(dāng)前的組織方式耦合起來(lái),使重構(gòu)變得更加困難。同時(shí)所提供的屬性是非響應(yīng)式的。這是出于設(shè)計(jì)的考慮,因?yàn)槭褂盟鼈儊?lái)創(chuàng)建一個(gè)中心化規(guī)?;臄?shù)據(jù)跟使用 $root做這件事都是不夠好的。如果你想要共享的這個(gè)屬性是你的應(yīng)用特有的,而不是通用化的,或者如果你想在祖先組件中更新所提供的數(shù)據(jù),那么這意味著你可能需要換用一個(gè)像 Vuex 這樣真正的狀態(tài)管理方案了。

你可以在 API 參考文檔學(xué)習(xí)更多關(guān)于依賴(lài)注入的知識(shí)。


程序化的事件偵聽(tīng)器


現(xiàn)在,你已經(jīng)知道了 $emit 的用法,它可以被 v-on 偵聽(tīng),但是 Vue 實(shí)例同時(shí)在其事件接口中提供了其它的方法。我們可以:

  • 通過(guò) $on(eventName, eventHandler) 偵聽(tīng)一個(gè)事件

  • 通過(guò) $once(eventName, eventHandler) 一次性偵聽(tīng)一個(gè)事件

  • 通過(guò) $off(eventName, eventHandler) 停止偵聽(tīng)一個(gè)事件

你通常不會(huì)用到這些,但是當(dāng)你需要在一個(gè)組件實(shí)例上手動(dòng)偵聽(tīng)事件時(shí),它們是派得上用場(chǎng)的。它們也可以用于代碼組織工具。例如,你可能經(jīng)??吹竭@種集成一個(gè)第三方庫(kù)的模式:

// 一次性將這個(gè)日期選擇器附加到一個(gè)輸入框上
// 它會(huì)被掛載到 DOM 上。
mounted: function () {
  // Pikaday 是一個(gè)第三方日期選擇器的庫(kù)
  this.picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })
},
// 在組件被銷(xiāo)毀之前,
// 也銷(xiāo)毀這個(gè)日期選擇器。
beforeDestroy: function () {
  this.picker.destroy()
}

這里有兩個(gè)潛在的問(wèn)題:

  • 它需要在這個(gè)組件實(shí)例中保存這個(gè) picker,如果可以的話(huà)最好只有生命周期鉤子可以訪(fǎng)問(wèn)到它。這并不算嚴(yán)重的問(wèn)題,但是它可以被視為雜物。

  • 我們的建立代碼獨(dú)立于我們的清理代碼,這使得我們比較難于程序化地清理我們建立的所有東西。

你應(yīng)該通過(guò)一個(gè)程序化的偵聽(tīng)器解決這兩個(gè)問(wèn)題:

mounted: function () {
  var picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })
  this.$once('hook:beforeDestroy', function () {
    picker.destroy()
  })
}

使用了這個(gè)策略,我甚至可以讓多個(gè)輸入框元素同時(shí)使用不同的 Pikaday,每個(gè)新的實(shí)例都程序化地在后期清理它自己:

mounted: function () {
  this.attachDatepicker('startDateInput')
  this.attachDatepicker('endDateInput')
},
methods: {
  attachDatepicker: function (refName) {
    var picker = new Pikaday({
      field: this.$refs[refName],
      format: 'YYYY-MM-DD'
    })
    this.$once('hook:beforeDestroy', function () {
      picker.destroy()
    })
  }
}

查閱這個(gè) fiddle 可以了解到完整的代碼。注意,即便如此,如果你發(fā)現(xiàn)自己不得不在單個(gè)組件里做很多建立和清理的工作,最好的方式通常還是創(chuàng)建更多的模塊化組件。在這個(gè)例子中,我們推薦創(chuàng)建一個(gè)可復(fù)用的 <input-datepicker> 組件。

想了解更多程序化偵聽(tīng)器的內(nèi)容,請(qǐng)查閱實(shí)例方法 / 事件相關(guān)的 API。

注意 Vue 的事件系統(tǒng)不同于瀏覽器的 EventTarget API。盡管它們工作起來(lái)是相似的,但是 $emit、$on, 和 $off 并不是 dispatchEvent、addEventListener 和 removeEventListener 的別名。


循環(huán)引用


遞歸組件

組件是可以在它們自己的模板中調(diào)用自身的。不過(guò)它們只能通過(guò) name 選項(xiàng)來(lái)做這件事:

name: 'unique-name-of-my-component'

當(dāng)你使用 Vue.component 全局注冊(cè)一個(gè)組件時(shí),這個(gè)全局的 ID 會(huì)自動(dòng)設(shè)置為該組件的 name 選項(xiàng)。

Vue.component('unique-name-of-my-component', {
  // ...
})

稍有不慎,遞歸組件就可能導(dǎo)致無(wú)限循環(huán):

name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'

類(lèi)似上述的組件將會(huì)導(dǎo)致“max stack size exceeded”錯(cuò)誤,所以請(qǐng)確保遞歸調(diào)用是條件性的 (例如使用一個(gè)最終會(huì)得到 falsev-if)。


組件之間的循環(huán)引用

假設(shè)你需要構(gòu)建一個(gè)文件目錄樹(shù),像訪(fǎng)達(dá)或資源管理器那樣的。你可能有一個(gè) <tree-folder> 組件,模板是這樣的:

<p>
  <span>{{ folder.name }}</span>
  <tree-folder-contents :children="folder.children"/>
</p>

還有一個(gè) <tree-folder-contents> 組件,模板是這樣的:

<ul>
  <li v-for="child in children">
    <tree-folder v-if="child.children" :folder="child"/>
    <span v-else>{{ child.name }}</span>
  </li>
</ul>

當(dāng)你仔細(xì)觀察的時(shí)候,你會(huì)發(fā)現(xiàn)這些組件在渲染樹(shù)中互為對(duì)方的后代和祖先——一個(gè)悖論!當(dāng)通過(guò) Vue.component 全局注冊(cè)組件的時(shí)候,這個(gè)悖論會(huì)被自動(dòng)解開(kāi)。如果你是這樣做的,那么你可以跳過(guò)這里。

然而,如果你使用一個(gè)模塊系統(tǒng)依賴(lài)/導(dǎo)入組件,例如通過(guò) webpack 或 Browserify,你會(huì)遇到一個(gè)錯(cuò)誤:

Failed to mount component: template or render function not defined.

為了解釋這里發(fā)生了什么,我們先把兩個(gè)組件稱(chēng)為 A 和 B。模塊系統(tǒng)發(fā)現(xiàn)它需要 A,但是首先 A 依賴(lài) B,但是 B 又依賴(lài) A,但是 A 又依賴(lài) B,如此往復(fù)。這變成了一個(gè)循環(huán),不知道如何不經(jīng)過(guò)其中一個(gè)組件而完全解析出另一個(gè)組件。為了解決這個(gè)問(wèn)題,我們需要給模塊系統(tǒng)一個(gè)點(diǎn),在那里“A 反正是需要 B 的,但是我們不需要先解析 B?!?/p>

在我們的例子中,把 <tree-folder> 組件設(shè)為了那個(gè)點(diǎn)。我們知道那個(gè)產(chǎn)生悖論的子組件是 <tree-folder-contents> 組件,所以我們會(huì)等到生命周期鉤子 beforeCreate 時(shí)去注冊(cè)它:

beforeCreate: function () {
  this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}

或者,在本地注冊(cè)組件的時(shí)候,你可以使用 webpack 的異步 import

components: {
  TreeFolderContents: () => import('./tree-folder-contents.vue')
}

這樣問(wèn)題就解決了!


模板定義的替代品


內(nèi)聯(lián)模板

當(dāng) inline-template 這個(gè)特殊的特性出現(xiàn)在一個(gè)子組件上時(shí),這個(gè)組件將會(huì)使用其里面的內(nèi)容作為模板,而不是將其作為被分發(fā)的內(nèi)容。這使得模板的撰寫(xiě)工作更加靈活。

<my-component inline-template>
  <div>
    <p>These are compiled as the component's own template.</p>
    <p>Not parent's transclusion content.</p>
  </div>
</my-component>

內(nèi)聯(lián)模板需要定義在 Vue 所屬的 DOM 元素內(nèi)。

不過(guò),inline-template 會(huì)讓模板的作用域變得更加難以理解。所以作為最佳實(shí)踐,請(qǐng)?jiān)诮M件內(nèi)優(yōu)先選擇 template 選項(xiàng)或 .vue 文件里的一個(gè) <template> 元素來(lái)定義模板。


X-Template

另一個(gè)定義模板的方式是在一個(gè) <script> 元素中,并為其帶上 text/x-template 的類(lèi)型,然后通過(guò)一個(gè) id 將模板引用過(guò)去。例如:

<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
  template: '#hello-world-template'
})

x-template 需要定義在 Vue 所屬的 DOM 元素外。

這些可以用于模板特別大的 demo 或極小型的應(yīng)用,但是其它情況下請(qǐng)避免使用,因?yàn)檫@會(huì)將模板和該組件的其它定義分離開(kāi)。


控制更新


感謝 Vue 的響應(yīng)式系統(tǒng),它始終知道何時(shí)進(jìn)行更新 (如果你用對(duì)了的話(huà))。不過(guò)還是有一些邊界情況,你想要強(qiáng)制更新,盡管表面上看響應(yīng)式的數(shù)據(jù)沒(méi)有發(fā)生改變。也有一些情況是你想阻止不必要的更新。


強(qiáng)制更新

如果你發(fā)現(xiàn)你自己需要在 Vue 中做一次強(qiáng)制更新,99.9% 的情況,是你在某個(gè)地方做錯(cuò)了事。

你可能還沒(méi)有留意到數(shù)組對(duì)象的變更檢測(cè)注意事項(xiàng),或者你可能依賴(lài)了一個(gè)未被 Vue 的響應(yīng)式系統(tǒng)追蹤的狀態(tài)。

然而,如果你已經(jīng)做到了上述的事項(xiàng)仍然發(fā)現(xiàn)在極少數(shù)的情況下需要手動(dòng)強(qiáng)制更新,那么你可以通過(guò) $forceUpdate 來(lái)做這件事。


通過(guò) v-once 創(chuàng)建低開(kāi)銷(xiāo)的靜態(tài)組件

渲染普通的 HTML 元素在 Vue 中是非??焖俚?,但有的時(shí)候你可能有一個(gè)組件,這個(gè)組件包含了大量靜態(tài)內(nèi)容。在這種情況下,你可以在根元素上添加 v-once 特性以確保這些內(nèi)容只計(jì)算一次然后緩存起來(lái),就像這樣:

Vue.component('terms-of-service', {
  template: `
    <div v-once>
      <h1>Terms of Service</h1>
      ... a lot of static content ...
    </div>
  `
})

再說(shuō)一次,試著不要過(guò)度使用這個(gè)模式。當(dāng)你需要渲染大量靜態(tài)內(nèi)容時(shí),極少數(shù)的情況下它會(huì)給你帶來(lái)便利,除非你非常留意渲染變慢了,不然它完全是沒(méi)有必要的——再加上它在后期會(huì)帶來(lái)很多困惑。例如,設(shè)想另一個(gè)開(kāi)發(fā)者并不熟悉 v-once 或漏看了它在模板中,他們可能會(huì)花很多個(gè)小時(shí)去找出模板為什么無(wú)法正確更新。