一萬字教你怎麼用 Vue

Vue

官網介紹它是一個構建用戶界面的漸進式框架 ;javascript

漸進式框架 : 主張最少 , 每一個框架都不可避免會有本身的一些特色 , 從而對使用者有必定的要求 , 這些要求就是主張 , 主張有強有弱,它的強勢程度會影響在業務開發中的使用方式 ; 而 Vue 雖然有全家桶套餐 , 可是你能夠只用它的一部分 , 而不是用了它的 核心庫 就必須用它的所有 .css

聲明式渲染

Vue.js 提供了簡潔的模板語法聲明式的將數據渲染至 DOM 中html

<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 函數 , 從而返回初始數據的一個全新數據對象vue

// 錯誤 示例
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 時 , 是直接一個相同的內存地址 , 因此爲了每一個組件的獨立 , 採用了這種方式 ; 但因爲根組件只有一個 , 不存在數據污染的狀況 , 因此就能夠是一個對象 ;java

參考資料 :webpack

指令

v-cloak

這個指令能夠配合着 CSS 隱藏未編譯的 Mustache 標籤 , 直到實例準備完畢web

問題展現 :ajax

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

v-text

更新某個元素節點下的值 ; 注意 : 會更新所有內容 , 若是想要局部更新 , 可使用 Mustache 語法算法

<div v-text="root"></div>
複製代碼

v-html

更新元素的 innerHTML 注意 : 普通 html 內容vue-cli

在網站上使用 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>
複製代碼

參考資料 :

http://www.javashuo.com/article/p-bztnguuf-bh.html

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>
複製代碼

參考資料 :

https://segmentfault.com/q/1010000011186651

  • .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

watch: {
  apiData: {
    handler(newVal, oldVal) { },
    deep: true,
    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 前綴詳情看官網

// 父組件
<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>
複製代碼

下圖能夠看出沒有顯示子組件時, 把這個文件給緩存起來了,只有真正調用到的時候纔會加載對應的資源;

固然這只是其中一個功能,好比網絡很差的時候,組件加載的用戶體驗會受到損害,超時的處理等等;具體配置參考 vue 官網 : 處理加載狀態

訪問子組件實例或子元素 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指向建立的實例,不能訪問到datacomputedwatchmethods上的方法和數據

  • 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>
複製代碼

更多動畫相關 : Vue過渡動畫

nextTick

Vue 在更新 DOM 的時候是異步的 , 只要偵聽到數據的變化 , 並緩存在同一事件循環中 , 等待事件循環中的全部的數據變化完成後, 統一更新視圖 , 爲了獲得最新的DOM 因此設置了 nextTick()

將回調延遲到下次 DOM 更新循環以後執行 , 在修改數據以後當即使用他 , 而後等待 DOM 更新 ;

簡單理解就是 : nextTick 是將這個回調函數延遲在下一次 DOM 更新數據後調用 , 即 DOM 從新渲染後自動執行該函數

created() {
  // created 鉤子中能夠操做 DOM 元素
  this.$nextTick(() => {
    this.$refs.divRef.innerHTML = 'hello vue'
  })
}
複製代碼
相關文章
相關標籤/搜索