Vue - 組件(Component)

一、什么是組件(Component)?

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

所有Vue組件同事也都是Vue實例,所以可以接受相同的選項對象(除了一些根級特有的選項)并且提供相同的生命周期鉤子。

二、使用組件

1.全局組件

我們知道,創(chuàng)建一個Vue實例可以:

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

全局注冊組件,可以使用Vue.component(tagName, options)。 比如:

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

請注意,對于自定義標(biāo)簽的命名 Vue.js 不強(qiáng)制遵循 W3C 規(guī)則 (小寫,并且包含一個短杠),盡管這被認(rèn)為是最佳實踐。

組件在注冊之后,便可以作為自定義元素:<my-component></my-component>使用了。
注意確保在初始化根實例之前注冊組件:

在初始化根實例之前注冊組件這句話就是指的是:

Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})

var vm = new Vue({
  el: '#box'
})

這樣的順序是對的,而

var vm = new Vue({
  el: '#box'
})

Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})

這樣是錯的。

<div id="example">
  <my-component></my-component>
</div>
// 注冊
Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})

// 創(chuàng)建根實例
new Vue({
  el: '#example'
})

我們要注意,一般選項中需要添加template,就是HTML的構(gòu)造。

渲染為:

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

2.局部注冊

不必把每個組件都注冊到全局。你可以通過某個 Vue 實例/組件的實例選項 components 注冊僅在其作用域中可用的組件:

var Child = {
  template: '<div>A custom component!</div>'
}

var vm = new Vue({
  el: '#box',
  components: {
    'my-component': Child
  }
})

var vm = new Vue({
  el: '#box2'
})
<div id="box">
  <my-component></my-component>

</div>

<div id="box2">
  <my-component></my-component>
</div>

Vue組件的全局注冊,可以在多個Vue實例中使用,如果是在Vue構(gòu)造器中局部注冊,那么只能在此Vue實例中使用。

那么在<div id="box">下面的<my-component></my-component>將不會渲染出來。

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

3.DOM模板解析注意事項

當(dāng)使用 DOM 作為模板的時候(例如,使用 el 選項來把 Vue 實例掛載到一個已有內(nèi)容的元素上),你會受到 HTML 本身的一些限制,因為 Vue 只有在瀏覽器解析,規(guī)范化之后才能獲取其內(nèi)容。尤其要注意,像 <ul> <ol>, <table><select> 這樣的元素里面允許包含的元素有限制,而另外一些像 <option> 這樣的元素只能出現(xiàn)在某些特定元素的內(nèi)部。

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

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

自定義組件 <my-row> 會被當(dāng)成無效的內(nèi)容,因此會導(dǎo)致錯誤的渲染結(jié)果。變通方式是使用特殊的 is 特性:

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

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

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

因此,請盡可能使用字符串模板。

  1. data必須是函數(shù)

構(gòu)造 Vue 實例時傳入的各種選項大多數(shù)都可以在組件里使用。只有一個例外:data 必須是函數(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>

data參數(shù)中,一般是返回的js對象。比如:

data: function () {
    return {
        text: 'text',
        name: 'Hello'
    }
}
var data = { counter: 0 }

Vue.component('simple-counter', {
  template: '<button v-on:click="counter += 1">{{ counter }}</button>',   # 注意:這里是 += 不是 +
  // 技術(shù)上 data 的確是一個函數(shù)了,因此 Vue 不會警告,
  // 但是我們卻給每個組件實例返回了同一個對象的引用
  data: function () {
    return data
  }
})

new Vue({
  el: '#example-2'
})

由于這三個組件實例共享了同一個 data 對象,因此遞增一個 counter 會影響所有組件!這就錯了。我們可以通過為每個組件返回全新的數(shù)據(jù)對象來修復(fù)這個問題:

data: function () {
  return {
    counter: 0
  }
}

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

5.組件組合

組件設(shè)計初衷就是要配合使用的,最常見的就是形成父子組件的關(guān)系:組件 A 在它的模板中使用了組件 B。它們之間必然需要相互通信:父組件可能要給子組件下發(fā)數(shù)據(jù),子組件則可能要將它內(nèi)部發(fā)生的事情告知父組件。然而,通過一個良好定義的接口來盡可能將父子組件解耦也是很重要的。這保證了每個組件的代碼可以在相對隔離的環(huán)境中書寫和理解,從而提高了其可維護(hù)性和復(fù)用性。

在 Vue 中,父子組件的關(guān)系可以總結(jié)為 prop 向下傳遞,事件向上傳遞。父組件通過 prop 給子組件下發(fā)數(shù)據(jù),子組件通過事件給父組件發(fā)送消息。看看它們是怎么工作的。

圖片.png

三、Prop

1.使用Prop傳值
組件實例的作用域是孤立的。這意味著不能 (也不應(yīng)該) 在子組件的模板內(nèi)直接引用父組件的數(shù)據(jù)。父組件的數(shù)據(jù)需要通過 prop 才能下發(fā)到子組件中。

子組件要顯式地用 props 選項聲明它預(yù)期的數(shù)據(jù):

Vue.component('child', {
  // 聲明 props
  props: ['message'],
  // 就像 data 一樣,prop 也可以在模板中使用
  // 同樣也可以在 vm 實例中通過 this.message 來使用
  template: '<span>{{ message }}</span>'
})
<div id="box">
  <child message="hellow"></child>
  <child message="hellow"></child>
  <child message="123"></child>
</div>

2.駝峰法 vs 短橫線分隔法

HTML 特性是不區(qū)分大小寫的。所以,當(dāng)使用的不是字符串模板時,camelCase (駝峰式命名) 的 prop 需要轉(zhuǎn)換為相對應(yīng)的 kebab-case (短橫線分隔式命名):

Vue.js中是區(qū)分大小寫的。

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

注意是自動轉(zhuǎn)換為的,我們需要在使用模板的時候<child my-message="hello!"></child>,注意到需要使用短橫線分隔符號。

3.動態(tài)Prop

與綁定到任何普通的 HTML 特性類似,我們可以使用 v-bind 來動態(tài)地將prop綁定到父組件的數(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>

如果你想把一個對象的所有屬性作為 prop 進(jìn)行傳遞,可以使用不帶任何參數(shù)的 v-bind (即用 v-bind 而不是 v-bind:prop-name)。例如,已知一個 todo 對象:

var vm = new Vue({
  el: '#box',
  data: {
    todo: {
      text: 'Learn Vue',
      isComplete: false
    }
  }
})
<todo-item v-bind="todo"></todo-item>

等價于:

<todo-item
  v-bind:text="todo.text"
  v-bind:is-complete="todo.isComplete"
></todo-item>

eg:

<div id="box">
  <child v-bind="todo"></child>
</div>
Vue.component('child', {
  props: ['text', 'is-complete'],
  template: '<span>{{ text }}</span>'
})

var vm = new Vue({
  el: '#box',
  data: {
    todo: {
      text: 'Learn Vue',
      isComplete: false
    }
  }
})

或者直接傳遞一個對象:

<body>

<div id="box">
  <child v-bind:todo="todo"></child>
</div>

Vue.component('child', {
  props: ['todo'],
  template: '<span>{{ todo.text }} {{ todo.isComplete }} </span>'
})

var vm = new Vue({
  el: '#box',
  data: {
    todo: {
      text: 'Learn Vue',
      isComplete: false
    }
  }
})

從上面可以看出,一般我們還是這樣傳遞最好:

<child v-bind:todo="todo"></child>

不要v-bind="todo",這樣不是很方便樣。

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

字面量語法,就是直接傳遞字符串這樣:
初學(xué)者常犯的一個錯誤是使用字面量語法傳遞數(shù)值:

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

因為它是一個字面量 prop,它的值是字符串 "1" 而不是一個數(shù)值。如果想傳遞一個真正的 JavaScript 數(shù)值,則需要使用 v-bind,從而讓它的值被當(dāng)作 JavaScript 表達(dá)式計算:
下面才是正確的:

<!-- 傳遞真正的數(shù)值 -->
<comp v-bind:some-prop="1"></comp>

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

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

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

在兩種情況下,我們很容易忍不住想去修改 prop 中數(shù)據(jù):

1)Prop 作為初始值傳入后,子組件想把它當(dāng)作局部數(shù)據(jù)來用;
2)Prop 作為原始數(shù)據(jù)傳入,由子組件處理成其它數(shù)據(jù)輸出。

正確的應(yīng)對方式是:
1)定義一個局部變量,并且使用prop的值初始化它:

Vue.component('child', {
  props: ['todo'],
  template: '<span>{{ todo }} </span>',
  data: function () {
    return {
      com_todo: this.todo + " ok"
    }
  }
})

注意:外部傳入了todo進(jìn)來,那么現(xiàn)在又返回的有一個com_todo,現(xiàn)在組件中可以使用的數(shù)據(jù)就有兩個了:todocom_todo。
在組件的template中,
可以這樣寫:

  template: '<span>{{ todo }} </span>'
  template: '<span>{{ this.todo }} </span>',
  template: '<span>{{ com_todo }} </span>',
  template: '<span>{{ this.com_todo }} </span>',

2)定義一個計算屬性(computed),來處理prop的值并且返回:

<div id="box">
  <child v-bind:todo="123"></child>
</div>
Vue.component('child', {
  props: ['todo'],
  template: '<span>{{ computed_todo }} </span>',
  data: function () {
    return {
      com_todo: this.todo + " ok"
    }
  },
  computed: {
    computed_todo: function () {
      return this.todo + "2"    # 注意這里不能 return todo + "2"
    }
  }
})

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

  1. Prop驗證

我們可以為組件的 prop 指定驗證規(guī)則。如果傳入的數(shù)據(jù)不符合要求,Vue 會發(fā)出警告。這對于開發(fā)給他人使用的組件非常有用。

要指定驗證規(guī)則,需要用對象的形式來定義 prop,而不能用字符串?dāng)?shù)組:
(我們之前的字符串?dāng)?shù)組形式是這樣的:props:['todo', 'doit'])

對象形式是:

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' }
      }
    },
    // 自定義驗證函數(shù)
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
})

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

String
Number
Boolean
Function
Object
Array
Symbol

四、 非Prop特性

所謂非 prop 特性,就是指它可以直接傳入組件,而不需要定義相應(yīng)的 prop。

盡管為組件定義明確的 prop 是推薦的傳參方式,組件的作者卻并不總能預(yù)見到組件被使用的場景。所以,組件可以接收任意傳入的特性,這些特性都會被添加到組件的根元素上。

例如,假設(shè)我們使用了第三方組件 bs-date-input,它包含一個 Bootstrap 插件,該插件需要在 input 上添加 data-3d-date-picker 這個特性。這時可以把特性直接添加到組件上 (不需要事先定義 prop):

<bs-date-input data-3d-date-picker="true"></bs-date-input>

添加屬性 data-3d-date-picker="true" 之后,它會被自動添加到 bs-date-input 的根元素上。

五、自定義事件

我們知道,父組件使用 prop 傳遞數(shù)據(jù)給子組件。但子組件怎么跟父組件通信呢?這個時候 Vue 的自定義事件系統(tǒng)就派得上用場了。

  1. 使用 v-on 綁定自定義事件
    每個Vue實例都實現(xiàn)了事件接口,即:
使用 $on(eventName) 監(jiān)聽事件  (也叫綁定事件)
使用 $emit(eventName) 觸發(fā)事件

Vue 的事件系統(tǒng)與瀏覽器的 EventTarget API 有所不同。盡管它們的運行起來類似,但是 $on$emit 并不是addEventListenerdispatchEvent 的別名。

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

不能用 $on 監(jiān)聽子組件釋放的事件,而必須在模板里直接用 v-on 綁定,參見下面的例子。

下面是一個例子:
(父組件在使用子組件的地方使用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="incrementCounter">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    incrementCounter: function () {
      this.counter += 1
      this.$emit('increment')
    }
  },
})

new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
})

我們可以看到,這里有兩個v-on:click=事件綁定,注意這個increment就是自定義事件:
是使用this.$emit('increment')觸發(fā)這個事件的。

<button v-on:click="incrementCounter">{{ counter }}</button>  # 子級(自己)
<button-counter v-on:increment="incrementTotal"></button-counter>  # 父級

子組件已經(jīng)和它外部完全解耦了。它所做的只是報告自己的內(nèi)部事件,因為父組件可能會關(guān)心這些事件。請注意這一點很重要。

2.給組件綁定原生事件

有時候,你可能想在某個組件的根元素上監(jiān)聽一個原生事件??梢允褂?v-on 的修飾符 .native。例如:

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

3..sync修飾符

在一些情況下,我們可能會需要對一個 prop 進(jìn)行“雙向綁定”。事實上,這正是 Vue 1.x 中的 .sync 修飾符所提供的功能。當(dāng)一個子組件改變了一個帶 .sync 的 prop 的值時,這個變化也會同步到父組件中所綁定的值。這很方便,但也會導(dǎo)致問題,因為它破壞了單向數(shù)據(jù)流。由于子組件改變 prop 的代碼和普通的狀態(tài)改動代碼毫無區(qū)別,當(dāng)光看子組件的代碼時,你完全不知道它何時悄悄地改變了父組件的狀態(tài)。這在 debug 復(fù)雜結(jié)構(gòu)的應(yīng)用時會帶來很高的維護(hù)成本。
上面所說的正是我們在 2.0 中移除 .sync 的理由。但是在 2.0 發(fā)布之后的實際應(yīng)用中,我們發(fā)現(xiàn) .sync 還是有其適用之處,比如在開發(fā)可復(fù)用的組件庫時。我們需要做的只是讓子組件改變父組件狀態(tài)的代碼更容易被區(qū)分。
從 2.3.0 起我們重新引入了 .sync 修飾符,但是這次它只是作為一個編譯時的語法糖存在。它會被擴(kuò)展為一個自動更新父組件屬性的 v-on 監(jiān)聽器。

eg:

<comp :foo.sync="bar"></comp>

會被擴(kuò)展為:

<comp :foo="bar" @update:foo="val => bar = val"></comp>

當(dāng)子組件需要更新 foo 的值時,它需要顯式地觸發(fā)一個更新事件:

this.$emit('update:foo', newValue)

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

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

<input v-model="something">

這不過是以下示例的語法糖:

<input
  v-bind:value="something"
  v-on:input="something = $event.target.value">
components: {
      'm-header':MHeader
}

components: {
      MHeader
}

是一樣的,只是省略掉了默認(rèn)的那個。

6.Vue的父組件與子組件之間的交互(數(shù)據(jù)和事件):
https://blog.csdn.net/qq_16559905/article/details/78761956

注意:子組件和父組件 函數(shù)的傳遞,是不需要寫在props中的。
子組件:

this.$emit('function', params)

父組件:

<sub-component @function="function_in_parent" ></sub-component>

這里的function_in_parent是在父組件中實現(xiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Vue 實例 屬性和方法 每個 Vue 實例都會代理其 data 對象里所有的屬性:var data = { a:...
    云之外閱讀 2,361評論 0 6
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容,還有我對于 Vue 1.0 印象不深的內(nèi)容。關(guān)于...
    云之外閱讀 5,168評論 0 29
  • 1.安裝 可以簡單地在頁面引入Vue.js作為獨立版本,Vue即被注冊為全局變量,可以在頁面使用了。 如果希望搭建...
    Awey閱讀 11,278評論 4 129
  • 此文基于官方文檔,里面部分例子有改動,加上了一些自己的理解 什么是組件? 組件(Component)是 Vue.j...
    陸志均閱讀 3,944評論 5 14
  • Pushing though the pain to be the best that I can be .去強(qiáng)忍...
    一只蝸牛的馬拉松閱讀 780評論 0 1

友情鏈接更多精彩內(nèi)容