iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 4
1
  • 上一章我們的 b-button 只是徒有虛名而已,接下來要讓他擁有一些作用。
  • 以下是本章參考資料,可以晚點再回來看,最下面也會放。
  1. https://jigsawye.com/2015/10/06/javascript-object-assign/
  2. https://cn.vuejs.org/v2/api/#inheritAttrs
  3. https://cn.vuejs.org/v2/api/#vm-attrs
  4. https://cn.vuejs.org/v2/api/#vm-listeners
  5. https://cn.vuejs.org/v2/api/#v-on
  • 一般來說我們使用 @click 來對某個元素進行點擊監聽,所以我們可能會像這樣來監聽我們的 m-button。
<m-button @click="app">給我一輛坦克車</m-button>

... in new Vue()

methods: {
	app () {
		alert('請給我一輛十階坦克車')
	}
}
  • 但是你會發現他一點作用也沒有,為何呢,這是因為在組件上寫的任何監聽,監聽的對象都是組件的 "自定義事件",請參考文件。那有辦法讓我們原本的思路成功嗎,可以,加上 .native 修飾就可以了,.native 就是用來監聽原生事件的。
<m-button @click.native="app">給我一輛坦克車</m-button>
  • 另外一個方法,若你不想使用 .native 硬要用@click,也是可以,就變成我們自定義。
  • 在子層中的 button 可以監聽原生 click 後
  • 可以透過 $emit( '自定義事件名稱', 附帶給父層的值 ) 一個自定義事件給父層
in m-button ...
template: `
    <button 
        type="button" 
        class="btn" 
        :class=" 'btn-' + dynamicClass"
        @click="$emit('click')"
    >
      <slot></slot>
    </button>
`
  • 透過發射一個一模一樣的 click 事件給父層,我們就可以像平常一樣的不使用 .native 了。
<m-button @click="app">給我一輛坦克車</m-button>
  • 發射自定義事件的同時也可以附帶值給父層,寫在 $emit() 第二個參數位置,父層用 $event 接收此值。
template: `
    <button 
        type="button" 
        class="btn" 
        :class=" 'btn-' + dynamicClass"
        @click="$emit('click', { name: 'aka allen'} )"
    >
      <slot></slot>
    </button>
`

父層用 $event 接收此值並傳進 app 方法 ...

<m-button @click="app($event)">給我一輛坦克車</m-button>

Vue 實例中 ...

methods: {
	app (e) {
		alert('請給我一輛十階坦克車')
		console.log(e) // 得到 { name: 'aka allen'} 物件
	}
}

  • 接下來參照官網範例, 對自定義事件看起來是滿高級的包裝,具體好不好用我不知道,但製作前必須先有一些前置知識點。

Object.assign(第一個物件, 第二個物件, ..., ...),原生 JS 方法。

用途 : 合併物件通通到第一個物件,若有重複的成員名稱,後面覆蓋前面。

let a = {
	banana: 1,
	apple: 4
}

let b = {
	apple: 10,
	bottle: 11
}

console.log(Object.assign(a, b)) 
/*
	輸出 
	{
		banana: 1,
		apple: 10,
		bottle: 11
	}
*/

inheritAttrs: false,為組件設置的option(可選)

我們從組件父層透過 tag attribute 的方式傳入資訊時,若此 attribute 有通過子層的 prop 驗證,那麼就會被提取進去子層,若是沒有通過 prop 驗證或者是此 tag attribute 根本不可能是 props 的成員之一時,此 tag attribute 將會被退貨,成為顯示在 HTML 上的屬性。

設定 inheritAttrs: false,那麼此默認行為會取消,你在 HTML 上加入的 tag attribute 將不會顯示並且全部被傳進子層,但是並不代表通過 prop 驗證,但是你可以在子層使用這些沒通過的值。

// 沒有 inheritAttrs: false
Vue.component('case-inherit-attrs', {
  props: ['label'],
  template: '<div>{{ label }}</div>'
})
<case-inherit-attrs 
     label="case-inheritAttrs 測試"
     apple="123"                
     banana="321"                
>
</case-inherit-attrs>
  • 看 apple 和 banana 被退貨到父層等待發落,只有 label 通過進子層

    https://ithelp.ithome.com.tw/upload/images/20200912/20129819PGzZnlzErr.jpg

  • 現在換下面加入 inheritAttrs: false

// 有 inheritAttrs: false
Vue.component('case-inherit-attrs', {
  inheritAttrs: false,
  props: ['label'],
  template: '<div>{{ label }}</div>'
})
  • 被照單全收,傳進子層了,但不代表通過 prop 驗證,只是被退貨在子層等待發落罷了。

https://ithelp.ithome.com.tw/upload/images/20200912/20129819sz0pWt0Wmi.jpg

$attrs

負責收集沒有通過 prop 驗證的成員。不受 inheritAttrs 狀態影響,不管你是被退到哪裏,通通都在這,透過子層 {{ $attrs }} 可以很清楚看見,可使用 v-bind="$attrs" 向下傳遞,晚點會有示範。

<case-attrs-with-inherit-false
    label="$attrs 測試 : 被退貨在子層等待發落"
    apple="不管退貨位置通通被綁架1"                
    banana="不管退貨位置通通被綁架2"
></case-attrs-with-inherit-false>

<br>

<case-attrs-no-inherit-false
  label="$attrs 測試 : 被退貨在父層,請自行F12查看"
  apple="不管退貨位置通通被綁架3"                
  banana="不管退貨位置通通被綁架4"               
></case-attrs-no-inherit-false>

... in script tag

Vue.component('case-attrs-with-inherit-false', {
  inheritAttrs: false,
  props: ['label'],
  template: '<div>{{ label }} <br> {{ $attrs }}</div>'
})

Vue.component('case-attrs-no-inherit-false', {
  props: ['label'],
  template: '<div>{{ label }} <br> {{ $attrs }}</div>'
})

https://ithelp.ithome.com.tw/upload/images/20200912/20129819fEymg1TNce.jpg

$listeners

用途 : 包含了組件父層監聽自定義事件的物件集合,可使用 v-on="$listeners" 向下傳遞,晚點會有示範。

<case-listeners
  @click="app"
  @input.native="app"
  @focus="app"
  @change="app"
  label="$listeners 測試,但因為子層沒有發射自定義事件,所以app沒用"
></case-listeners>

in script tag ...

Vue.component('case-listeners', {
  props: ['label'],
  template: `
    <input :value="label" />
  `,
  created () {
    console.log('case-listeners', this.$listeners)
  }
})
  • $listeners = 父層監聽自定義事件的物件集合,其值皆為預設行為的 function 回傳 event

https://ithelp.ithome.com.tw/upload/images/20200912/20129819M1eQWqMpl2.jpg

示範怎麼傳遞 v-on="$attrs"、v-on="$listeners"

  1. 在 'transfer-attrs-listeners' 註冊局部組件 'receieve-aka-allen'
  2. 'transfer-attrs-listeners' 的 template 中使用 'receieve-aka-allen'
  3. 並把 v-on="$attrs"、v-on="$listeners" 掛上 'receieve-aka-allen' 的父層標籤
  4. 'receieve-aka-allen' 子層即可取得這些監聽的 $listeners、及傳入的 $attrs
  5. 子層要用 props: ['akaAllen'] ,才能把從父層傳進來的 v-bind="$attrs" 接出來使用,不然通通都會留在 $attrs 裡面,還記得 $attrs 會收集沒通過 prop 驗證的 tag attribute 嗎 ? 不然就是你也可以直接使用 $attrs.elvis or $attrs.elvis.akaAllen , 也 OK 啦。只是寫 props 比較有工序的感覺。
<transfer-attrs-listeners
	aka-allen="aka allen 被傳到了 receieve-aka-allen (子層裡註冊的子組件,紅色框框的部分),要用 props 接出來,把 props 和 {{ akaAllen }} 拿掉,我就會進 $attrs"        
	elvis="elvis 無憂無慮 飄盪著沒被用到,如果把 receieve-aka-allen 內的 props 和 {{ akaAllen }} 拿掉,aka allen就會跑來我這邊了。"
	talfin="talfin在子層"
	@input="app"
	@focus="app2"         
></transfer-attrs-listeners>

in script tag ...

Vue.component('transfer-attrs-listeners', {
  props: ['talfin'],
  components: {
    'receieve-aka-allen': {
      props: ['akaAllen'],
      template: `<div style= "border: solid 1px red;"> 
        akaAllen: {{ akaAllen }} <br><br> 
        $attrs : {{ $attrs }} <br><br>
        <input
          v-on="$listeners"  
        />
      </div>`,
      created () {
        console.log('this.$listeners: ', this.$listeners)
      }
    }
  },
  template: `
    <div style= "border: solid 2px green;">
      <label> {{ talfin }} </label>
      <receieve-aka-allen 
        v-bind="$attrs"
        v-on="$listeners"
      >
      </receieve-aka-allen>
    </div>
  `
})

in Vue 實例

methods: {
  app (e) {
    alert('給我一輛十階坦克車')
    console.log(e)
  },
  app2 (e) {
    console.log(e)
  }
}

https://ithelp.ithome.com.tw/upload/images/20200912/201298190WzCFFBlmb.jpg

  • 以上的案例都在這裡
  • 接下來要解說官網案例,還記得這裡嗎 ?

https://ithelp.ithome.com.tw/upload/images/20200912/20129819jjKpnWcBic.jpg

  • 官網 将原生事件绑定到组件 案例中提供了一個模板,最終目的是要讓我們可以在組件上監聽所有的原生事件而不用 .native ( 當然這是假象,實際上監聽的還是自定義事件 ),以此例子來說,用 .native 是沒有用的,因為此組件雖然看起來有個輸入框,但因為實際上他是包裹在 label tag 內的,除非你把 label 刪掉改成以 input 做為根元素,如此 .native 才有用,label 是沒有 focus 事件可以監聽的,由此例可發現 .native 會直接對應到根元素。
<base-input v-on:focus.native="onFocus"></base-input>

...

let baseInput = {
  inheritAttrs: false,
  props: ['label', 'value'],
	template: `
		<label>
		  {{ label }}
		  <input
            v-bind="$attrs"
		    v-bind:value="value"
		    v-on:input="$emit('input', $event.target.value)"
		  >
		</label>
	`
}

  • 而為了能夠不使用 .native ,官網做了如下改寫,透過上面的前置知識點,影該就能比較清楚這樣改寫的意義。
  1. 首先 inheritAttrs: false 取消退貨給父層的行為,此行為于此組件目前看來是沒什麼作用,畢竟不知道此組件的上下文使用情境,不過因為此組件這樣設定,所以才決定於前置知識點介紹。
  2. props: ['label', 'value'] 沒什麼好說的
  3. 計算屬性內設置了 v-on = "inputListeners" 的 getters,其中關鍵的來了,其實可以回去看一下我們 'receieve-aka-allen' 這個局部組件,裡面的 input v-on="$listeners 就已經達到讓使用者不必寫 .native 的作用了
  • 問題 : v-on="$listeners" 為何可以達到效果 ?
  • 解答 : 文件說明 v-on 支援物件寫法,還記得 $listeners = 父層監聽自定義事件的物件集合,其值皆為預設行為的 function 嗎 ? 所以 $listeners 本身的格式丟進去是完全符合規格的阿,直接丟進去,在父層隨便監聽分分鐘都不需要 .native 了呀。

https://ithelp.ithome.com.tw/upload/images/20200912/20129819htqibndsbt.jpg

而如果你有這樣的需求的話,透過 Object.assign() 在知識點介紹到的後面覆蓋前面的特性,就可對特定的事件做複寫,並且 Object.assign() 返回的是相同的格式,所以也能夠無痛放回去 v-on 。

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` 将所有的对象合并为一个新对象
      return Object.assign({},
        // 我们从父级添加所有的监听器
        this.$listeners,
        // 然后我们添加自定义监听器,
        // 或覆写一些监听器的行为
        {
          // 这里确保组件配合 `v-model` 的工作
          input: function (event) {
			// 如果我沒記錯的話,原本預設發射回去父層的附帶值只有event而已
            vm.$emit('input', event.target.value)
						
          }
        }
      )
    }
  },
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
  `
})

這裡是本章範例

  • 以下是本章參考資料,下一章講命名插槽,有什麼問題都可以留言彼此互相探討。
  1. https://jigsawye.com/2015/10/06/javascript-object-assign/
  2. https://cn.vuejs.org/v2/api/#inheritAttrs
  3. https://cn.vuejs.org/v2/api/#vm-attrs
  4. https://cn.vuejs.org/v2/api/#vm-listeners
  5. https://cn.vuejs.org/v2/api/#v-on

沒事也可以逛逛我們其他團隊成員的文章啦 ~~
eien_zheng: 前端小嘍嘍的Golang學習旅程_The journey of learning Golang 系列
PollyPO技術: 前端設計轉前端工程師-JS踩坑雜記 30 天 系列
阿電: 忍住不打牌位,只要30天VueJS帶你上A牌 系列
喬依司: 實作經典 JavaScript 30 系列


上一篇
Day 03: 組件 Prop + Slot + 仿造 BootstrapVue <b-button>
下一篇
Day 05: 具名插槽 + 作用域 + CLI 前哨站之拆解 BootstrapVue Table cell() 假象
系列文
Vue CLI + Firebase 雲端資料庫 30天打造簡易部落格及後臺管理30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言