Vue全套知識點

HaiTang_xx發表於2020-11-13

Vue全套知識點

Vue

官網介紹它是一個構建使用者介面的漸進式框架 ;
漸進式框架 : 主張最少 , 每個框架都不可避免會有自己的一些特點 , 從而對使用者有一定的要求 , 這些要求就是主張 , 主張有強有弱,它的強勢程度會影響在業務開發中的使用方式 ; 而 Vue 雖然有全家桶套餐 , 但是你可以只用它的一部分 , 而不是用了它的 核心庫 就必須用它的全部 .

宣告式渲染

Vue.js 提供了簡潔的模板語法宣告式的將資料渲染至 DOM 中

<div id="app">
	{{ message }}
</div>

const vm = new Vue({
	el: '#app',
	data: {
		message: 'hello vue'
	}
})
  • el : 元素掛載點;只有在 new 建立例項的時候生效 ; 例項掛載之後可以使用 vm.$el 訪問
  • data : Vue 例項的資料物件 , Vue 會遞迴的將 data 的 property 轉換為 getter 和 setter , 從而讓 data 的 property 能夠響應資料變化 ; 物件必須是純粹的物件 (含有 0 個或者 多個 鍵值對) 瀏覽器 API 建立的物件 , 原型上的 property 會被忽略 , 大概來說 data 只能存在資料 , 不推薦觀察擁有狀態行為的物件 ;
  • {{}}: 插值表示式 ; 官網也稱為 Mustache 語法

為什麼元件中 data 是方法

當一個元件被定義時 (非根元件) data 必須宣告為一個返回物件的函式 , 因為元件可能被用來建立多個例項, 如果 data 仍然是一個物件 , 這樣所有例項講共享引用同一個資料物件 , 通過提供 data 函式 , 每次建立一個例項的時候 , 我們能夠呼叫 data 函式 , 從而返回初始資料的一個全新資料物件

// 錯誤 示例
let options = {
  data: {
    uname: 'zs'
  }
}
function Component(options) {
  this.data = options.data
}

let user1 = new Component(options)
let user2 = new Component(options)

user1.data.uname = 'ls' // 修改 user1  觸發了所有
console.log(user2.data.uname) // ls
// 正確示例
let options = {
  data() {
    return {
      uname: 'zs'
    }
  }
}
function Component(options) {
  this.data = options.data()
}

let user1 = new Component(options)
let user2 = new Component(options)

user1.data.uname = 'ls' 
console.log(user2.data.uname) // zs
console.log(user1.data.uname) // ls

由於元件是可以多次複用的 , 如果不使用 function return 每個元件的 data 在記憶體中都是指向同一個地址的 , 由於 JavaScript 複雜資料型別的特性 , 那一個資料改變其他的也改變了 , 但是如果用了 function return 其實就相當於申明瞭新變數 , 相互獨立 , 自然就不存在以上例子中存在的問題 ; JavaScript 在賦值 Object 時 , 是直接一個相同的記憶體地址 , 所以為了每個元件的獨立 , 採用了這種方式 ; 但由於根元件只有一個 , 不存在資料汙染的情況 , 所以就可以是一個物件 ;

指令

v-cloak

這個指令可以配合著 CSS 隱藏未編譯的 Mustache 標籤 , 直到例項準備完畢

問題展示 :

在這裡插入圖片描述

/* css */
[v-cloak] { display: none; }
<div v-cloak>{{ root }}</div>

v-text

更新某個元素節點下的值 ; 注意 : 會更新全部內容 , 如果想要區域性更新 , 可以使用 Mustache 語法

<div v-text="root"></div>

v-html

更新元素的 innerHTML 注意 : 普通 html 內容
在網站上使用 HTML 是非常危險的 , 容易導致 XSS 工具 , 使用者提交內容時 切記勿要使用

<div v-html="html"></div>
new Vue({
  el: '#app',
  data: {
    html: '<p>hello vue</p>'
  }
})

v-pre

原文輸出 , 不會參與編譯 , 輸入什麼內容就展示什麼內容

<div v-pre>{{ will not compile }}</div>

v-once

被定義了 v-once 指令的元素或者元件 (包括元素或元件內的子孫節點) 只能被渲染一次 , 首次渲染收 , 即時資料發生變化 , 也不會被重新渲染 , 一般用於靜態內容展示 ;

<div v-once>{{ content }}</div>
const vm = new Vue({
  el: '#app',
  data: {
    content: 'this is init data'
  }
})
vm.content = 'update data'

v-showv-if

這裡的 v-if 不單單是這一個指令 , 它包含 v-else-if v-else 功能差不多 , 這裡就統一解釋了
v-show : 根據表示式的真假值 , 判斷元素是否隱藏 ( 切換元素的 display : block/none )
v-if : 根據表示式的值來有條件的渲染資料 , 在切換時元素以及它的資料繫結 / 元件被銷燬並重建

差異 :
在這裡插入圖片描述

<div v-if="isShow"> v-if </div>
<div v-show="isShow"> v-show </div>

分支判斷程式碼演示

<!-- 最終一會展示一個 p 標籤中的內容  -->
<div>
  <p v-if="score > 90"> 
  	<span>成績優異 : {{ score }}</span>
  </p>
  <p v-else-if="score > 70"> 
  	<span>成績及格 : {{ score }}</span>
  </p>
  <p v-else> 
  	<span>不及格 : {{ score }}</span>
  </p>
</div>

v-for

v-for 中被迴圈的物件 , 必須是一個可迭代物件iterable ( Array | Number | Object | String … )
語法格式為 alias in expression 其中的 in 也可以使用 of 替代
可以為陣列或者物件增加索引值

<!-- 陣列迴圈 -->
<div v-for="(item, index) in items">
  {{ item.text }}
</div>
<!-- 物件迴圈 -->
<div v-for="(val, key, index) in object">
  {{ val }} {{ key }} {{ index }}
</div>

為什麼 v-for 必須新增唯一 key

當 Vue 正在更新使用 v-for 渲染的資料列表時 , 它預設使用 就地更新 策略 , 如果資料項的順序被改變 , Vue 將不會移動 DOM 來匹配資料項的資料 , 而是就地更新每個元素 , 保證它們在每個索引位置的正確渲染 ;

為什麼要加 key

  • 為了給 Vue 一個提示 , 以便它跟蹤每個節點的身份 , 從而重用 和 重新排序現有元素 需要為每一項提供一個唯一 key
  • key 主要用在 Vue 的 虛擬 DOM 演算法 , 在新舊節點對比時 , 辨識虛擬DOM , 如果不使用 key 會使用一種最大限度減少動態元素並且儘可能的嘗試就地修改/複用相同型別元素的演算法 , 而如果使用了key 它會基於 key 的變化重新排列元素順序 , 並且會移除 key 不存在的元素

為什麼不能用 indexkey

// 元件資料定義
const vm = new Vue({
  el: '#app',
  data: {
    users: [
      { id: 1, uname: 'zs', age: 23 },
	     { id: 2, uname: 'ls', age: 24 },
	     { id: 3, uname: 'we', age: 25 },
	     { id: 4, uname: 'mz', age: 26 },
	  ]
  }
})

index 錯誤示例

重點在於上面我們所說的會基於 Key 的變化重新排列元素順序 , 可以看出如果我們用 index 作為 key 陣列翻轉的時候 , 其實 Key 的順序是沒有變的 , 但是傳入的值完全變了 , 這時候原本不一樣的資料 , 被誤以為一樣了 , 所以就造成以下問題 ;

在這裡插入圖片描述

<!-- 具體語法稍後介紹; 意思為點選翻轉陣列 -->
<button @click="users.reverse()">年齡排序</button>
<ul>
  <!-- 迴圈這個 users 生成一個資料列表 並且裡面帶有 多選框 以供我們測試 -->
  <li v-for="(user, index) of users" :key="index">
    <input type="checkbox" />
    <span>{{ user.uname }}</span>
  </li>
</ul>

唯一 Id 正確示例

此時的 key 和資料做繫結 , 當你翻轉陣列的時候 , 繫結的其實是這一條資料 , 而不是索引 , 就不會造成以上問題了

在這裡插入圖片描述

<li v-for="(user, index) of users" :key="user.id">
  ......
</li>

v-bind

// 繫結 attrbute
<div v-bind:content="message"></div>

// 繫結 class 
<div :class="{box: isBox}"></div>
<div :class="['box', 'box1']"></div>
<div :class="['box', {box1: isBox}]"></div>

// 繫結 style
<div :style="{fontSize: '20px', color: 'white'}"></div>
<div :style="[{fontSize: '20px'}, {color: 'white'}]"></div>

v-on

事件繫結 ; 可縮寫 @ 監聽DOM事件 , 並在觸發時執行一些 js 程式碼

<button v-on:click="count += 1"></button>
{{ count }}

可以接收一個方法名稱 ;
注意 : 當只是一個方法名稱時, 預設第一個引數為事件物件 e
當需要傳入引數時 , 那麼事件物件就需要手動的傳入, 最後一個 並且強制寫成 $event

<button @click="handle">點選1</button>
<button @click="handle1('content', $event)">點選2</button>
methods: {
  handle(e) {
    console.log(e.target)
  },
  handle1(ct, e) {
  	console.log(ct)
    console.log(e.target)
  }
}

事件修飾符

  • .prevent : 阻止預設事件
  • .stop : 阻止冒泡
  • .self : 只有當前元素觸發事件
  • .once : 只觸發一次該事件
  • .native : 監聽元件根元素的原生事件
// 定義子元件
Vue.component('my-component', {
  template: `
    <button @mousedown="handle" :style="{color: 'white', lineHeight: '20px', backgroundColor: 'black'}">元件</button>
  `,
  methods: {
    handle() {
      console.log('///')
    }
  }
})

加了 native 相當於把自定義元件看成了 html 可以直接在上面監聽原生事件, 否則自定義元件上面繫結的就是自定義事件 , 而你在自定義事件上沒有定義這個事件 , 所以不加 native 不會執行

// 父元件中引用
<my-component @click.native="handle('父元件')"></my-component>
  • .capture : 新增事件監聽時 , 使用 捕獲模式
// 此時會優先捕獲 box1
<div class="box" style="background: skyblue;  width: 180px;" @click.capture="handle('box1')">
  <div class="box1 box" style="background: slateblue; width: 140px;" @click="handle('box2')">
    <div class="box2 box" style="background: red;" @click="handle('box3')"></div>
  </div>
</div>
// 允許只有修飾符 prevent 阻止預設事件
<a href="http://www.baidu.com" @click.prevent >百度</a>

// 多個事件修飾符可以連用觸發時機也是相同的
<a href="http://www.baidu.com" @click.prevent.stop="handle('a')">baidu</a>

按鍵修飾符

//Vue 中允許為 v-on 監聽鍵盤事件時新增鍵盤修飾符
<input v-on:keyup.enter="submit">
當然提供了大多數的按鍵碼別名 按鍵碼
還可以通過全域性 Vue.config.keyCodes 自定義修飾符別名
Vue.config.keyCodes.f1 = 112

v-model

在表單元素上建立資料雙向繫結 , 它會根據控制元件型別自動選取正確的值來更新元素

// 文字
<input type="text" v-model="message">
<p>{{ message }}</p>
// 多行文字
<textarea cols="30" rows="10" v-model="message"></textarea>
<p>{{ message }}</p>
// 單選框
<input type="radio" value="男" v-model="sex">男
<input type="radio" value="女" v-model="sex">女
<p>{{ sex }}</p>
// 單個核取方塊
<input type="checkbox" v-model="checked">
<p>{{ checked }}</p>
// 多個核取方塊
<input type="checkbox" value="打籃球" v-model="hobby"/>打籃球
<input type="checkbox" value="打皮球" v-model="hobby"/>打皮球
<input type="checkbox" value="打氣球" v-model="hobby"/>打氣球
<input type="checkbox" value="打棒球" v-model="hobby"/>打棒球
<p>{{ hobby }}</p>
// 選擇框 -> 單選
<select v-model="selected">
  <option>javascript</option>
  <option>html</option>
  <option>css</option>
</select>
<p>{{ selected }}</p>
// 選擇框 -> 多選
<select v-model="selectList" multiple>
  <option>javascript</option>
  <option>html</option>
  <option>css</option>
</select>
<p>{{ selectList }}</p>
// 例項物件
new Vue({
  el: '#app',
  data: {
    message: '', // 多行, 單行文字
    sex: '', // 單選框
    checked: false, // 核取方塊單個
    hobby: [], // 核取方塊多個
    selected: '', // 選擇框 -> 單個
    selectList: [] // 選擇框 -> 多個
  }
})

修飾符

  • .lazy : 預設情況下 v-model在每次的 input 事件觸發後將輸入框內容進行同步 , 新增 lazy 修飾符後 , 會變成 change 事件後同步資料
<input v-model.lazy="message">
  • .number : 使用者輸入的值轉為數值型別
<input v-model.number="age">
  • .trim : 過濾輸入框中的左右空白
<input v-model.trim="message">

Vue.set

如果在例項建立之後新增新的屬性到例項上 , 它不會觸發更新檢視 怎麼理解呢 ?

data() {
  return {
    info: {
      uname: 'zs'
    }
  }
}
mounted() {
  // 此時是不會生效的 , 如果再模組化的開發中還會報錯
  this.info.age = 23
}

ES5 的限制 , Vue 不能檢測到物件屬性的新增或者刪除 , 因為 Vue 在初始化的時候將屬性轉換為getter setter 所以屬性必須要在 data 物件上才能讓 Vue 轉換 , 只有在 data 物件上 才是響應式的

mounted() {
  // 正確寫法
  this.$set(this.info, 'age', 23)
}

Vue.set() : 與 this.$set沒有區別, 一個全域性 一個區域性 官網說 this.$setVue.set的一個別名

methods

methods 將會被混入到Vue 例項中 , 可以直接通過 vm 例項訪問這些方法 , 或者在指令表示式中使用 , 方法中的this自動繫結 Vue 例項
注意 : methods 中的 方法 不要使用 箭頭函式 , 箭頭函式中的 this指向父級作用域的上下文 , 所以this 將不會指向 Vue 例項

new Vue({
  methods: {
    handle() {
      console.log(this)
    }
  }
})

計算屬性 computed

模板內寫表示式固然是很方便的 , 但是你應該明白 , 表示式的初衷是用來計算的 , 比如處理一些字串 , 時間格式等等 , 如果我們寫成方法吧 ! 每次都要去呼叫 , 那就太麻煩了 , 為此 Vue 提供了 計算屬性 computed

可以看出每次我們都去呼叫這個引數 , 感覺很不方便

<input type="text" v-model.number="input1"/> + 
<input type="text" v-model.number="input2"/> =
<span>{{ getSum() }}</span>
data() {
  return {
    sum: '',
    input1: '',
    input2: ''
  }
},
methods: {
  getSum() {
    return this.sum = this.input1 + this.input2
  }
}

下面我們使用計算屬性解決 ; 可以看出我們去除了 data 中的 sum 屬性 在 computed 中新增了 getSum 函式

<input type="text" v-model.number="input1"/> + 
<input type="text" v-model.number="input2"/> =
<span>{{ getSum }}</span>
data() {
  return {
    input1: '',
    input2: ''
  }
},
computed: {
  getSum() {
    return this.input1 + this.input2
  }
},

那麼問題來了 為什麼定義了一個函式, 卻當成屬性執行 ? 其實這個只是簡寫而已 , 算是一個語法糖 , 每一個計算屬性包含 get 和 set 當只有 get 時可以簡寫為 函式的格式

export default {
  computed: {
    getSum: {
      get() {
       // 獲取資料
      },
      set(val) {
        // val 是這個計算屬性被修改之後的資料  設定資料
      }
    }
  }
}

示例 : 看完這個例子就明白了為什麼叫 計算屬性了吧

<input type="text" v-model.number="input1"/> + 
<input type="text" v-model.number="input2"/> =
<span>{{ getSum }}</span>
<!-- set 函式可以接收這裡傳遞過來的值 -->
<button @click="getSum = '未知數'">修改 getSum</button>
computed: {
  getSum: {
    get() {
      return this.input1  + this.input2
    },
    // 接收 getSum 這個屬性改變後的值
    set(val) {
      console.log(val)
      this.input1 = 20
      this.input2 = 30
    }
  }
},

偵聽屬性 watch

雖然計算屬性在大多數情況下都適用 , 但有時也需要一個自定義的偵聽器 , 這個時候就需要 偵聽屬性 watch

仍然是計算兩數之和 ; 在 watch 監聽了 input1 的屬性 input1 觸發時 求出 sum ; 仔細看已經出現了問題, 修改 input2 的時候就不會再觸發了 ;

總結 : 它監聽 data 某一個屬性的變化 , 並不會創造新的屬性

<input type="text" v-model.number="input1"/> + 
<input type="text" v-model.number="input2"/> =
<span>{{ getSum }}</span>
data() {
  return {
    input1: '',
    input2: '',
    getSum: ''
  }
},
watch: {
  // 這樣寫看著是一個函式, 和屬性理解不一致, 當然還可以寫成這樣
  input1(newVal, oldVal) {
    console.log(newVal, oldVal)
    this.getSum = this.input1 + this.input2
  }
  input2: {
    // 回撥函式監聽 input2 的變化  函式名必須是 handler
    handler(newVal, oldVal) {
      console.log(newVal, oldVal)
      this.getSum = this.input1 + this.input2
    }
  }
}

如果我們需要偵聽物件屬性, 可以在選項引數中使用 deep: true 注意監聽資料的變更不需要這麼做

watch: {
  obj: {
    handler() {
      // ....
    },
    deep: true 
  }
}

watch 使用時有一個特點 , 就是當值第一次繫結的時候 , 不會執行監聽函式 , 只有值發生改變時才會執行 , 如果我們需要在最初繫結值的時候也執行函式 , 則需要用到immediate: true

methods computed watch 區別

  • watch 就是單純的監聽某個資料的變化 , 支援深度監聽 , 接收兩個引數一個最新值, 一個變化前的舊值 , 結果不會被快取 , 並且 watch 可以處理非同步任務

  • computed 是計算屬性, 依賴於某個或者某些屬性值 , 計算出來的結果會出現快取 , 只有當資料的依賴項變化時才會發生變化 , 會建立一個新的屬性

  • methods 是函式呼叫 , 沒有快取 , 主要處理一些業務邏輯, 而不是監聽或者計算一些屬性

過濾器 filter

可以被用於一些常見的文字格式化 , 允許被應用在兩個地方 {{}} v-bind

{{ msg | formatMsg }}
<div v-bind:msg="msg | formatMsg"></div>
  • 可以在元件的選項中定義元件內私有的過濾器
Vue.component('son-component', {
  template: `
    <div>{{ msg | formatMsg }}</div>
  `,
  data() {
    return {
      msg: 'this is message'
    }
  },
  filters: {
    formatMsg(msg) {
      return msg.toString().toUpperCase()
    }
  }
})
  • 可以在建立 Vue 例項之前定義全域性過濾器
Vue.filter('formatMsg', function(msg) {
  return msg.toString().toUpperCase()
})
  • 過濾器預設是以 |前面的的內容作為過濾器的第一個引數 , 還可以再次傳入傳輸
<div>{{ msg | formatMsg('lower') }}</div>
Vue.filter('formatMsg', function(msg, args) {
  console.log(msg) // lower
  if (args === 'lower') {
    return msg.toString().toLowerCase()
  }
})

自定義指令 directive

與上面提到的指令一致 , 如果那些指令不能滿足使用要求 , 可以自己進行定製

自定獲取焦點案例

<input type="text" v-focus/>
// 全域性指令 定義時不需要 v-  呼叫時要加上 v- 字首
Vue.directive('focus', {
  inserted(el) {
    el.focus()
  }
})
// 或者可以定義為區域性
directives: {
  'focus': {
    inserted(el) {
      el.focus()
    }
  }
}

鉤子函式

  • bind :只呼叫一次 , 指令第一次繫結元素時呼叫 , 在這裡可以進行一次性的初始化設定 ;
  • inserted : 被繫結元素插入父節點時呼叫 , 不一定渲染完成 , html 已經建立好了
  • update :所在元件的 VNode 更新時呼叫
  • componentUpdated : 指令所在的元件的 VNode 全部更新完成後
  • unbind : 指令與元素解綁時呼叫

鉤子函式引數

  • el :指令所繫結的元素 , 可以直接操作 DOM
  • binding : 指令相關的配置物件
    • modifiers : 一個包含修飾符的物件 示例v-drag.limit
    • name :指令名 , 不包含字首
    • value :指令繫結的值 v-drag="true"
<div v-drag>
// 拖拽方塊案例
Vue.directive('drag', {
  // 初始化樣式
  bind(el) {
    el.style.position = 'absolute'
    el.style.top = 0
    el.style.left = 0
    el.style.width = '100px'
    el.style.height = '100px'
    el.style.background = 'skyblue'
    el.style.cursor = 'pointer'
  },
  // 元素物件存在後, 開始寫拖動邏輯
  inserted(el, binding) {
    let draging = false
    let elLeft = 0
    let elRight = 0
    
    document.addEventListener('mousedown', function (e) {
      draging = true
      let move = el.getBoundingClientRect()
      elLeft = e.clientX - move.left
      elRight = e.clientY - move.top  
    })
    document.addEventListener('mousemove', function (e) {
      let moveX = e.clientX - elLeft
      let moveY = e.clientY - elRight
      
      if (draging) {
        el.style.left = moveX + 'px'
        el.style.top = moveY + 'px'
      }
    })
    document.addEventListener('mouseup', function () {
      draging = false
    })
  }
})

自定義指令修飾符

相信大家仔細看上面的程式碼可能會發現這個方格拖拽還存在一些問題 ; 它還是可以拖拽到可視區域之外的 , 那麼可不可以傳遞一個修飾符 , 來告訴他呢 ? 這時候就需要用到 binding 這個指令配置相關的物件了

// 我們先傳入修飾符 limit 為自己定義的修飾符
<div v-drag.limit>
// 既然不想讓他拖拽出視口, 那麼就應該在滑鼠移動的時候加入一些邏輯
document.addEventListener('mousemove', function (e) {
  let moveX = e.clientX - elLeft
  let moveY = e.clientY - elRight
  
  // 是否傳入了修飾符 limit 為什麼這樣可以獲取 下面就上截圖
  if (binding.modifiers.limit) {
    moveX = moveX <= 0 ? moveX = 0 : moveX   
    moveY = moveY <= 0 ? moveY = 0 : moveY
  }
  if (draging) {
    el.style.left = moveX + 'px'
    el.style.top = moveY + 'px'
  }
  console.log(binding) // binding 物件
})

自定義指令傳參

上面我們已經解決了拖出視口的問題 , 只要傳遞一個修飾符就解決了 , 那麼現在我們希望可以手動的暫停拖拽 , 當然也是可行的 ;

<div v-drag.limit="{isDrag: false}">
document.addEventListener('mousemove', function (e) {
  let moveX = e.clientX - elLeft
  let moveY = e.clientY - elRight
  
  // 是否傳入了修飾符 limit
  if (binding.modifiers.limit) {
    moveX = moveX <= 0 ? moveX = 0 : moveX   
    moveY = moveY <= 0 ? moveY = 0 : moveY
  }
  // 是否傳入 isDrag 判斷是否可滑動
  if (!binding.value.isDrag) return
  if (draging) {
    el.style.left = moveX + 'px'
    el.style.top = moveY + 'px'
  }
})

元件

通常一個元件會以一棵巢狀的元件數的形式來組織 ; 為了能在模板中使用 , 這些元件必須先註冊以便 vue 能夠識別 ;

  • 全域性元件
Vue.component('GlobalComponent', {
  template: `<div> hello component </div>`
})
// 命名時推薦駝峰 , 呼叫時推薦 - 連結, html 不識別大小寫
<global-component></global-component>
  • 區域性元件
new Vue({
  el: '#app',
  components: {
    SonComponent: {
      template: `<div>hello private component</div>`
    }
  }
})
// 元件可以被複用多次
<private-component></private-component>
<private-component></private-component>
<private-component></private-component>
  • 模組化開發中的元件
import SonComponent from '@/components/SonComponent.vue'

export default {
  components: {
    SonComponent
  }
}
<son-component></son-component>

通過 props 向子元件傳遞資料

prop 是元件上一些自定義的 attribute , 當一個值傳遞給一個 prop attribute 的時候 , 它就變成那個元件例項的 property ;

// 父元件
<div>
  <son-component content="傳遞給子元件的資料, 如果動態傳值可以加 v-bind"></son-component>
</div>
// 子元件
Vue.component('SonComponent', {
   // 多個單詞可以是駝峰式, 但是父元件傳遞時多個單詞必須是 - 連線
   // props 中的值, 可以像 data 中的資料一樣訪問  this.content / {{ content }} 
   // props 是隻讀的 切記不要修改 會報錯
   props: ['content'],
   template: `<div> {{ content }} </div>`
})

props 可以是陣列也可以是一個物件 , 用來接收來自父元件的資料 ;
物件允許配置高階選項 , 如型別檢測等

  • type : 可以是 Number String Boolean Array Object Date Function 任何自定義建構函式 , 或上述內容組成的陣列 , 會檢查一個 prop 是否是給定的型別 , 否則丟擲異常

  • default : 預設值 , 物件或者陣列的預設值必須從一個工廠函式中返回

  • required : boolean 是否為必填項

  • validator : Function 自定義驗證函式會將 prop 的值作為唯一的引數傳入

props: {
  content: {
    type: String,
    // default: 0, 普通值可直接預設返還
    default: () => [1, 2, 3],
    required: true,
  	 // 如果傳進來的 content 長度大於 20 就會報錯
    validator: (value) => value.length >= 20
  }
}

監聽子元件事件 $emit

有些時候 , 父元件需要用的子元件中特定的值時 , 可以使用 $emit 把這個值傳遞出去

  • 行內模式傳值
// 子元件
<template>
  <div class="son">
    // $emit 第一個引數自定義事件, 第二個及以後是傳遞的資料
    <button @click="$emit(son-com, [1, 2, 3])"></button>
  </div>
</template>
// 父元件
// 監聽子元件定義的自定義事件 , 通過 $event 訪問第一個傳遞的引數
<son-component @son-com="msg = $event"></son-component>
  • 事件處理函式傳值
// 子元件
<template>
  <div class="son">
    <button @click="sonHandle"></button>
  </div>
</template>
<script>
export default {
  methods: {
    sonHandle() {
      this.$emit('son-com', '需要傳遞的值')
    }
  }
}
</script>
// 父元件
<template>
  <div class="parent">
    <son-component @son-com="parentHandle"></son-component>
    // 顯示傳入其他引數的話 必須使用 $event 接收子元件傳遞過來的值
    <son-component @son-com="parent('顯示傳入引數', $event)"></son-component>
  </div>
</template>
<script>
export default {
  methods: {
    // 預設第一個值就是傳遞過來的引數
    parentHandle(arg) {
      	console.log(arg) // 需要傳遞的值
    },
    // 對應傳入引數的位置
    parent(params, arg) {
      console.log(params) // 顯示傳入引數
      console.log(arg) // 需要傳遞的值
    }
  }
}
</script>
  • 事件處理函式傳遞多個值
// 子元件
<template>
  <div class="son">
    <button @click="sonHandle"></button>
  </div>
</template>
<script>
export default {
  methods: {
    sonHandle(event) {
      // 事件物件可以在任意位置 , 放到前面相對比較好接收
      this.$emit('son-com', event, '需要傳遞的值', '需要傳遞的第二個值')
    }
  }
}
</script>
// 父元件
<template>
  <div class="son">
    <button @son-com="parent"></button>
  </div>
</template>
<script>
export default {
  methods: {
    parent(event, ...args) {
      console.log(event)
      // 如果不想使用剩餘引數, 也可以多傳遞引數逐個使用
      console.log(args)
    }
  }
}
</script>

元件上使用v-model

在使用這個功能之前我們需要先了解一個東西 , v-model究竟是什麼 ; 其實它從某種程度來說就是一個語法糖

<input type="text" v-model="msg"/>
<input :value="msg" @input="msg = $event.target.value"/>

應用到元件中就是下面這樣 為了不引起歧義, 我把自定義的事件以及屬性加了test 字首詳情看VUE官方文件

// 父元件
<model-input 
  :test-value="searchText" 
  @test-input="searchText = $event"
  >
</model-input> 
  • 子元件的 result 必須繫結到 value 上面
  • 在這個 input 觸發的時候 通過 $emit 將自定義的 test-input 在暴露出去
// 子元件
<input
  type="text"
  v-bind:value="testValue"
  @input="$emit('test-input', $event.target.value)"
>
// script
props: ['testValue'] // 自定義屬性傳遞過來的值

此時我們再優化一下 , 使用 v-model

// 父元件
<model-input v-model="searchText"></model-input> 

由於我們元件中使用了 v-model 而前面我們也提到了 v-model 其實是 v-bind 和 v-on 的語法糖 , 所以只能用 value 屬性和 input 事件

// 子元件
<input
  type="text"
  v-bind:value="value"
  @input="$emit('input', $event.target.value)"
>
// script
props: ['value'] 

那麼問題來了 , 上面我們提過 v-model 預設是 value屬性 和 input事件 , 但是像單選框 , 核取方塊等型別怎麼處理 ? 對此 Vue 提供了model 選項來避免這樣的衝突

單個核取方塊元件繫結

// 父元件
<model-input v-model="isChecked"></model-input>

// script
data() {
  return {
    isChecked: false
  }
},

選中 和 未選中 返回 true / false

// 子元件
<input
  type="checkbox"
  v-bind:checked="checked"
  @change="$emit('change', $event.target.checked)"
>
// script

export default {
  name: 'ModelInput',
  // v-model 拆分
  model: {
    prop: 'checked', // 將傳進來的 isChecked 變成 checked 供後面的 props 使用
    event: 'change' //  定義 emit 自定義的事件名字
  },
  props: {
    checked: {
      type: Boolean
    }
  }
}

元件插槽

在 2.6.0 中 為具名插槽和作用域插槽提供了新的統一語法 v-slot 它取代了 slotslot-scope 這兩個目前已被廢棄 , 但是還沒有移除 (仍然可以使用)
插槽 : 簡單理解就是 佔坑 在元件模板中佔好位置 , 當使用該元件的標籤時 , 元件標籤的內容就會自動填坑 , ( 替換元件模板中的 slot位置 ) , 並且可以作為承載分發內容的出口

內容插槽

// 子元件
<template>
  <div>
    <p>這是元件的頭部</p>
    <slot></slot>
    <p>這是元件的尾部</p>
  </div>
</template>
// 父元件
<<template>
  <div>
    <!-- 插槽內可以是任何內容 元件,文字,標籤-->
    <slot-test>
      <p>這是插槽的內容</p>
    </slot-test>
  </div>
</template>

規則 : 父級模板裡的所有內容都是在父級作用域中編譯的 ; 子模板的所有內容都在子作用域中編譯

預設內容插槽

<slot> 標籤內可以加入 元件, 文字, 標籤等預設內容 , 如果父元件呼叫時, 沒有傳入內容, 那麼就會展示預設的內容

// 子元件
<template>
  <div>
    <p>這是元件的頭部</p>
    <slot>我是預設內容</slot>
    <p>這是元件的尾部</p>
  </div>
</template>

具名插槽

有些時候一個插槽是不能滿足需求的 , 我們可能需要多個 ; 對於這種情況 , <slot>元素中有一個特殊的 attribute name這個 attribute 用來定義額外的插槽

// 子元件
<template>
  <div>
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <!-- 如果沒有指定 name 預設的 name 為default -->
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

在向具名插槽提供內容的時候 , 可以在一個 template 元素中上 使用v-slot指令 並以引數的形式提供名稱

// 父元件
<template>
  <div>
	<slot-test>
	  <template v-slot:header> 我是 header 內容 </template>
      我是 沒有指定 name 的內容 <!-- 或者也可以寫成下面的內容 -->
      <template v-slot:default> 沒有指定 name 的內容 </template>
      <template v-slot:footer> 我是 footer 內容 </template>
	</slot-test>
  </div>
</template>

<template>中的所有內容都會傳入響應的插槽 , 任何沒有包裹在帶有 v-slot 的 <template>中的內容都會被視為預設插槽的內容 ; 通俗一點來說 , 就是我在子元件中宣告瞭多個 沒有 name 的<slot> , 那麼 我在父元件中 , 只需要渲染一次 , 所有子元件的插槽就都會被渲染 ;
注意 : v-slot 只能新增在 <template> 上 , 還有一種特殊情況後面會說

具名插槽縮寫

v-slot:替換為 #
注意 : 只有帶引數時可以使用 , 其他情況下是無效的

// 子元件
<div>
  <slot></slot>
</div>
/* 父元件 */

// 錯誤示例
<slot-test>
  <template #>
    <!-- 內容。。。。-->
  </template>
</slot-test>

// 正確示例
<slot-test>
  <template #default>
    <!-- 內容。。。。-->
  </template>
</slot-test>

作用域插槽

從某種意義上來說 , 插槽是子元件高可用 , 高定製化的一種手段 , 那麼我們肯定會碰到插槽內容 , 需要訪問子元件中資料的情況 ;
我們可以通過v-bind把需要傳遞的值 繫結到 <slot> 上 , 然後在 父元件中 使用 v-slot 設定一個值來定義提供插槽的名字

// 子元件
<template>
  <div>
	<slot name="userInfo" :user="userInfo"></slot>
  </div>
</template>
<script>
export default {
  data() {
    return {
      userInfo: {
        firstName: 'firstName',
        lastName: 'lastName'
      }
    }
  }
}
</script>
// 父元件
<template>
  <div>
    <slot-test>
      <!-- 如果只有一個插槽可以吧 v-slot 寫到 元件上面 具體看下面-->
      <template v-slot:userInfo="slotProps">
        {{ slotProps.user.firstName }}
      </template>
    </slot-test>
  </div>
</template>

獨佔預設插槽縮寫語法

當被提供的內容只有預設插槽時 , 元件的標籤才可以當做插槽的模板使用 , 這樣我們就可以把v-slot直接用在元件上

// 子元件
<div>
  <slot :user="userInfo"></slot>
</div>

// 元件資料
data(){
  return {
    userInfo: {
      firstName: 'firstName',
      lastName: 'lastName'
    }
  }
}
// 父元件
<div>
  <slot-test v-slot="slotProps">
    {{ slotProps.user.firstName }}
  </slot-test>
</div>
  • 不要嘗試插槽的 “縮寫語法” 和 “具名插槽混用” 會導致作用域不明確
  • 只要出現多個插槽 , 需要始終為所有的插槽使用<template>語法

解構插槽 Prop

插槽支援通過 ES6 結構傳入具體的插槽 prop 至於原理 ;
官網說 : 作用域插槽內部原理是將你的插槽內容包括在一個傳入單個引數的函式裡 ; 沒有聽懂 ? 自身檔次還不夠, 還是看看怎麼用的吧

<div>
  <slot-test v-slot="{ user }">
    {{ user.firstName }}
  </slot-test>
</div>

在提供多個 prop 的時候 ,它同樣開啟了 prop 重新命名的功能

<div>
  <slot-test v-slot="{ user: preson }">
    {{ preson.firstName }}
  </slot-test>
</div>

插槽其他示例

插槽 prop 允許我們將插槽轉換為可複用的模板 , 這些模板可以基於輸入不同的 prop 渲染出不同的內容;
比如我們設計一個 <todo-list> 元件 ,它是一個列表且包含一定的邏輯

// 子元件
<template>
  <ul>
    <li v-for="item in list" :key="item.id">
      <!-- 這裡把控制邏輯交出去,由父元件去控制一定的邏輯 -->
      <slot name="todo" :item="item">
        {{ item.uname }}
      </slot>
    </li>
  </ul>
</template>
// 資料
[
  {id: 1, uname: 'zs'},
  {id: 2, uname: 'ls'},
  {id: 3, uname: 'we'},
  {id: 4, uname: 'mz'},
]
// 父元件
<test v-slot:todo="slotProps">
  <!-- 本來是顯示名字的改成了顯示id -->
  {{ slotProps.item.id }}
</test>

動態元件 component

在說動態元件之前, 先動手實現一個 tab 分頁,不然貌似不是很好理解 ?

// 模板
<div>
  <button @click="handle('post')" :style="{background: !isShow ? 'red' : ''}">post</button>
  <button @click="handle('list')" :style="{background: isShow ? 'red' : ''}">list</button>
  <item-post v-show="!isShow"></item-post>
  <item-list v-show="isShow"></item-list>
</div>

// script
import ItemPost from '@/components/ItemPost.vue'
import ItemList from '@/components/ItemList.vue'

export default {
  name: 'Home',
  components: {
    ItemPost,
    ItemList
  },
  data() {
    return {
      isShow: true
    }
  },
  methods: {
    handle(title) {
      title === 'list' ? this.isShow = true : this.isShow = false
    }
  }
}

可以看出上面我們寫了兩個小元件,做了一個簡易的 tab 選項卡 ;
重點可以看我們引入元件的方式;就是簡單的在模板中應用;常規操做;現在只引入兩個標籤還好,那如果三五十來個就顯得有點麻煩了

動態元件應用

有了動態元件之後, 類似這種簡易的 tab 切換我們就不需要再模板中使用這麼多的元件標籤了

看程式碼示例, 我們只需要 template 中引入元件的部分 以及在 script 中的 data 新增一個屬性

<!-- 每次點選 button 的時候觸發 handle事件函式時 is繫結的值會重新渲染元件 -->
<component :is="showWhat"></component>
data() {
  return {
    showWhat: 'ItemPost',
    isShow: true
  }
},
methods: {
  handle(title) {
    // 這裡同時控制了切換之後 active 的樣式,所以就不刪了
    title === 'list' ? this.isShow = true : this.isShow = false
    // 加一層判斷切換邏輯
    title === 'list' ? this.showWhat = 'ItemList' : this.showWhat = 'ItemPost'
  }
}

這裡就出現了一些問題, 雖然我們完成了 元件之間的切換,但是關於 input 的選中狀態我們沒有保留下來,原因是每次切換的時候,Vue 都會建立一個新的 showWhat例項 重新建立動態元件的行為通常來說還是非常有用的,但是在我們這個案例中,更希望它能夠保留下來 為了解決這個問題,Vue 中提供了一個內建元素 <keep-alive> 只需要將該元素把元件包裹起來即可

<!-- 失活的 tab 將會被快取 -->
<keep-alive>  
  <component :is="showWhat"></component>
</keep-alive>

非同步元件

在大型應用中, 我們可能需要將應用中分割成小一些的程式碼塊,並且只在需要的時候,再次載入這個模組;道理和 webpack 的按需載入是一樣的;

這裡先建立一個簡單的元件

<template>
  <div>這是一個簡單的元件</div>
</template>

然後在主元件內常規引入看下效果

<template>
  <div>
    <dynamic></dynamic>
  </div>
</template>
<script>
import Dynamic from '@/component/Dynamic.vue'

export default {
  name: 'Home',
  components: {
    Dynamic
  }
}
</script>

非同步元件示例

根據上面的例子,我們修改一下 script 部分

export default {
  components: {
    // script 頭部的匯入, 直接在元件內匯入
    Dynamic: () => import Dynamic from '@/component/Dynamic.vue'
  }
}

可以看出下圖多出了0.js 由此可見 vue-cli 幫助我們分開打包了檔案;這個檔案在被載入之後,就會被快取起來;這個例子只是看出了它被拆開了,並不能證明我們們開始說的按需渲染的道理;基於這個例子可以再改一下,加一個判斷邏輯

<template>
  <div>
    // 就是增加一個按鈕,點選控制,這裡就給大家簡化了
    <dynamic  v-if="isShow"></dynamic>
  </div>
</template>

訪問子元件例項或子元素 ref

儘管存在 prop 和事件 , 有的時候你扔可能需要再 JavaScript 中直接訪問一個子元件 , 為了達到這個目的你可以使用 ref 這個屬性為子元件賦予一個 id 引用

// 模板
<template>
  <div>
    <item-list ref="itemRef"></item-list>
    <input type="text" ref="inputRef"/>
  </div>
</template>
// script
export default {
  mounted() {
    // 子元件的例項物件
    console.log(this.$refs.itemRef)
    // 元素的例項物件, 可以操作 dom
    console.log(this.$refs.inputRef)
  }
}

如果 ref 和 v-for 一起使用的話 , 那麼 this.refs將會得到一個元素陣列

<ul>
  <li v-for="user of userList" :key="user.id" :style="{listStyle: 'none'}" ref="forRef">
    <input type="checkbox" />
    {{ user.id }} --- {{ user.uname }} --- {{ user.age }}
  </li>
</ul>

mounted() {
  console.log(this.$refs.forRef)
}

$refs 只會在元件渲染完成後生效 , 並且它不是響應式的 , 這僅作為用於直接操作子元件的 逃生艙 應該儘量避免在模板或者計算屬性中訪問 $refs

元件生命週期

每個 Vue 例項建立時都需要經過一系列的初始化 ; 開始建立 , 初始化資料 , 編譯模板, 掛載DOM , 更新 , 渲染 , 解除安裝等一系列過程 , 成為 生命週期 , 同時在這些過程中也會執行一些叫做生命週期鉤子的函式

  • beforeCreate : 建立前 例項初始化之後,this指向建立的例項,不能訪問到data、computed、watch、methods上的方法和資料

  • created :建立後 例項建立完成 , 可以訪問 data computed watch methods 資料 , 沒有渲染進 瀏覽器 無法訪問 DOM ; 注意 : 這個生命週期內傳送 ajax 請求 是沒有什麼方法對例項化過程進行攔截的 , 因此加入某些資料必須獲取之後才能進入這個頁面的話 , 並不適合在這個方法內完成 , 建議使用 beforeRouterEnter 路由鉤子中完成

  • beforeMount : 掛載前 掛載開始之前呼叫 , beforeMount 之前會找到對應的 template 編譯成 render 函式

  • mounted :掛載後 例項掛載到 DOM , 可以操作DOM $ref 可以使用 ; 此時可以做一些 ajax 操作 , mounted 只會執行一次

  • beforeUpdate : 更新前 響應式資料更新時呼叫 , DOM 重新渲染和打補丁之前 , 可以在這裡進一步更改狀態 , 不會進行二次渲染

  • updated :更新後 虛擬DOM重新渲染和打補丁之後呼叫 , 元件已經更新 , 可以執行後續操作 ; 避免在這裡運算元據 , 可能會陷入死迴圈

  • activated : 被 keep-alive 快取的元件啟用時呼叫

  • deacticated : 被 keep-alive 快取的元件停用時呼叫

// 子元件
// 要在子元件的聲命週期內 , 才會觸發這兩個鉤子函式
activated() {
  console.log('itemList 被啟用')
},
deactivated() {
  console.log('itemList 被停用 / 失活')
},
// 父元件
// toggle 切換時觸發子元件中的這兩個鉤子函式
<button @click="com = 'ItemPost'">post</button>
<button @click="com = 'ItemList'">list</button> 

<keep-alive>
  <component :is="com"></component>
</keep-alive>
  • beforeDestroy :銷燬前 例項銷燬之前呼叫 , 這裡各種資料仍然可以訪問 ; 可以再次銷燬定時器, 解綁事件等操作
  • destroyed :銷燬後 例項銷燬後呼叫 , Vue 例項的所有內容都會解除繫結 , 所有事件事件監聽器會被移除 , 所有子例項也會被銷燬
<template>
  <div>
    <p>this is $el</p>
    <button @click="info = '修改後的值'">{{ info }}</button> 
    <button @click="handle">解除安裝</button> 
  </div>
</template>
<script>
export default {
  name: 'HomePage',
  data() {
    return {
      info: 'data options',
      flag: true
    }
  },
  methods: {
    handle() {
      this.$destroy()
    }
  },
  beforeCreate() {
    console.group('##### 元件建立前 beforeCreate #####')
    console.log('Vue', this)
    console.log(this.info)
    console.log(this.$el)
  },
  created() {
    console.group('##### 元件建立後 created #####')
    console.log(this.info)
    console.log(this.$el)
  },
  beforeMount() {
    console.group('##### 元件掛載前 beforeMount #####')
    console.log(this.info)
    console.log(this.$el)
  },
  mounted() {
    console.group('##### 元件掛載後 mounted #####')
    console.log(this.$el)
  },
  beforeUpdate() {
    console.group('##### 元件更新前 beforeUpdate #####')
    console.log(`這裡的資料已經修改了只是沒有渲染 ----- `+ this.info)
    this.info  = '又修改了一次'
  },
  updated() {
    console.group('##### 元件更新後 updated #####')
    console.log('更新後的新值: ', this.info)
  },
  beforeDestroy() {
    console.group('##### 元件解除安裝前 updated #####')
    console.log(this.info)
    console.log(this.$el)
  },
  destroyed() {
    console.group('##### 元件解除安裝後 updated #####')
    console.log(this.info)
    console.log(this.$el)
  }
}
</script>

父子元件生命週期

  • 父元件執行完 beforeMount鉤子之後就會去載入 子元件 , 只元件載入完成後才會觸發父元件的 Mounted
  • 子元件更新不會觸發父元件的更新, ( 不涉及父子元件互動資料 )
  • 子元件的解除安裝會觸發父元件的更新

過渡 動畫

Vue 在插入,更新或者移出 DOM 時,提供多種不同的過渡效果
在下列情形中可以給任何元素新增進入/離開過渡
- 條件渲染 (v-if v-show
- 動態元件 (component)
- 元件根節點

過渡的類名:在進入/離開的過渡中,會有6個 class 切換

  • v-enter :定義過渡的開始 , 在元素被插入之前生效 , 在元素被插入之後的下一幀移除
  • v-enter-active :定義進入過渡生效時的狀態 , 在整個進入過渡的階段中都會應用 . 在元素被插入之前生效 . 咋過渡/動畫結束之後移除 , 這個類可以被定義進入過渡的過程時間 , 延遲 和曲線函式
  • v-enter-to : 定義進入過渡的結束狀態 , 在元素被插入後的下一幀生效 ( 同時 v-enter 移除 ), 在過渡動畫完成之後移除
  • v-leave :定義離開過渡的開始時間 , 在離開過渡被觸發時立刻生效 , 下一幀被移除
  • v-leave-active :定義離開過渡生效時的狀態 , 在整個離開過渡的階段中應用 , 在離開過渡被觸發時 立即生效 , 在過渡動畫完成之後移除 , 這個類可以被定義離開過渡的過程時間 , 延遲和曲線函式
  • v-leave-to : 定義離開過渡的結束狀態 , 在離開過渡被觸發後下一幀生效 (同時 v-leave 移除), 在過渡東環完成之後移除
<transition>
  <dynamic v-if="isShow"></dynamic>
</transition>

如果 <transition> 沒有 name 屬性 類名預設是 v- 開頭 , 如果使用了自定義名字 , 替換成自定名字開頭 ; transition 不會被渲染成真是的 DOM 元素
示例 :<transition name="my-trans"> my-trans-enter

<style lang="css">
  .v-enter, .v-leave-to{
    opacity: 0;
    transition: all .5s ;
  }
  .v-enter-to, .v-leave{
    opacity: 1;
    transition: all .5s ;
  }
</style>

多個元件的過渡

<transition>
  <keep-alive>  
    <component :is="showWhat"></component>
  </keep-alive>
</transition>
<style lang="css">
  .v-enter, .v-leave-to{
    opacity: 0;
  }
  .v-enter-active, .v-leave-active{
    transition: opacity .3s ease;
  }
</style>
過渡模式

在切換 tab 的時候 內容被重繪了 , 一個是離開過渡的時候, 另一個是進入過渡 , 這是<transition>的預設行為 , 進入和離開同時發生 ; 過渡模式僅適用於元件之間
同時生效的進入和離開的過渡不能滿足所有要求,所以 Vue 提供了過渡模式

  • in-out : 新元素先進行過渡 , 完成之後當前元素過渡離開
  • out-in :當前元素先進行過渡 , 完成之後新元素過渡進入

可以看出上圖的動畫效果為當前元素還沒有完成 , 新元素就進來了 , 此時我們可以使用 out-in 模式即可解決上述問題

<transition mode="out-in">
  <keep-alive>  
    <component :is="showWhat"></component>
  </keep-alive>
</transition>

初始渲染過渡

可以通過appear屬性設定節點的初始渲染過渡 ; 需要注意的是, 這個關鍵字加上之後, 預設就會帶有過渡效果 , translateY也可以自定義類名和鉤子 ; 初始渲染過渡

<transition appear>
  <div v-if="toggle">post</div>
  <div v-else>list</div>
</transition>

列表過渡

  • 列表過渡需要用到 <transition-group>

  • 會產生標籤名, 預設為 span 可以通過 tag 屬性進行設定過渡模式不可用

<transition-group  tag="ul" appear>
  <li v-for="user of users" :key="user.id">
    <p>{{ user.uname }}</p>
  </li>
</transition-group>

nextTick

Vue 在更新 DOM 的時候是非同步的 , 只要偵聽到資料的變化 , 並快取在同一事件迴圈中 , 等待事件迴圈中的所有的資料變化完成後, 統一更新檢視 , 為了得到最新的DOM 所以設定了 nextTick()
將回撥延遲到下次 DOM 更新迴圈之後執行 , 在修改資料之後立即使用他 , 然後等待 DOM 更新 ;
簡單理解就是 : nextTick 是將這個回撥函式延遲在下一次 DOM 更新資料後呼叫 , 即 DOM 重新渲染後自動執行該函式

created() {
  // created 鉤子中可以操作 DOM 元素
  this.$nextTick(() => {
    this.$refs.divRef.innerHTML = 'hello vue'
  })
}

混入 Mixin

混入提供一種很靈活的方式, 來分發 Vue 元件中的可複用功能, 一個混入物件可以包含任意元件選項 (data, components, methods, created 等), 當元件使用混入物件時, 所有混入物件的選項將被混合進元件本身的選項 ;

假設我們現在要封裝一個 button 元件, 我們可以嘗試著不再 components 資料夾中去封裝, 在同級目錄中新建一個 Mixin 資料夾 例項

<!-- path: Mixin/MyButton/MyButton.vue -->
<template>
  <button :class="['my-btn', type]">
    <!-- 我們希望使用者輸入按鈕的名字所以使用插槽 -->
    <slot></slot>
  </button>
</template>
<script>
// 我們希望使用者傳遞進來一個 type 來定製 btn 樣式
export default {
  props: {
    type: {
      type: String,
      default: 'my-primay'
    }
  }
}
</script>
<style scoped>
 /* 基類樣式 */
 .my-btn {
    border: none;
    outline: none;
    line-height: 30px;
    width: 50px;
  }
 /* 使用者可擴充樣式 */
.btn-primary {
  background: skyblue;
}
.btn-danger {
  background: orange;
}
.btn-success {
  background: palegreen;
}
</style>

然後我們在同級別的目錄下建立一個 Mixin 物件

// path: Mixin/MyButton/index.js
import MyButton from './MyButton.vue'
export default {
  // 這裡我們使用了 元件中的 components 物件
  components: {
    MyButton
  }
}

然後我們在頁面中去使用它

<!-- views/page.vue -->
<template>
  <!-- 此時我們只需要傳入指定不同的類名即可變換 button 的樣式 -->
  <my-button type="btn-danger">按鈕</my-button>
</template>
<script>
import MyButton from './mixin/MyButton'
export default {
  // 元件內使用 mixins 將這個物件混入, 
  mixins: [MyButton]  
}
</script>

簡單總結一下, 可以看出我們上面雖然用的有些牽強, 不曉得有沒有注意到 , 我們實際上等於把 註冊元件的方式給抽離出去了, 取而代之的是把他混入進去, 這裡就可以簡單理解, 很想 合併了兩個物件的概念 , 那麼接下來就看一下細節

選項合併

當元件和混入物件含有同名選項時 , 這些選項將以恰當 的方式進行合併
什麼叫恰當 ? 看示例

  • 輸入物件會進行遞迴合併 , 並在發生衝突時,以元件資料優先
  • 同名的鉤子函式會合併成為一個陣列,依次呼叫,mixin的鉤子優先執行
  • 物件型別選項會合併為同一個物件,如果鍵名衝突,以元件內為主
// mixin/index.js
export default {
  data() {
    return {
      m_name: 'lisi',
      m_list: [1, 2, 3]
    }
  },
  created() {
    // 混入之後會被呼叫 同時我們也可以在混入之前呼叫元件內的資料
    console.log(this.m_name, this.m_list, 'mixin')
    console.log(this.c_name, this.c_list, 'component')
      
    // 執行 handle 
    this.handle() // 這個handle 實際就是呼叫了 元件中的handle 而不是 mixin 中的handle
    this.handle1()
  },
  methods: {
    // 由於此處的 handle 與 元件記憶體在同名 按照上面說明, 這個handle被覆蓋以下面元件中為主
    handle() {
      console.log('mixin handle')
    },
    handle1() {
      console.log('mixin handle1')
    }
  }
}
<template>
  <div></div>
</template>
<script>
import mixin from '@/mixin'
export default {
  mixins: [mixin],
  data() {
    return {
      c_name: 'zhangsan',
      c_list: [1, 2, 3]
    }
  },
  created() {
    // 同理這裡也可以直接訪問 mixin 中的資料,這裡就不演示了
    console.log('this is component')
      
    // 呼叫handle 
    this.handle() 
  },
  methods: {
    handle() {
      console.log('component handle')
    }
  }
}
</script>

全域性混入

混入也可以全域性註冊 , 使用時需要小心, 全域性混入會影響每一個之後建立的 vue 元件,也就是說以後建立的每一個 Vue 元件會混入這個 mixin 都會執行這裡面的操作

// main.js
Vue.mixin({
  data() {
    return {}
  },
  created() {},
  // ......
})

provide / inject 依賴注入

provideinject主要開發高階外掛/元件庫時使用,並不推薦用於普通應用程式中
這對選項要一起使用,允許一個祖先元件向自己所有的子孫元件注入依賴,不論元件層間有多深;
也就說這對選項可以跨越元件的傳值 下面看示例

// App.vue
<template>
  <div>
    <router-view></router-view>
  </div>
</template>
<script>
export default {
  data() {
    return {
      info: { name: 'zs', age: 23 }
    }
  },
  // 使用 provide 把值傳遞出去
  // 這裡碰到一點小問題,官網說可以是 Objct | function 返回一個物件,這裡試了 Object 就是不能成功不知道為啥?
  provide() {
    return {
      info: this.info,
      reload: this.reload
    }
  },
  methods: {
    reload() {
      console.log('reload')
    }
  }
}
</script>
// Home.vue
<template>
  <div>
    Home
    <provide-test></provide-test>
  </div>
</template>
<script>
// 這裡我們只是把需要用到 provide 資料的元件引入,可以看出我們並沒有使用
import ProvideTest from '@/components/ProvideTest.vue'
export default {
  components: {
    ProvideTest
  }
}
</script>
// components/ProvideTest.vue
<template>
  <div></div>
</template>
<script>
export default {
  // 這裡我們使用 indect 來接收 父元件傳遞過來的資料,可以看出這種方式類似於 props 接收
  inject: ['info', 'reload'],
  mounted() {
    // 然後就可以通過例項訪問這些資料了
    console.log(this.info)
    this.reload()
  }
}
</script>

provideinject繫結並不是響應式的, 這是刻意為之,如果你傳入了一個可監聽的物件,那麼其物件的 property 還是可響應式的;inject接收過來的資料,不要試圖修改,會報錯的

非父子元件傳值 on / on/emit

e m i t 這 個 我們 們 前 面 就 知 道 了 觸 發 當 前 實 例 上 的 事 件 , 附 件 參 數 傳 給 監 聽 器 的 回 調 ( 可 以 實 現 子 組 件 向 父 組 件 間 的 傳 值 ) 了 解 如 何 傳 值 之 前 先 了 解 ‘ emit 這個我們們前面就知道了 觸發當前例項上的事件, 附件引數傳給監聽器的回撥 (可以實現子元件向父元件間的傳值) 瞭解如何傳值之前先了解 ` emit我們調on o n ‘ 監 聽 當 前 實 例 的 上 的 自 定 義 事 件 , 事 件 可 以 由 ‘ on`監聽當前例項的上的自定義事件,事件可以由 ` onemit `觸發,回撥函式會接收所有傳入事件觸發的額外引數, 乍一看和指令 v-on 差不多,那麼下面就看下具體示例

// About.vue
<template>
  <div>
    <button @click="handle">點選傳遞</button>
  </div>
</template>
<script>
export default {
  methods: {
    handle() {
      // 這裡使用 emit 觸發一個自定義事件 如果是監聽原生事件 同樣可以傳遞 event
      this.$emit('on-click', '單檔案內被監聽了')
    }
  },
  mounted() {
    // 在 mounted 中 用 on 去監聽觸發的事件, 並且接收傳遞過來的引數
    this.$on('on-click', msg => {
      console.log(msg)
    })
  }
}
</script>

當然如果只是這樣使用, 肯定是略顯雞肋, 既然知道 API 的特性,那麼下面看父子元件傳值

兄弟元件之間的通訊,EventBus Vue 中也叫 事件匯流排,是一種釋出訂閱模式(關於釋出訂閱模式)通過EventBus 來作為溝通橋樑,讓所有元件共用一個事件中心,可以像該事件中心註冊事件或者接收事件

// Bus/index.js
import Vue from 'vue'

// 建立一個空的 Vue 例項 用來做 橋樑
export const Bus = new Vue()
// main.js
import { Bus } from './Bus'
// 將這個空的例項繫結到 vue 的原型中
Vue.prototype.$bus = Bus
// About.vue
<template>
  <div>
    <button @click="handle">點選傳遞</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      message: ''
    }
  },
  methods: {
    handle() {
      // 通過 $bus.$emit 觸發事件 可以是自定義事件也可以是原生事件
      this.$bus.$emit('click', '其他檔案內被監聽了', event)
    }
  }
}
</script>
// Home.vue
<template>
  <div></div>
</template>
<script>
export default {
  mounted() {
    // 兄弟元件中可以通過 $bus.$on 監聽觸發的事件, 並且接收一個回撥處理引數
    this.$bus.$on('click', (msg, e) => {
      console.log(msg)
      console.log(e)
    })
  }
}
</script>

注意: 元件掛在之前會執行 mounted 鉤子,所以在這個鉤子中監聽觸發的事件,當然在 created 中也是可以的 建議還是 mounted

結語:作者是juejin-DoubleX,整理不易點個贊吧

相關文章