Vue.js 2.0 組件

2022-04-01 13:41 更新

什么是組件?

組件(Component)是 Vue.js 最強(qiáng)大的功能之一。組件可以擴(kuò)展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素, Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以是原生 HTML 元素的形式,以 is 特性擴(kuò)展。

使用組件

注冊

之前說過,我們可以通過以下方式創(chuàng)建一個 Vue 實(shí)例:

new Vue({
  el: '#some-element',
  // 選項(xiàng)
})

要注冊一個全局組件,你可以使用 Vue.component(tagName, options)。 例如:

Vue.component('my-component', {
  // 選項(xiàng)
})

對于自定義標(biāo)簽名,Vue.js 不強(qiáng)制要求遵循 W3C規(guī)則 (小寫,并且包含一個短杠),盡管遵循這個規(guī)則比較好。

組件在注冊之后,便可以在父實(shí)例的模塊中以自定義元素 <my-component></my-component> 的形式使用。要確保在初始化根實(shí)例之前注冊了組件:

<div id="example">
  <my-component></my-component>
</div>
// 注冊
Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})
// 創(chuàng)建根實(shí)例
new Vue({
  el: '#example'
})

渲染為:

<div id="example">
  <div>A custom component!</div>
</div>
A custom component!

局部注冊

不必在全局注冊每個組件。通過使用組件實(shí)例選項(xiàng)注冊,可以使組件僅在另一個實(shí)例/組件的作用域中可用:

var Child = {
  template: '<div>A custom component!</div>'
}
new Vue({
  // ...
  components: {
    // <my-component> 將只在父模板可用
    'my-component': Child
  }
})

這種封裝也適用于其它可注冊的 Vue 功能,如指令。

DOM 模版解析說明

當(dāng)使用 DOM 作為模版時(例如,將 el 選項(xiàng)掛載到一個已存在的元素上), 你會受到 HTML 的一些限制,因?yàn)?Vue 只有在瀏覽器解析和標(biāo)準(zhǔn)化 HTML 后才能獲取模版內(nèi)容。尤其像這些元素 <ul> , <ol>, <table> , <select> 限制了能被它包裹的元素, <option> 只能出現(xiàn)在其它元素內(nèi)部。

在自定義組件中使用這些受限制的元素時會導(dǎo)致一些問題,例如:

<table>
  <my-row>...</my-row>
</table>

自定義組件 <my-row> 被認(rèn)為是無效的內(nèi)容,因此在渲染的時候會導(dǎo)致錯誤。變通的方案是使用特殊的 is 屬性:

<table>
  <tr is="my-row"></tr>
</table>

應(yīng)當(dāng)注意,如果您使用來自以下來源之一的字符串模板,這些限制將不適用:

  • <script type="text/x-template">
  • JavaScript內(nèi)聯(lián)模版字符串
  • .vue 組件

因此,有必要的話請使用字符串模版。

data 必須是函數(shù)

使用組件時,大多數(shù)選項(xiàng)可以被傳入到 Vue 構(gòu)造器中,有一個例外: data 必須是函數(shù)。 實(shí)際上,如果你這么做:

Vue.component('my-component', {
  template: '<span>{{ message }}</span>',
  data: {
    message: 'hello'
  }
})

那么 Vue 會在控制臺發(fā)出警告,告訴你在組件中 data 必須是一個函數(shù)。最好理解這種規(guī)則的存在意義。

<div id="example-2">
  <simple-counter></simple-counter>
  <simple-counter></simple-counter>
  <simple-counter></simple-counter>
</div>
var data = { counter: 0 }
Vue.component('simple-counter', {
  template: '<button v-on:click="counter += 1">{{ counter }}</button>',
  // 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)了

構(gòu)成組件

組件意味著協(xié)同工作,通常父子組件會是這樣的關(guān)系:組件 A 在它的模版中使用了組件 B 。它們之間必然需要相互通信:父組件要給子組件傳遞數(shù)據(jù),子組件需要將它內(nèi)部發(fā)生的事情告知給父組件。然而,在一個良好定義的接口中盡可能將父子組件解耦是很重要的。這保證了每個組件可以在相對隔離的環(huán)境中書寫和理解,也大幅提高了組件的可維護(hù)性和可重用性。

在 Vue.js 中,父子組件的關(guān)系可以總結(jié)為 props down, events up 。父組件通過 props 向下傳遞數(shù)據(jù)給子組件,子組件通過 events 給父組件發(fā)送消息??纯此鼈兪窃趺垂ぷ鞯?。

props down, events up

Props

使用Props傳遞數(shù)據(jù)

組件實(shí)例的作用域是孤立的。這意味著不能并且不應(yīng)該在子組件的模板內(nèi)直接引用父組件的數(shù)據(jù)??梢允褂?props 把數(shù)據(jù)傳給子組件。

prop 是父組件用來傳遞數(shù)據(jù)的一個自定義屬性。子組件需要顯式地用 props 選項(xiàng) 聲明 “prop”:

Vue.component('child', {
  // 聲明 props
  props: ['message'],
  // 就像 data 一樣,prop 可以用在模板內(nèi)
  // 同樣也可以在 vm 實(shí)例中像 “this.message” 這樣使用
  template: '<span>{{ message }}</span>'
})

然后向它傳入一個普通字符串:

<child message="hello!"></child>

結(jié)果:

hello!

camelCase vs. kebab-case

HTML 特性不區(qū)分大小寫。當(dāng)使用非字符串模版時,prop的名字形式會從 camelCase 轉(zhuǎn)為 kebab-case(短橫線隔開):

Vue.component('child', {
  // camelCase in JavaScript
  props: ['myMessage'],
  template: '<span>{{ myMessage }}</span>'
})
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>

再次說明,如果你使用字符串模版,不用在意這些限制。

動態(tài) Props

類似于用 v-bind 綁定 HTML 特性到一個表達(dá)式,也可以用 v-bind 動態(tài)綁定 props 的值到父組件的數(shù)據(jù)中。每當(dāng)父組件的數(shù)據(jù)變化時,該變化也會傳導(dǎo)給子組件:

<div>
  <input v-model="parentMsg">
  <br>
  <child v-bind:my-message="parentMsg"></child>
</div>

使用 v-bind 的縮寫語法通常更簡單:

<child :my-message="parentMsg"></child>

字面量語法 vs 動態(tài)語法

初學(xué)者常犯的一個錯誤是使用字面量語法傳遞數(shù)值:

<!-- 傳遞了一個字符串"1" -->
<comp some-prop="1"></comp>

因?yàn)樗且粋€字面 prop ,它的值以字符串 "1" 而不是以實(shí)際的數(shù)字傳下去。如果想傳遞一個實(shí)際的 JavaScript 數(shù)字,需要使用 v-bind ,從而讓它的值被當(dāng)作 JavaScript 表達(dá)式計算:

<!-- 傳遞實(shí)際的數(shù)字 -->
<comp v-bind:some-prop="1"></comp>

單向數(shù)據(jù)流

prop 是單向綁定的:當(dāng)父組件的屬性變化時,將傳導(dǎo)給子組件,但是不會反過來。這是為了防止子組件無意修改了父組件的狀態(tài)——這會讓應(yīng)用的數(shù)據(jù)流難以理解。

另外,每次父組件更新時,子組件的所有 prop 都會更新為最新值。這意味著你不應(yīng)該在子組件內(nèi)部改變 prop 。如果你這么做了,Vue 會在控制臺給出警告。

通常有兩種改變 prop 的情況:

  1. prop 作為初始值傳入,子組件之后只是將它的初始值作為本地數(shù)據(jù)的初始值使用;
  2. prop 作為需要被轉(zhuǎn)變的原始值傳入。

更確切的說這兩種情況是:

  1. 定義一個局部 data 屬性,并將 prop 的初始值作為局部數(shù)據(jù)的初始值。
    props: ['initialCounter'],
    data: function () {
      return { counter: this.initialCounter }
    }
  2. 定義一個 computed 屬性,此屬性從 prop 的值計算得出。
    props: ['size'],
    computed: {
      normalizedSize: function () {
        return this.size.trim().toLowerCase()
      }
    }

注意:在 JavaScript 中對象和數(shù)組是引用類型,指向同一個內(nèi)存空間,如果 prop 是一個對象或數(shù)組,在子組件內(nèi)部改變它會影響父組件的狀態(tài)。

Prop 驗(yàn)證

組件可以為 props 指定驗(yàn)證要求。如果未指定驗(yàn)證要求,Vue 會發(fā)出警告。當(dāng)組件給其他人使用時這很有用。

prop 是一個對象而不是字符串?dāng)?shù)組時,它包含驗(yàn)證要求:

Vue.component('example', {
  props: {
    // 基礎(chǔ)類型檢測 (`null` 意思是任何類型都可以)
    propA: Number,
    // 多種類型
    propB: [String, Number],
    // 必傳且是字符串
    propC: {
      type: String,
      required: true
    },
    // 數(shù)字,有默認(rèn)值
    propD: {
      type: Number,
      default: 100
    },
    // 數(shù)組/對象的默認(rèn)值應(yīng)當(dāng)由一個工廠函數(shù)返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定義驗(yàn)證函數(shù)
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
})

type 可以是下面原生構(gòu)造器:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array

type 也可以是一個自定義構(gòu)造器,使用 instanceof 檢測。

當(dāng) prop 驗(yàn)證失敗了, Vue 將拒絕在子組件上設(shè)置此值,如果使用的是開發(fā)版本會拋出一條警告。

自定義事件

我們知道,父組件是使用 props 傳遞數(shù)據(jù)給子組件,但如果子組件要把數(shù)據(jù)傳遞回去,應(yīng)該怎樣做?那就是自定義事件!

使用 v-on 綁定自定義事件

每個 Vue 實(shí)例都實(shí)現(xiàn)了事件接口(Events interface),即:

  • 使用 $on(eventName) 監(jiān)聽事件
  • 使用 $emit(eventName) 觸發(fā)事件

Vue的事件系統(tǒng)分離自瀏覽器的EventTarget API。盡管它們的運(yùn)行類似,但是$on和 $emit 不是 addEventListener 和 dispatchEvent 的別名。

另外,父組件可以在使用子組件的地方直接用 v-on 來監(jiān)聽子組件觸發(fā)的事件。

下面是一個例子:

<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
  template: '<button v-on:click="increment">{{ counter }}</button>',
  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 。例如:

<my-component v-on:click.native="doTheThing"></my-component>

使用自定義事件的表單輸入組件

自定義事件也可以用來創(chuàng)建自定義的表單輸入組件,使用 v-model 來進(jìn)行數(shù)據(jù)雙向綁定。牢記:

<input v-model="something">

僅僅是一個語法:

<input v-bind:value="something" v-on:input="something = $event.target.value">

所以在組件中使用時,它相當(dāng)于下面的簡寫:

<input v-bind:value="something" v-on:input="something = arguments[0]">

所以要讓組件的 v-model 生效,它必須:

  • 接受一個 value 屬性
  • 在有新的 value 時觸發(fā) input 事件

一個非常簡單的貨幣輸入:

<currency-input v-model="price"></currency-input>
Vue.component('currency-input', {
  template: '\
    <span>\
      $\
      <input\
        ref="input"\
        v-bind:value="value"\
        v-on:input="updateValue($event.target.value)"\
      >\
    </span>\
  ',
  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))
    }
  }
})

上面的實(shí)現(xiàn)方式太過理想化了。 比如,用戶甚至可以輸入多個小數(shù)點(diǎn)或句號 - 呸!因此我們需要一個更有意義的例子,下面是一個更加完善的貨幣過濾器:

這個接口不僅僅可以用來連接組件內(nèi)部的表單輸入,也很容易集成你自己創(chuàng)造的輸入類型。想象一下:

<voice-recognizer v-model="question"></voice-recognizer>
<webcam-gesture-reader v-model="gesture"></webcam-gesture-reader>
<webcam-retinal-scanner v-model="retinalImage"></webcam-retinal-scanner>

非父子組件通信

有時候非父子關(guān)系的組件也需要通信。在簡單的場景下,使用一個空的 Vue 實(shí)例作為中央事件總線:

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)管理模式.

使用 Slots 分發(fā)內(nèi)容

在使用組件時,常常要像這樣組合它們:

<app>
  <app-header></app-header>
  <app-footer></app-footer>
</app>

注意兩點(diǎn):

  1. <app> 組件不知道它的掛載點(diǎn)會有什么內(nèi)容。掛載點(diǎn)的內(nèi)容是由<app>的父組件決定的。
  2. <app> 組件很可能有它自己的模版。

為了讓組件可以組合,我們需要一種方式來混合父組件的內(nèi)容與子組件自己的模板。這個過程被稱為內(nèi)容分發(fā) (或 “transclusion” 如果你熟悉 Angular)。Vue.js 實(shí)現(xiàn)了一個內(nèi)容分發(fā) API ,參照了當(dāng)前 Web組件規(guī)范草案,使用特殊的 <slot> 元素作為原始內(nèi)容的插槽。

編譯作用域

在深入內(nèi)容分發(fā) API 之前,我們先明確內(nèi)容的編譯作用域。假定模板為:

<child-component>
  {{ message }}
</child-component>

message 應(yīng)該綁定到父組件的數(shù)據(jù),還是綁定到子組件的數(shù)據(jù)?答案是父組件。組件作用域簡單地說是:

父組件模板的內(nèi)容在父組件作用域內(nèi)編譯;子組件模板的內(nèi)容在子組件作用域內(nèi)編譯。

一個常見錯誤是試圖在父組件模板內(nèi)將一個指令綁定到子組件的屬性/方法:

<!-- 無效 -->
<child-component v-show="someChildProperty"></child-component>

假定 someChildProperty 是子組件的屬性,上例不會如預(yù)期那樣工作。父組件模板不應(yīng)該知道子組件的狀態(tài)。

如果要綁定子組件內(nèi)的指令到一個組件的根節(jié)點(diǎn),應(yīng)當(dāng)在它的模板內(nèi)這么做:

Vue.component('child-component', {
  // 有效,因?yàn)槭窃谡_的作用域內(nèi)
  template: '<div v-show="someChildProperty">Child</div>',
  data: function () {
    return {
      someChildProperty: true
    }
  }
})

類似地,分發(fā)內(nèi)容是在父組件作用域內(nèi)編譯。

單個 Slot

除非子組件模板包含至少一個 <slot> 插口,否則父組件的內(nèi)容將會被丟棄。當(dāng)子組件模板只有一個沒有屬性的 slot 時,父組件整個內(nèi)容片段將插入到 slot 所在的 DOM 位置,并替換掉 slot 標(biāo)簽本身。

最初在 <slot> 標(biāo)簽中的任何內(nèi)容都被視為備用內(nèi)容。備用內(nèi)容在子組件的作用域內(nèi)編譯,并且只有在宿主元素為空,且沒有要插入的內(nèi)容時才顯示備用內(nèi)容。

假定 my-component 組件有下面模板:

<div>
  <h2>I'm the child title</h2>
  <slot>
    如果沒有分發(fā)內(nèi)容則顯示我。
  </slot>
</div>

父組件模版:

<div>
  <h1>I'm the parent title</h1>
  <my-component>
    <p>This is some original content</p>
    <p>This is some more original content</p>
  </my-component>
</div>

渲染結(jié)果:

<div>
  <h1>I'm the parent title</h1>
  <div>
    <h2>I'm the child title</h2>
    <p>This is some original content</p>
    <p>This is some more original content</p>
  </div>
</div>

具名Slots

<slot> 元素可以用一個特殊的屬性 name 來配置如何分發(fā)內(nèi)容。多個 slot 可以有不同的名字。具名 slot 將匹配內(nèi)容片段中有對應(yīng) slot 特性的元素。

仍然可以有一個匿名 slot ,它是默認(rèn) slot ,作為找不到匹配的內(nèi)容片段的備用插槽。如果沒有默認(rèn)的 slot ,這些找不到匹配的內(nèi)容片段將被拋棄。

例如,假定我們有一個 app-layout 組件,它的模板為:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

父組件模版:

<app-layout>
  <h1 slot="header">Here might be a page title</h1>
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>
  <p slot="footer">Here's some contact info</p>
</app-layout>

渲染結(jié)果為:

<div class="container">
  <header>
    <h1>Here might be a page title</h1>
  </header>
  <main>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </main>
  <footer>
    <p>Here's some contact info</p>
  </footer>
</div>

在組合組件時,內(nèi)容分發(fā) API 是非常有用的機(jī)制。

動態(tài)組件

多個組件可以使用同一個掛載點(diǎn),然后動態(tài)地在它們之間切換。使用保留的 <component>元素,動態(tài)地綁定到它的 is 特性:

var vm = new Vue({
  el: '#example',
  data: {
    currentView: 'home'
  },
  components: {
    home: { /* ... */ },
    posts: { /* ... */ },
    archive: { /* ... */ }
  }
})
<component v-bind:is="currentView">
  <!-- 組件在 vm.currentview 變化時改變! -->
</component>

也可以直接綁定到組件對象上:

var Home = {
  template: '<p>Welcome home!</p>'
}
var vm = new Vue({
  el: '#example',
  data: {
    currentView: Home
  }
})

keep-alive

如果把切換出去的組件保留在內(nèi)存中,可以保留它的狀態(tài)或避免重新渲染。為此可以添加一個 keep-alive 指令參數(shù):

<keep-alive>
  <component :is="currentView">
    <!-- 非活動組件將被緩存! -->
  </component>
</keep-alive>

在 API 參考查看更多 <keep-alive> 的細(xì)節(jié)。

雜項(xiàng)

編寫可復(fù)用組件

在編寫組件時,記住是否要復(fù)用組件有好處。一次性組件跟其它組件緊密耦合沒關(guān)系,但是可復(fù)用組件應(yīng)當(dāng)定義一個清晰的公開接口。

Vue 組件的 API 來自三部分 - props, events 和 slots :

  • Props 允許外部環(huán)境傳遞數(shù)據(jù)給組件
  • Events 允許組件觸發(fā)外部環(huán)境的副作用
  • Slots 允許外部環(huán)境將額外的內(nèi)容組合在組件中。

使用 v-bind 和 v-on 的簡寫語法,模板的縮進(jìn)清楚且簡潔:

<my-component
  :foo="baz"
  :bar="qux"
  @event-a="doThis"
  @event-b="doThat"
>
  <img slot="icon" src="...">
  <p slot="main-text">Hello!</p>
</my-component>

子組件索引

盡管有 props 和 events ,但是有時仍然需要在 JavaScript 中直接訪問子組件。為此可以使用 ref 為子組件指定一個索引 ID 。例如:

<div id="parent">
  <user-profile ref="profile"></user-profile>
</div>
var parent = new Vue({ el: '#parent' })
// 訪問子組件
var child = parent.$refs.profile

當(dāng) ref 和 v-for 一起使用時, ref 是一個數(shù)組或?qū)ο螅鄳?yīng)的子組件。

$refs 只在組件渲染完成后才填充,并且它是非響應(yīng)式的。它僅僅作為一個直接訪問子組件的應(yīng)急方案——應(yīng)當(dā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: '<div>I am async!</div>'
    })
  }, 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。

組件命名約定

當(dāng)注冊組件(或者 props)時,可以使用 kebab-case ,camelCase ,或 TitleCase 。Vue 不關(guān)心這個。

// 在組件定義中
components: {
  // 使用 camelCase 形式注冊
  'kebab-cased-component': { /* ... */ },
  'camelCasedComponent': { /* ... */ },
  'TitleCasedComponent': { /* ... */ }
}

在 HTML 模版中,請使用 kebab-case 形式:

<!-- 在HTML模版中始終使用 kebab-case -->
<kebab-cased-component></kebab-cased-component>
<camel-cased-component></camel-cased-component>
<title-cased-component></title-cased-component>

當(dāng)使用字符串模式時,可以不受 HTML 的 case-insensitive 限制。這意味實(shí)際上在模版中,你可以使用 camelCase 、 PascalCase 或者 kebab-case 來引用你的組件和 prop:

<!-- 在字符串模版中可以用任何你喜歡的方式! -->
<my-component></my-component>
<myComponent></myComponent>
<MyComponent></MyComponent>

如果組件未經(jīng) slot 元素傳遞內(nèi)容,你甚至可以在組件名后使用 / 使其自閉合:

<my-component/>

當(dāng)然,這只在字符串模版中有效。因?yàn)樽蚤]的自定義元素是無效的 HTML ,瀏覽器原生的解析器也無法識別它。

遞歸組件

組件在它的模板內(nèi)可以遞歸地調(diào)用自己,不過,只有當(dāng)它有 name 選項(xiàng)時才可以:

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

當(dāng)你利用Vue.component全局注冊了一個組件, 全局的ID作為組件的 name 選項(xiàng),被自動設(shè)置。

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

如果你不謹(jǐn)慎, 遞歸組件可能導(dǎo)致死循環(huán):

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

上面組件會導(dǎo)致一個錯誤 “max stack size exceeded” ,所以要確保遞歸調(diào)用有終止條件 (比如遞歸調(diào)用時使用 v-if 并讓他最終返回 false )。

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

如果子組件有 inline-template 特性,組件將把它的內(nèi)容當(dāng)作它的模板,而不是把它當(dāng)作分發(fā)內(nèi)容。這讓模板更靈活。

<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>

但是 inline-template 讓模板的作用域難以理解。最佳實(shí)踐是使用 template 選項(xiàng)在組件內(nèi)定義模板或者在 .vue 文件中使用 template 元素。

X-Templates

另一種定義模版的方式是在 JavaScript 標(biāo)簽里使用 text/x-template 類型,并且指定一個id。例如:

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

這在有很多模版或者小的應(yīng)用中有用,否則應(yīng)該避免使用,因?yàn)樗鼘⒛0婧徒M件的其他定義隔離了。

使用 v-once 的低級靜態(tài)組件(Cheap Static Component)

盡管在 Vue 中渲染 HTML 很快,不過當(dāng)組件中包含大量靜態(tài)內(nèi)容時,可以考慮使用 v-once將渲染結(jié)果緩存起來,就像這樣:

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


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號