Vue 組件

此文基于官方文檔掖鱼,里面部分例子有改動亮靴,加上了一些自己的理解

什么是組件掀泳?

組件(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ī)則比較好。

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

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

渲染為:

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

#局部注冊

不必在全局注冊每個組件。通過使用組件實例選項注冊缎脾,可以使組件僅在另一個實例/組件的作用域中可用:

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

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

#DOM 模版解析說明

當使用 DOM 作為模版時(例如遗菠,將 el 選項掛載到一個已存在的元素上), 你會受到 HTML 的一些限制联喘,因為 Vue 只有在瀏覽器解析和標準化 HTML 后才能獲取模版內容。尤其像這些元素 <ul> 辙纬, <ol>豁遭, <table><select> 限制了能被它包裹的元素贺拣, <option> 只能出現在其它元素內部蓖谢。

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

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

自定義組件 <my-row> 被認為是無效的內容譬涡,因此在渲染的時候會導致錯誤闪幽。變通的方案是使用特殊的is 屬性:

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

應當注意,如果您使用來自以下來源之一的字符串模板涡匀,這些限制將不適用:

  • <script type="text/x-template">
  • JavaScript內聯模版字符串
  • .vue 組件

因此盯腌,有必要的話請使用字符串模版單文件組件

#單文件組件

在很多Vue項目中渊跋,我們使用 Vue.component 來定義全局組件腊嗡,緊接著用 new Vue({ el: '#container '}) 在每個頁面內指定一個容器元素着倾。

  • 全局定義(Global definitions) 強制要求每個 component 中的命名不得重復
  • 字符串模板(String templates) 缺乏語法高亮,在 HTML 有多行的時候燕少,需要用到丑陋的 \
  • 不支持CSS(No CSS support) 意味著當 HTML 和 JavaScript 組件化時卡者,CSS 明顯被遺漏
  • 沒有構建步驟(No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用預處理器,如 Pug (formerly Jade) 和 Babel

文件擴展名為 .vue 的 single-file components(單文件組件) 為以上所有問題提供了解決方法客们,并且還可以使用 Webpack 或 Browserify 等構建工具崇决。

這是一個文件名為 Hello.vue 的簡單實例:

<template>
    <div class="message">{{ message }}</div>
</template>

<script type="text/javascript">
export default {
  data () {
    return {
        message: 'Hello World!'
    }
  }
}
</script>

<style lang="less" scoped>
    .message{
        text-align: center;
        color: red;
    }   
</style>

現在我們獲得:

  • 完整語法高亮
  • CommonJS 模塊
  • 組件化的 CSS

我們可以使用預處理器來構建簡潔和功能更豐富的組件,比如 Jade底挫,Babel (with ES2015 modules)恒傻,和 Stylus。

這些特定的語言只是例子建邓,你可以只是簡單地使用 Buble盈厘,TypeScript,SCSS官边,PostCSS - 或者其他任何能夠幫助你提高生產力的預處理器沸手。

注意:

  • <template>里只允許一個根元素,如:
<template >
    <div>content</div>
</template>

不能寫成

<template >
    <div>head</div>
    <div>content</div>
    <div>foot</div>
</template>
  • 你可以在每一個語言塊中使用任何你想用的預處理器注簿,如 lang="less"契吉。
  • Vue 文件格式可以支持局部 CSS,只要在 <style> 標簽上加上一個 scoped 屬性诡渴。并且一個組件的局部 CSS 不會像單純的選擇器嵌套那樣『滲透』到它包含的子組件當中去捐晶。

#data 必須是函數

使用組件時,大多數可以傳入到 Vue 構造器中的選項可以在注冊組件時使用妄辩,有一個例外: data 必須是函數惑灵。 實際上,如果你這么做:

//以下是錯誤案例
export default {
  data: {
        message: 'Hello World!'
    }
  }
}

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

<template>
    <div>
      <simple-counter></simple-counter>
      <simple-counter></simple-counter>
      <simple-counter></simple-counter>
    </div>
</template>
<script type="text/javascript">
var data = { counter: 0 }

export default {
  components: {
    simpleCounter: {
      template: '<button @click="counter += 1">{{ counter }}</button>',
      // data 是一個函數畔塔,因此 Vue 不會警告潭辈,
      // 但是我們?yōu)槊恳粋€組件返回了同一個對象引用
      data() {
        return data;
      }
    }
  }
}
</script>

由于這三個組件共享了同一個 data , 因此增加一個 counter 會影響所有組件澈吨!我們可以通過為每個組件 返回新的 data 對象 來解決這個問題:

data() {
  return {
    counter: 0
  }
}

現在每個 counter 都有它自己內部的狀態(tài)了:

#構成組件

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

在 Vue.js 中,父子組件的關系可以總結為 props down, events up 割择。父組件通過 props 向下傳遞數據給子組件眷篇,子組件通過 events 給父組件發(fā)送消息±笥荆看看它們是怎么工作的蕉饼。

props-events.png

Prop

使用 Prop 傳遞數據

組件實例的作用域是孤立的。這意味著不能并且不應該在子組件的模板內直接引用父組件的數據玛歌∶粮郏可以使用 props 把數據傳給子組件。

prop 是父組件用來傳遞數據的一個自定義屬性支子。子組件需要顯式地用 props 選項聲明 “prop”:

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

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

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

#camelCase vs. kebab-case

HTML 特性不區(qū)分大小寫。當使用非字符串模版時译荞,prop的名字形式會從 camelCase 轉為 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) Prop

類似于用 v-bind 綁定 HTML 特性到一個表達式,也可以用 v-bind 動態(tài)綁定 props 的值到父組件的數據中塔猾。每當父組件的數據變化時篙骡,該變化也會傳導給子組件:

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

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

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

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

初學者常犯的一個錯誤是使用字面量語法傳遞數值:

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

因為它是一個字面 prop ,它的值以字符串 "1" 而不是以實際的數字傳下去丈甸。如果想傳遞一個實際的 JavaScript 數字糯俗,需要使用 v-bind ,從而讓它的值被當作 JavaScript 表達式計算:

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

#單向數據流

prop 是單向綁定的:當父組件的屬性變化時睦擂,將傳導給子組件得湘,但是不會反過來。這是為了防止子組件無意修改了父組件的狀態(tài)——這會讓應用的數據流難以理解顿仇。

另外淘正,每次父組件更新時,子組件的所有 prop 都會更新為最新值臼闻。這意味著你不應該在子組件內部改變 prop 鸿吆。如果你這么做了,Vue 會在控制臺給出警告述呐。

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

  1. prop 作為初始值傳入惩淳,子組件之后只是將它的初始值作為本地數據的初始值使用;

  2. prop 作為需要被轉變的原始值傳入乓搬。

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

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

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

#Prop 驗證

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

prop 是一個對象而不是字符串數組時着降,它包含驗證要求:

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

type 可以是下面原生構造器:

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

type 也可以是一個自定義構造器,使用 instanceof 檢測任洞。

當 prop 驗證失敗了蓄喇,如果使用的是開發(fā)版本會拋出一條警告。

自定義事件

我們知道交掏,父組件是使用 props 傳遞數據給子組件妆偏,但如果子組件要把數據傳遞回去,應該怎樣做盅弛?那就是自定義事件钱骂!

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

每個 Vue 實例都實現了事件接口(Events interface),即:

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

Vue的事件系統分離自瀏覽器的EventTarget API挪鹏。盡管它們的運行類似见秽,但是$on$emit 不是addEventListenerdispatchEvent 的別名。

另外讨盒,父組件可以在使用子組件的地方直接用 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>

父組件監(jiān)聽子組件的increment事件,執(zhí)行incrementTotal方法

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

子組件用$emit向父組件觸發(fā)increment事件

在本例中返顺,子組件已經和它外部完全解耦了禀苦。它所做的只是觸發(fā)一個父組件關心的內部事件。

給組件綁定原生事件

有時候创南,你可能想在某個組件的根元素上監(jiān)聽一個原生事件伦忠,如clickkeyup等稿辙±ヂ耄可以使用 .native 修飾 v-on 。例如:

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

如果不加.native修飾,父組件則會把click當作子組件傳來的自定義事件監(jiān)聽

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

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

<input v-model="something">

僅僅是一個語法糖:

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

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

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

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

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

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

父組件:

<template>
  <div>
    <currency-input v-model="price"></currency-input>
  </div>
</template>

<script type="text/javascript">
import currencyInput from './currencyInput.vue';

export default {
  data () {
    return {
      price:0
    }
  },
  components: {
    currencyInput: currencyInput
    
  }
}
</script>

子組件 currencyInput.vue

<template>
  <span>
    <input ref="input" v-bind:value="value" v-on:input="updateValue($event.target.value)"></span>
</template>

<script type="text/javascript">
export default {

  props: ['value'],

  methods: {

    // 不是直接更新值淘钟,而是使用此方法來對輸入值進行格式化和位數限制
    updateValue: value => {
      var formattedValue = value
        // 刪除兩側的空格符
        .trim()
        // 保留 2 小數位
        .slice(0, value.indexOf('.') + 3)

      // 如果值不統一,手動覆蓋以保持一致
      if (formattedValue !== value) {
        this.$refs.input.value = formattedValue
      }

      // 通過 input 事件發(fā)出數值
      this.$emit('input', Number(formattedValue))
    }
  }

}
</script>

上面的實現方式太過理想化了陪毡。 比如米母,用戶甚至可以輸入多個小數點或非法字符!我們甚至可以做個更加完善的貨幣過濾器毡琉。

這個接口不僅僅可以用來連接組件內部的表單輸入铁瞒,也很容易集成你自己創(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>

#非父子組件通信

有時候非父子關系的組件也需要通信桅滋。在簡單的場景下慧耍,使用一個空的 Vue 實例作為中央事件總線:

var bus = new Vue()
// 觸發(fā)組件 A 中的事件
bus.$emit('id-selected', 1)
// 在組件 B 創(chuàng)建的鉤子中監(jiān)聽事件
bus.$on('id-selected', function (id) {
  // ...
})

在更多復雜的情況下,你應該考慮使用專門的 狀態(tài)管理模式.

使用 Slot 分發(fā)內容

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

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

注意兩點:

  1. <app> 組件不知道它的掛載點會有什么內容芍碧。掛載點的內容是由<app>的父組件決定的。
  2. <app> 組件很可能有它自己的模版号俐。

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

#編譯作用域

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

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

message 應該綁定到父組件的數據许布,還是綁定到子組件的數據?答案是父組件绎晃。組件作用域簡單地說是:

父組件模板的內容在父組件作用域內編譯蜜唾;子組件模板的內容在子組件作用域內編譯。

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

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

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

如果要綁定子組件內的指令到一個組件的根節(jié)點咱揍,應當在它的模板內這么做:

Vue.component('child-component', {
  // 有效颖榜,因為是在正確的作用域內
  template: '<div v-show="someChildProperty">Child</div>',
  data: function () {
    return {
      someChildProperty: true
    }
  }
})

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

#單個 Slot

除非子組件模板包含至少一個 <slot> 插口掩完,否則父組件的內容將會被丟棄噪漾。當子組件模板只有一個沒有屬性的 slot 時,父組件整個內容片段將插入到 slot 所在的 DOM 位置且蓬,并替換掉 slot 標簽本身欣硼。

最初在 <slot> 標簽中的任何內容都被視為備用內容。備用內容在子組件的作用域內編譯恶阴,并且只有在宿主元素為空诈胜,且沒有要插入的內容時才顯示備用內容。

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

<div>
  <h2>我是子組件的標題</h2>
  <slot>
    只有在沒有要分發(fā)的內容時才會顯示冯事。
  </slot>
</div>

父組件模版:

<div>
  <h1>我是父組件的標題</h1>
  <my-component>
    <p>這是一些初始內容</p>
    <p>這是更多的初始內容</p>
  </my-component>
</div>

渲染結果:

<div>
  <h1>我是父組件的標題</h1>
  <div>
    <h2>我是子組件的標題</h2>
    <p>這是一些初始內容</p>
    <p>這是更多的初始內容</p>
  </div>
</div>

#具名 Slot

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

仍然可以有一個匿名 slot 括授,它是默認 slot ,作為找不到匹配的內容片段的備用插槽岩饼。如果沒有默認的 slot 荚虚,這些找不到匹配的內容片段將被拋棄。

例如籍茧,假定我們有一個 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">這里可能是一個頁面標題</h1>
  <p>主要內容的一個段落。</p>
  <p>另一個主要段落寞冯。</p>
  <p slot="footer">這里有一些聯系信息</p>
</app-layout>
渲染結果為:
<div class="container">
  <header>
    <h1>這里可能是一個頁面標題</h1>
  </header>
  <main>
    <p>主要內容的一個段落渴析。</p>
    <p>另一個主要段落。</p>
  </main>
  <footer>
    <p>這里有一些聯系信息</p>
  </footer>
</div>

在組合組件時吮龄,內容分發(fā) API 是非常有用的機制俭茧。

#作用域插槽

作用域插槽是一種特殊類型的插槽,用作使用一個(能夠傳遞數據到)可重用模板替換已渲染元素漓帚。

在子組件中母债,只需將數據傳遞到插槽,就像你將 prop 傳遞給組件一樣:

<div class="child">
  <slot text="hello from child"></slot>
</div>

在父級中尝抖,具有特殊屬性 scope 的 <template> 元素毡们,表示它是作用域插槽的模板。scope 的值對應一個臨時變量名昧辽,此變量接收從子組件中傳遞的 prop 對象:

<div class="parent">
  <child>
    <template scope="props">
      <span>hello from parent</span>
      <span>{{ props.text }}</span>
    </template>
  </child>
</div>

如果我們渲染以上結果衙熔,得到的輸出會是:

<div class="parent">
  <div class="child">
    <span>hello from parent</span>
    <span>hello from child</span>
  </div>
</div>

作用域插槽更具代表性的用例是列表組件,允許組件自定義應該如何渲染列表每一項:

<my-awesome-list :items="items">
  <!-- 作用域插槽也可以在這里命名 -->
  <template slot="item" scope="props">
    <li class="my-fancy-item">{{ props.text }}</li>
  </template>
</my-awesome-list>

列表組件的模板:

<ul>
  <slot name="item"
    v-for="item in items"
    :text="item.text">
    <!-- fallback content here -->
  </slot>
</ul>

動態(tài)組件

多個組件可以使用同一個掛載點搅荞,然后動態(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

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

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

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

雜項

#編寫可復用組件

在編寫組件時婉称,記住是否要復用組件有好處。一次性組件跟其它組件緊密耦合沒關系构蹬,但是可復用組件應當定義一個清晰的公開接口王暗。

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

  • Props 允許外部環(huán)境傳遞數據給組件
  • Events 允許組件觸發(fā)外部環(huán)境的副作用
  • Slots 允許外部環(huán)境將額外的內容組合在組件中。
  • 使用 v-bind 和 v-on 的簡寫語法庄敛,模板的縮進清楚且簡潔:
<my-component
  :foo="baz"
  :bar="qux"
  @event-a="doThis"
  @event-b="doThat"
>
  ![](...)
  <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

當 ref 和 v-for 一起使用時绷雏, ref 是一個數組或對象,包含相應的子組件怖亭。

$refs 只在組件渲染完成后才填充涎显,并且它是非響應式的。它僅僅作為一個直接訪問子組件的應急方案——應當避免在模版或計算屬性中使用 $refs 兴猩。

#異步組件

在大型應用中期吓,我們可能需要將應用拆分為多個小模塊,按需從服務器下載倾芝。為了讓事情更簡單讨勤, Vue.js 允許將組件定義為一個工廠函數,動態(tài)地解析組件的定義晨另。Vue.js 只在組件需要渲染時觸發(fā)工廠函數潭千,并且把結果緩存起來,用于后面的再次渲染借尿。例如:

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // Pass the component definition to the resolve callback
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

工廠函數接收一個 resolve 回調脊岳,在收到從服務器下載的組件定義時調用。也可以調用 reject(reason) 指示加載失敗垛玻。這里 setTimeout 只是為了演示。怎么獲取組件完全由你決定奶躯。推薦配合使用 :Webpack 的代碼分割功能:

Vue.component('async-webpack-example', function (resolve) {
  // 這個特殊的 require 語法告訴 webpack
  // 自動將編譯后的代碼分割成不同的塊帚桩,
  // 這些塊將通過 Ajax 請求自動下載。
  require(['./my-async-component'], resolve)
})

你可以使用 Webpack 2 + ES2015 的語法返回一個 Promise resolve 函數:

Vue.component(
  'async-webpack-example',
  () => System.import('./my-async-component')
)

如果你是 Browserify 用戶,可能就無法使用異步組件了,它的作者已經表明 Browserify 是不支持異步加載的嘹黔。Browserify 社區(qū)發(fā)現 一些解決方法账嚎,可能有助于已存在的復雜應用莫瞬。對于其他場景,我們推薦簡單實用 Webpack 構建郭蕉,一流的異步支持

#組件命名約定

當注冊組件(或者 props)時疼邀,可以使用 kebab-case ,camelCase 召锈,或 TitleCase 旁振。Vue 不關心這個。

// 在組件定義中
components: {
  // 使用 kebab-case 形式注冊
  'kebab-cased-component': { /* ... */ },
  // register using camelCase
  'camelCasedComponent': { /* ... */ },
  // register using TitleCase
  '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>

當使用字符串模式時拐袜,可以不受 HTML 的 case-insensitive 限制。這意味實際上在模版中梢薪,你可以使用 camelCase 蹬铺、 TitleCase 或者 kebab-case 來引用:

<!-- 在字符串模版中可以用任何你喜歡的方式! -->
<my-component></my-component>
<myComponent></myComponent>
<MyComponent></MyComponent>
如果組件未經 slot 元素傳遞內容,你甚至可以在組件名后使用 / 使其自閉合:
<my-component/>

當然秉撇,這只在字符串模版中有效甜攀。因為自閉的自定義元素是無效的 HTML ,瀏覽器原生的解析器也無法識別它琐馆。

#遞歸組件

組件在它的模板內可以遞歸地調用自己规阀,不過,只有當它有 name 選項時才可以:

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

當你利用Vue.component全局注冊了一個組件, 全局的ID作為組件的 name 選項啡捶,被自動設置.

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

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

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

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

#內聯模版

如果子組件有 inline-template 特性瞎暑,組件將把它的內容當作它的模板彤敛,而不是把它當作分發(fā)內容。這讓模板更靈活了赌。

<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 讓模板的作用域難以理解墨榄。最佳實踐是使用 template 選項在組件內定義模板或者在 .vue 文件中使用 template 元素。

#X-Templates

另一種定義模版的方式是在 JavaScript 標簽里使用 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'
})

這在有很多模版或者小的應用中有用,否則應該避免使用逢并,因為它將模版和組件的其他定義隔離了之剧。

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

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

Vue.component('terms-of-service', {
  template: '\
    <div v-once>\
      <h1>Terms of Service</h1>\
      ... a lot of static content ...\
    </div>\
  '
})
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市玻蝌,隨后出現的幾起案子蟹肘,更是在濱河造成了極大的恐慌词疼,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帘腹,死亡現場離奇詭異贰盗,居然都是意外死亡,警方通過查閱死者的電腦和手機阳欲,發(fā)現死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門舵盈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胸完,你說我怎么就攤上這事书释。” “怎么了赊窥?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵爆惧,是天一觀的道長。 經常有香客問我锨能,道長扯再,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任址遇,我火速辦了婚禮熄阻,結果婚禮上,老公的妹妹穿的比我還像新娘倔约。我一直安慰自己秃殉,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布浸剩。 她就那樣靜靜地躺著狞贱,像睡著了一般除嘹。 火紅的嫁衣襯著肌膚如雪淌哟。 梳的紋絲不亂的頭發(fā)上蓄拣,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音重罪,去河邊找鬼樱哼。 笑死,一個胖子當著我的面吹牛剿配,可吹牛的內容都是我干的搅幅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼呼胚,長吁一口氣:“原來是場噩夢啊……” “哼盏筐!你這毒婦竟也來了?” 一聲冷哼從身側響起砸讳,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤琢融,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后簿寂,有當地人在樹林里發(fā)現了一具尸體漾抬,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年常遂,在試婚紗的時候發(fā)現自己被綠了纳令。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡克胳,死狀恐怖平绩,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情漠另,我是刑警寧澤捏雌,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站笆搓,受9級特大地震影響性湿,放射性物質發(fā)生泄漏。R本人自食惡果不足惜满败,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一肤频、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧算墨,春花似錦宵荒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至面粮,卻和暖如春少孝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背熬苍。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工稍走, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柴底。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓婿脸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柄驻。 傳聞我的和親對象是個殘疾皇子狐树,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容