深入了解組件

傳遞靜態(tài)或動(dòng)態(tài)Prop

傳入靜態(tài)的值:

<blog-post title="My journey with Vue"></blog-post>

這時(shí)候值是一個(gè)字符串
你也可以通過(guò)v-bind動(dòng)態(tài)賦值:

<blog-post v-bind:title="post.title"></blog-post>

這時(shí)候值是一個(gè)js表達(dá)式

傳入一個(gè)布爾值

// 包含該prop沒(méi)有值的情況在內(nèi),都意味著true
<blog-post is-pushlished></blog-post>
<blog-post :is-pushlished="true"></blog-post>

傳入一個(gè)對(duì)象的所有屬性

如果你想要將一個(gè)對(duì)象的所有屬性都作為prop傳入,你可以使用不帶參數(shù)的v-bind。例如,對(duì)于一個(gè)給定的對(duì)象post:

post: {
   id: 1,
   title: 'My Journey with Vue'
}
// 下面的模版
<blog-post v-bind="post"></blog-post>
// 等價(jià)于
<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

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

所有prop都使得其父子prop之間形成一個(gè)單向下行綁定:父級(jí)prop的更新會(huì)向下流動(dòng)到子組件中,但是反過(guò)來(lái)不行。這樣會(huì)防止從子組件意外改變父組件的狀態(tài),從而導(dǎo)致你的應(yīng)用數(shù)據(jù)流向難以理解。額外的,每次父級(jí)組件發(fā)生更新時(shí),子組件中所有prop都將會(huì)刷新為最新的值。
總結(jié):prop會(huì)在子組件創(chuàng)建之前傳遞,父組件通過(guò)prop向子組件傳遞基本數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型,如果是基本數(shù)據(jù)類(lèi)型,這時(shí)候改變props的數(shù)據(jù)就會(huì)報(bào)錯(cuò),如果是引用數(shù)據(jù)類(lèi)型如果改變?cè)紨?shù)據(jù)不會(huì)報(bào)錯(cuò),但是重新賦值或改變某個(gè)屬性值就會(huì)報(bào)錯(cuò)

prop驗(yàn)證

Vuecomponent('my-component', {
    props: {
       propA: Number,
       // 多個(gè)可能的類(lèi)型
       propB: [String, Number],
       // 是否必傳,如果為true,則父組件必須要傳這個(gè)值,否則報(bào)錯(cuò)
       propC: {
           type: String,
           required: true
       },
       // 帶有默認(rèn)值的對(duì)象
       propD: {
           type: Object,
           default: () => { message: 'hello' }
       },
       // 自定義驗(yàn)證函數(shù),如果不符合條件就會(huì)報(bào)錯(cuò)
       propE: {
           validator: (value) => {
                return ['success', 'warning', 'danger'].indexOf(value) !== -1
           }
       }
    }
})

注意:prop會(huì)在一個(gè)組件實(shí)例創(chuàng)建之前進(jìn)行驗(yàn)證,所以實(shí)例的屬性(如data、computed等)在default或validator函數(shù)中是不可用的

非prop的特性

一個(gè)非prop特性是指?jìng)飨蛞粋€(gè)組件,但是該組件并沒(méi)有相應(yīng)prop定義的特性。

<bootstrap data-date-picker="activated"></bootstrap>

data-date-picker="activated"這個(gè)特性就會(huì)自動(dòng)添加到<bootstrap>的根元素上。

替換/合并已有的特性

假如子組件<bootstrap-date-input>的模板是這樣的:

<input type="date" class="form-control">

這時(shí)候我們?cè)谧咏M件上添加一個(gè)type和一個(gè)class:

<bootstrap-date-input  type="text" class="date-picker"></bootstrap-date-input>

子組件的模版上已經(jīng)有了這兩個(gè)屬性了,這時(shí)候我們發(fā)現(xiàn)子組件內(nèi)部設(shè)置的type被外部的type替換成text,但是class合并成form-control和date-picker。
總結(jié):除了class和style特性外部和內(nèi)部的根元素會(huì)合并起來(lái),其他的值會(huì)被外部替換掉

禁用特性繼承

如果你不希望組件的根元素繼承特性,你可以在組件的選項(xiàng)中設(shè)置inheritAttrs:false。例如:

Vue.component('my-component', {
    inheritAttrs:false,
    ....
})

這尤其適合配合實(shí)例的$attrs屬性使用,該屬性包含了傳遞給一個(gè)組件的特性名和特性值。

自定義事件

model屬性

一個(gè)組件上的v-model默認(rèn)會(huì)利用名為value的prop和名為input的事件,但是像單選框、復(fù)選框等類(lèi)型的輸入控件可能會(huì)將value特性用于不同的目的。model選項(xiàng)可以用來(lái)避免這樣的沖突:

<base-checkbox v-model="lovingVue"></base-checkbox>
Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

上面我們可以看到model將從父組件傳過(guò)來(lái)的屬性不管是vlaue還是checked統(tǒng)一名稱(chēng)為checked,監(jiān)聽(tīng)的事件不管是@input還是@change統(tǒng)一為change,這樣子就不需要根據(jù)不同的情況寫(xiě)不同的屬性和方法。
這里的lovingVue的值將會(huì)傳入這個(gè)名為checked的prop。同時(shí)當(dāng)<base-checkbox>觸發(fā)一個(gè)change事件并附帶一個(gè)新的值的時(shí)候,這個(gè)logingVue的屬性將會(huì)被更新。
注意你仍然需要在組件的props選項(xiàng)里聲明checked這個(gè)prop。

.sync修飾符

在有些情況下,我們可能需要對(duì)一個(gè)prop進(jìn)行雙向綁定。不幸的是,真正的雙向綁定會(huì)帶來(lái)維護(hù)上的問(wèn)題,因?yàn)樽咏M件可以修改父組件,造成理解困難。
這時(shí)候我們可以使用.sync修飾符,它的本質(zhì)和v-model類(lèi)似,只是一種縮寫(xiě)。

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

上面的代碼使用.sync就可以寫(xiě)成

<text-document v-bind:title.sync="doc.title"></text-document>

這樣在子組件中,就可以通過(guò)下面的代碼來(lái)實(shí)現(xiàn)對(duì)這個(gè)prop重新賦值的意圖了。

this.$emit('update:title', newTitle)

v-model和.sync背景

父子組件傳遞數(shù)據(jù)通過(guò)props,props傳遞的數(shù)據(jù)是單向數(shù)據(jù)流,但如果傳遞的是引用數(shù)據(jù)類(lèi)型,那么改變子組件傳遞過(guò)來(lái)的值父組件的值也會(huì)發(fā)生改變,這樣子雖然可以實(shí)現(xiàn)父子組件的雙向綁定,但會(huì)犧牲數(shù)據(jù)流向的簡(jiǎn)潔性,使得數(shù)據(jù)難以理解,最好不要這樣去做,在這個(gè)背景下v-model和.sync就出現(xiàn)了,這兩種方式都實(shí)現(xiàn)父子組件數(shù)據(jù)的雙向綁定。

v-mode和.sync對(duì)比

.sync從功能上看和v-model十分類(lèi)似,都是為了實(shí)現(xiàn)數(shù)據(jù)的雙向綁定,本質(zhì)上也都不是真正的雙向綁定,而是語(yǔ)法糖。
相比較之下,.sync更加靈活,它可以給多個(gè)prop使用,而v-model在一個(gè)組件中只能有一個(gè)。
從語(yǔ)義上來(lái)看,v-model綁定的值是指這個(gè)組件的綁定值,比如input組件,select組件,各種表單元素,這些組件所綁定的值使用v-model比較合適,因?yàn)関-model是一個(gè)語(yǔ)法糖,傳遞的各種類(lèi)型的值比如vlaue、checked等等,vue已經(jīng)處理了各種情況,不需要自己去分別定義傳遞的屬性。其他情況沒(méi)有這種語(yǔ)義,.sync會(huì)更合適。
從寫(xiě)法上來(lái)看,.sync更加簡(jiǎn)潔,不需要在 子組件中定義props就可以直接實(shí)現(xiàn)數(shù)據(jù)雙向綁定。
這兩種方式子組件都沒(méi)有直接改變prop的值,只是通過(guò)事件監(jiān)聽(tīng)的方式直接改變父組件的值。
注意帶有 .sync 修飾符的 v-bind 不能和表達(dá)式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是無(wú)效的)。取而代之的是,你只能提供你想要綁定的屬性名,類(lèi)似 v-model。
當(dāng)我們用一個(gè)對(duì)象同時(shí)設(shè)置多個(gè) prop 的時(shí)候,也可以將這個(gè) .sync 修飾符和 v-bind 配合使用:

<text-document v-bind.sync="doc"></text-document>

這樣會(huì)把 doc 對(duì)象中的每一個(gè)屬性 (如 title) 都作為一個(gè)獨(dú)立的 prop 傳進(jìn)去,然后各自添加用于更新的 v-on 監(jiān)聽(tīng)器。
將 v-bind.sync 用在一個(gè)字面量的對(duì)象上,例如 v-bind.sync=”{ title: doc.title }”,是無(wú)法正常工作的,因?yàn)樵诮馕鲆粋€(gè)像這樣的復(fù)雜表達(dá)式的時(shí)候,有很多邊緣情況需要考慮。

參考文章:https://juejin.im/post/5d0489dff265da1ba84a8e41

插槽

我們?yōu)榫呙宀酆妥饔糜虿宀垡肓艘粋€(gè)新的統(tǒng)一的語(yǔ)法,即v-slot指令。它取代了slot和slot-scope這兩個(gè)目前已被廢棄但未被移除且仍在文檔中的特性。

插槽內(nèi)容

Vue實(shí)現(xiàn)了一套內(nèi)容分發(fā)的API,將slot元素作為承載分發(fā)內(nèi)容的出口,也被稱(chēng)為插槽,插槽的內(nèi)容是寫(xiě)在自定義組件中間,插槽內(nèi)容可以是包含任何模版代碼或其他組件,這些內(nèi)容會(huì)將slot元素替換,如果自定義組件內(nèi)沒(méi)有插槽,那么這個(gè)組件中間寫(xiě)的任何內(nèi)容都會(huì)被拋棄。

編譯作用域

當(dāng)你想在一個(gè)插槽內(nèi)容中使用數(shù)據(jù)時(shí),例如:

<template>
 <div id="demo">
  <navigation-link url="/profile">
    name: {{name}}
    url: {{url}}
  </navigation-link>
</div>
</template>
<script>
import navigationLink from './navigationLink'
export default {
  components: {navigationLink},
  data () {
    return {
      name: 'jack'
    }
  }
}
</script>

你會(huì)發(fā)現(xiàn)插槽內(nèi)容跟模版其他地方一樣可以訪(fǎng)問(wèn)相同的實(shí)例屬性(也就是相同的作用域),而不能訪(fǎng)問(wèn)navigation-link子組件的作用域。
作為一條規(guī)則,請(qǐng)記?。?strong>父級(jí)模版里的所有內(nèi)容都是在父級(jí)作用域中編譯的,子模版里的所有內(nèi)容都是在子作用域中編譯的。

后備內(nèi)容(備用內(nèi)容)

如果插槽內(nèi)沒(méi)有內(nèi)容,我們希望能顯示默認(rèn)的內(nèi)容,這時(shí)候我們可以在slot元素中寫(xiě),如果沒(méi)有插槽內(nèi)容就會(huì)顯示slot元素中內(nèi)容,如果有內(nèi)容就會(huì)替換slot元素中的內(nèi)容。

具名插槽

有時(shí)我們需要多個(gè)插槽,例如對(duì)于一個(gè)帶有如下模版的<base-layout>組件:

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

一個(gè)不帶name的slot出口會(huì)帶有隱含的名字default。
在向具名插槽提供內(nèi)容的時(shí)候,我們可以在一個(gè)template元素上使用v-slot指令,并以v-slot的參數(shù)的形式提供其名稱(chēng):

<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)的插槽。任何沒(méi)有被包裹在帶有 v-slot 的 <template> 中的內(nèi)容都會(huì)被視為默認(rèn)插槽的內(nèi)容。
注意:v-slot只能添加在一個(gè)template元素上(只有一種例外情況就是獨(dú)占默認(rèn)插槽的縮寫(xiě)語(yǔ)法,下面會(huì)講到)

作用域插槽

如果我們想讓插槽內(nèi)容訪(fǎng)問(wèn)組件內(nèi)的數(shù)據(jù),這時(shí)候我們可以使用作用域插槽,將數(shù)據(jù)作為slot元素的一個(gè)特性綁定上去:

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

綁定在slot元素上的特性被稱(chēng)為插槽porp。在父級(jí)作用域中,我們可以給v-slot帶一個(gè)值來(lái)定義我們提供的插槽prop的名字:

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>
獨(dú)占默認(rèn)插槽的縮寫(xiě)語(yǔ)法

當(dāng)組件內(nèi)只有默認(rèn)插槽時(shí),組件的標(biāo)簽就可以被當(dāng)作插槽的模版來(lái)使用。這樣我們就可以把v-slot直接用在組件上:

<current-user v-slot:default="slotProps">
  {{slotProps.user.firstName}}
</current-user>
// 不帶參數(shù)的v-slot被假定對(duì)應(yīng)默認(rèn)插槽,可以簡(jiǎn)寫(xiě)為
<current-user v-slot="slotProps">
  {{slotProps.user.firstName}}
</current-user>

注意默認(rèn)插槽的縮寫(xiě)語(yǔ)法不能和具名插槽混用,因?yàn)樗鼤?huì)導(dǎo)致作用域不明確,只要出現(xiàn)多個(gè)插槽,請(qǐng)始終為所有的插槽使用完整的基于<template>的語(yǔ)法。

解構(gòu)插槽prop
<current-user v-slot="{ user }">
  {{ user.firstName }}
</current-user>
// prop重命名
<current-user v-slot="{ user: person }">
  {{ person.firstName }}
</current-user>
// 插槽prop是undefined
<current-user v-slot="{ user = { firstName: 'Guest' } }">
  {{ user.firstName }}
</current-user>

動(dòng)態(tài)插槽名

動(dòng)態(tài)指令參數(shù)也可以用在v-slot上,來(lái)定義動(dòng)態(tài)的插槽名:

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>

具名插槽的縮寫(xiě)

跟v-on和v-bind一樣,v-slot也有縮寫(xiě),即把參數(shù)之前的所有內(nèi)容(v-slot:)替換為字符#。例如v-slot:header可以重寫(xiě)為#header:

<base-layout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>
</base-layout>

如果你希望使用縮寫(xiě)的話(huà),你必須始終以明確插槽名取而代之:

<current-user #default="{ user }">
  {{ user.firstName }}
</current-user>

總結(jié):插槽有點(diǎn)像一個(gè)單獨(dú)的組件,可以定義它的名稱(chēng),也可以傳值給插槽內(nèi)容,插槽在設(shè)計(jì)基礎(chǔ)組件的時(shí)候非常有用,比如我之前寫(xiě)的彈框組件,element-ui中的很多組件都有用到插槽,你可以在組件里面寫(xiě)你自定義的其他組件。

處理邊界的情況

在大多數(shù)情況下,我們最好不要觸達(dá)另一個(gè)組件實(shí)例內(nèi)部或手動(dòng)操作DOM元素。比如使用$refs、$parent、$children、$root等等,最好使用Vuex來(lái)管理應(yīng)用的狀態(tài)。

混入

混入提供了一種非常靈活的方式,來(lái)分發(fā)Vue組件中的可復(fù)用功能。

選項(xiàng)合并

當(dāng)組件和混入對(duì)象含有同名選項(xiàng)時(shí),這些選項(xiàng)將以恰當(dāng)?shù)姆绞竭M(jìn)行合并。

data數(shù)據(jù)合并

data中數(shù)據(jù)有同名選項(xiàng)時(shí),如果是基本類(lèi)型就會(huì)以組件數(shù)據(jù)為準(zhǔn),如果是引用類(lèi)型就會(huì)合并。

生命周期合并

生命周期里的代碼都會(huì)執(zhí)行,并且混入對(duì)象的代碼會(huì)先執(zhí)行。

methods、components、directives

這三個(gè)對(duì)象將被合并為同一個(gè)對(duì)象,如果發(fā)生同名選項(xiàng),以組件的為準(zhǔn)。
注意:Vue.extend()也使用同樣的策略進(jìn)行合并。

自定義指令

基本用法

除了核心功能默認(rèn)內(nèi)置的指令比如v-model、v-show,Vue也允許注冊(cè)自定義指令。一般用在需要對(duì)普通DOM元素進(jìn)行底層操作,這時(shí)候就會(huì)用到自定義指令。比如業(yè)務(wù)需求是input輸入框初始是聚焦的狀態(tài):

// 注冊(cè)一個(gè)全局自定義指令v-focus
Vue.directive('focus', {
  // 當(dāng)被綁定的元素插入到DOM中時(shí)
  inserted (el, binding) {
     el.focus()
  }
})
// 注冊(cè)一個(gè)局部的指令
directives: {
  focus: {
    inserted (el, binding) {
      el.focus()
    }
  }
}
動(dòng)態(tài)指令參數(shù)

指令的參數(shù)可以是動(dòng)態(tài)的。例如,在v-myditective:[argument]="value"中,argument參數(shù)可以根據(jù)組件實(shí)例數(shù)據(jù)進(jìn)行更新!這使得自定義指令可以在應(yīng)用中被靈活使用。

<div id="dynamicexample">
  <h3>Scroll down inside this section ↓</h3>
  <p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
Vue.directive('pin', {
  bind: function (el, binding, vnode) {
    el.style.position = 'fixed'
    var s = (binding.arg == 'left' ? 'left' : 'top')
    el.style[s] = binding.value + 'px'
  }
})

new Vue({
  el: '#dynamicexample',
  data: function () {
    return {
      direction: 'left'
    }
  }
})

過(guò)濾器

Vue允許你自定義過(guò)濾器,可被用于一些常見(jiàn)的文本格式化,或者是一些數(shù)據(jù)轉(zhuǎn)換等等,但是過(guò)濾器在實(shí)踐當(dāng)中可以被其他方式取代,比如寫(xiě)一些公共方法去處理或者直接直接在methods去調(diào)用某個(gè)方法等等,所以現(xiàn)在用到的越來(lái)越少,這個(gè)功能就顯得有點(diǎn)雞肋。不過(guò)它有一個(gè)優(yōu)勢(shì)是別的方法實(shí)現(xiàn)比較麻煩的,鏈?zhǔn)秸{(diào)用,后面會(huì)講到。

基本用法

過(guò)濾器可以用在兩個(gè)地方:雙花括號(hào)插值和v-bind表達(dá)式。

// 在雙花括號(hào)中
{{ message | capitalize }}
// 在v-bind中
<div v-bind="rawId | formatId"></div>

你可以在一個(gè)組件的選項(xiàng)中定義本地的過(guò)濾器:

filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

或者在創(chuàng)建Vue實(shí)例之前全局 定義過(guò)濾器:

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
  // ...
})

過(guò)濾器可以串聯(lián):

{{ message | filterA('arg1', 'arg2') | filterB }}

在這個(gè)例子中,message表達(dá)式將作為filterA過(guò)濾器函數(shù)的第一個(gè)參數(shù),'arg1'和'arg2'將作為第二個(gè)、第三個(gè)參數(shù),filterA函數(shù)返回的結(jié)果又會(huì)作為filterB函數(shù)的第一個(gè)參數(shù)。

key

key的特殊屬性主要用在Vue虛擬DOM算法,在新舊nodes對(duì)比時(shí)辨識(shí)vnodes。如果不實(shí)用key,Vue會(huì)使用一種最大限度減少動(dòng)態(tài)元素并且盡可能的嘗試修復(fù)/再利用相同類(lèi)型元素的算法。使用key,它會(huì)基于key的變化重新排列元素順序,并且會(huì)移除key不存在的元素。
有相同父元素的子元素必須有特殊的key。重復(fù)的key會(huì)造成渲染錯(cuò)誤。它可以用于強(qiáng)制替換元素/組件而不是重復(fù)使用它。

Vue.extend()

使用基礎(chǔ)Vue構(gòu)造器,創(chuàng)建一個(gè)'子類(lèi)'。參數(shù)是一個(gè)包含組件選項(xiàng)的對(duì)象,data選項(xiàng)必須是函數(shù)。

<div id="mount-point"></div>
// 創(chuàng)建構(gòu)造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 創(chuàng)建 Profile 實(shí)例,并掛載到一個(gè)元素上。
new Profile().$mount('#mount-point')

結(jié)果如下:

<p>Walter White aka Heisenberg</p>

Vue.observable()

讓一個(gè)對(duì)象可響應(yīng)。Vue內(nèi)部會(huì)用它來(lái)處理data函數(shù)返回的對(duì)象。
返回的對(duì)象可以直接用于渲染函數(shù)和計(jì)算屬性?xún)?nèi),并且會(huì)在發(fā)生改變時(shí)觸發(fā)相應(yīng)的更新。也可以作為最小化的跨組件狀態(tài)存儲(chǔ)器,用于簡(jiǎn)單的場(chǎng)景:

const state = Vue.observable({ count: 0 })

const Demo = {
  render(h) {
    return h('button', {
      on: { click: () => { state.count++ }}
    }, `count is: ${state.count}`)
  }
}

參考文章:https://segmentfault.com/a/1190000019292569

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

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

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