尤大宣佈 Vue3.0 已經進入候選階段【賀電】!!!如今也能夠提早試用啦,能夠經過 Vite 直接啓動一個項目。另外 Vue3.0 的文檔也已經上線,感興趣的小夥伴能夠先睹爲快。如下是我對比 Vue2.x 版本文檔得出的一部分差別,歡迎補充。vue
在 Vue2.x 中,經過 new Vue 建立 Vue 的實例,而且經過傳入 el參數 進行掛載 DOMnode
<!-- Vue2.x 建立實例 -->
var vm = new Vue({
// 選項
})
<!-- Vue2.x 掛載DOM -->
var vm = new Vue({
el: '#app',
data: {a:1}
})
複製代碼
在 Vue3.0 中,經過 createApp 方法建立 Vue 的實例,建立實例後能夠把這個容器傳給 mount 方法來掛載react
<!-- Vue3.0 建立實例 -->
Vue.createApp(/* options */)
<!-- Vue3.0 掛載DOM -->
Vue.createApp(/* options */).mount('#app')
複製代碼
生命週期沒有太大的改變,因爲建立實例的方法改變了,所以有一些細微的差異。git
值得注意的是:在 Vue2.x 中,銷燬實例的兩個鉤子是 beforeDestory 以及 destoryed,而在 Vue3.0 中這兩個鉤子的名字變動爲 beforeUnmount 和 unmounted。es6
Vue2.x 生命週期 github
Vue3.0 生命週期api
Vue2.x 和 Vue3.0 都還是採用經過給 Vue 的 元素加一個特殊的 is 屬性來實現數組
<!-- 組件會在 `currentTabComponent` 改變時改變 -->
<component v-bind:is="currentTabComponent"></component>
複製代碼
可是對於解析 DOM 模板,諸如<ul>
、<table>
等限制內部元素的特殊狀況,相比 Vue2.x 中是經過綁定 is 屬性, Vue3.0 提供的是 v-is 指令bash
<!-- Vue2.x 使用 is 屬性 -->
<table>
<tr is="blog-post-row"></tr>
</table>
複製代碼
<!-- Vue3.0 使用 v-is 指令 -->
<table>
<tr v-is="'blog-post-row'"></tr>
</table>
複製代碼
Vue2.x 和 Vue3.0 都還是經過$emit('myEvent')
觸發事件,經過v-on:myEvent
來監聽事件,不一樣的是,Vue3.0 在組件中提供了 emits 屬性來定義事件app
<!-- Vue3.0 自定義事件 -->
app.component('custom-form', {
emits: ['in-focus', 'submit']
})
複製代碼
甚至你能夠在自定義事件中添加校驗,這時須要把emits設置爲對象,而且爲事件名分配一個函數,該函數接收傳遞給 $emit 調用的參數,並返回一個布爾值以指示事件是否有效
<!-- Vue3.0 爲自定義事件添加校驗 -->
app.component('custom-form', {
emits: {
// No validation
click: null,
// Validate submit event
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm() {
this.$emit('submit', { email, password })
}
}
})
複製代碼
v-model
在 Vue2.x 中, v-model 默認會利用 value
做爲 prop 名以及 input
做爲觸發的 event 名。對於特殊的場景,也能夠經過 model
選項來指定 prop 名和 event 名(注意這時仍需在 props 裏聲明這個 prop)
<!-- Vue2.0 自定義 v-model -->
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)"
>
`
})
複製代碼
請注意,在 Vue3.0 中, v-model 默認會利用 modelValue
做爲 prop 名以及 update:modelValue
做爲觸發的 event 名。
支持給每一個 v-model 傳入一個參數,這樣就能夠在一個組件上同時使用多個 v-model
<!-- Vue3.0 自定義 v-model 而且傳入參數 -->
<my-component v-model:foo="bar" v-model:name="userName"></my-component>
複製代碼
甚至還能夠爲 v-model 設置自定義修飾符,默認是經過在props中定義 modelModifiers
對象來接受修飾符,所以你能夠經過修飾符來設置你想要的不一樣的事件觸發機制
<!-- Vue3.0 自定義修飾符默認接收方式 -->
<div id="app">
<my-component v-model.capitalize="myText"></my-component>
{{ myText }}
</div>
const app = Vue.createApp({
data() {
return {
myText: ''
}
}
})
app.component('my-component', {
props: {
modelValue: String,
modelModifiers: {
default: () => ({})
}
},
methods: {
emitValue(e) {
let value = e.target.value
if (this.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
this.$emit('update:modelValue', value)
}
},
template: `<input
type="text"
v-bind:value="modelValue"
v-on:input="emitValue">`
})
app.mount('#app')
複製代碼
固然,對於傳入了參數的 v-model ,則須要在props裏面配置arg + "Modifiers"
來接收這個帶參數的v-model的修飾符
<!-- Vue3.0 自定義參數的自定義修飾符 -->
<my-component v-model:foo.capitalize="bar"></my-component>
app.component('my-component', {
props: ['foo', 'fooModifiers'],
template: `
<input type="text"
v-bind:value="foo"
v-on:input="$emit('update:foo', $event.target.value)">
`,
created() {
console.log(this.fooModifiers) // { capitalize: true }
}
})
複製代碼
Vue2.x 混入的方式 經過 Vue.extend({mixins: [myMixin]})
定義一個使用混入對象的組件
// 定義一個混入對象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定義一個使用混入對象的組件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
複製代碼
而 Vue3.0 則和建立一個實例類似,經過 Vue.createApp({mixins: [myMixin]})
定義一個使用混入對象的組件
// 定義一個混入對象
const myMixin = {
created() {
this.hello()
},
methods: {
hello() {
console.log('hello from mixin!')
}
}
}
// 定義一個使用混入對象的組件
const app = Vue.createApp({
mixins: [myMixin]
})
app.mount('#mixins-basic') // => "hello from mixin!"
複製代碼
Vue2.x 的指令定義對象包含 5 個鉤子:
bind
:只調用一次,指令第一次綁定到元素時調用。在這裏能夠進行一次性的初始化設置。inserted
:被綁定元素插入父節點時調用 (僅保證父節點存在,但不必定已被插入文檔中)。update
:所在組件的 VNode 更新時調用,可是可能發生在其子 VNode 更新以前。指令的值可能發生了改變,也可能沒有。可是你能夠經過比較更新先後的值來忽略沒必要要的模板更新。componentUpdated
:指令所在組件的 VNode 及其子 VNode 所有更新後調用。unbind
:只調用一次,指令與元素解綁時調用。Vue3.0 的指令對象包含 6 個鉤子:
beforeMount
:指令第一次綁定到元素時調用。在這裏能夠進行一次性的初始化設置。mounted
:當被綁定元素插入父節點時調用。beforeUpdate
:在更新所在組件的VNode以前調用。updated
:指令所在組件的 VNode 及其子 VNode 所有更新後調用。beforeUnmount
:在綁定元素的父組件卸載以前調用。(對比 Vue2.x 新增)unmounted
:只調用一次,指令與元素解綁且父組件已卸載時調用。在 Vue3.0 中,因爲對片斷的支持,組件可能會存在多個根節點,這時使用自定義指令可能會產生問題。自定義指令對象包含的鉤子會被包裝並做爲 Vnode 生命週期鉤子注入到 Vnode 的數據中。
<!-- Vue3.0 自定義指令對象包含的鉤子包裝後 -->
{
onVnodeMounted(vnode) {
// call vDemo.mounted(...)
}
}
複製代碼
當在組件中使用自定義指令時,這些onVnodeXXX
鉤子將做爲無關屬性直接傳遞給組件,能夠像這樣在模板中直接掛接到元素的生命週期中(這裏不太明白,以後試驗過再來更新)
<div @vnodeMounted="myHook" />
複製代碼
當子組件在內部元素上使用 v-bind="$attrs"
時,它也將應用它上面的任何自定義指令。
Vue3.0 內置<teleport>
的組件能夠傳送一段模板到其餘位置,
<!-- Vue3.0 <teleport>傳送組件 -->
<body>
<div id="app" class="demo">
<h3>Move the #content with the portal component</h3>
<div>
<teleport to="#endofbody">
<p id="content">
This should be moved to #endofbody.
</p>
</teleport>
<span>This content should be nested</span>
</div>
</div>
<div id="endofbody"></div>
</body>
複製代碼
若是<teleport>
包含Vue組件,它將仍然是<teleport>
父組件的邏輯子組件,也就是說,即便在不一樣的地方呈現子組件,它仍將是父組件的子組件,並將從父組件接收 prop
。
使用多個傳送組件 會採用累加的邏輯,像這樣
<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>
<!-- 結果 B 渲染在 A 後面 -->
<div id="modals">
<div>A</div>
<div>B</div>
</div>
複製代碼
Vue2.x 的渲染函數的參數是createElement
Vue3.0 的渲染函數的參數createVNode
(這個名字更接近它實際的意義,返回虛擬 DOM)
一樣將h
做爲別名,在 Vue3.0 中能夠直接經過 Vue.h
獲取
const app = Vue.createApp({})
app.component('anchored-heading', {
render() {
const { h } = Vue
return h(
'h' + this.level, // tag name
{}, // props/attributes
this.$slots.default() // array of children
)
},
props: {
level: {
type: Number,
required: true
}
}
})
複製代碼
事件 & 按鍵修飾符 Vue2.x 對於 .passive、.capture 和 .once 這些事件修飾符,提供了相應的前綴能夠用於 on:、
修飾符 | 前綴 |
---|---|
.passive |
& |
.capture |
! |
.once |
~ |
.capture.once 或 |
|
.once.capture |
~! |
<!-- Vue2.x 對修飾符使用前綴 -->
on: {
'!click': this.doThisInCapturingMode,
'~keyup': this.doThisOnce,
'~!mouseover': this.doThisOnceInCapturingMode
}
複製代碼
而 Vue3.0 則是使用對象語法
<!-- Vue3.0 對修飾符使用對象語法 -->
render() {
return Vue.h('input', {
onClick: {
handler: this.doThisInCapturingMode,
capture: true
},
onKeyUp: {
handler: this.doThisOnce,
once: true
},
onMouseOver: {
handler: this.doThisOnceInCapturingMode,
once: true,
capture: true
},
})
}
複製代碼
開發插件 Vue3.0 仍須要暴露一個 install
方法,傳入兩個參數,第一個是經過Vue.createApp
構造的對象,第二個是用戶傳入的options
// plugins/i18n.js
export default {
install: (app, options) => {
// Plugin code goes here
}
}
複製代碼
插件中經過暴露出的app.config.globalProperties
屬性註冊全局方法
// plugins/i18n.js
<!-- 經過 app.config.globalProperties 全局注入 translate 方法 -->
export default {
install: (app, options) => {
app.config.globalProperties.$translate = (key) => {
return key.split('.')
.reduce((o, i) => { if (o) return o[i] }, i18n)
}
}
}
複製代碼
還能夠經過inject
來爲用戶提供方法或屬性
// plugins/i18n.js
<!-- 這樣組件裏就能夠經過 inject 訪問 i18n 和 options -->
export default {
install: (app, options) => {
app.config.globalProperties.$translate = (key) => {
return key.split('.')
.reduce((o, i) => { if (o) return o[i] }, i18n)
}
app.provide('i18n', options)
}
}
將['i18n']注入組件並訪問 -->
複製代碼
使用插件 仍然是經過 use()
方法,能夠接受兩個參數,第一個參數是要使用的插件,第二個參數可選,會傳入到插件中去。
import { createApp } from 'vue'
import App from './App.vue'
import i18nPlugin from './plugins/i18n'
const app = createApp(App)
const i18nStrings = {
greetings: {
hi: 'Hallo!'
}
}
app.use(i18nPlugin, i18nStrings)
app.mount('#app')
複製代碼
衆所周知,Vue2.x 是經過 Object.defineProperty
結合訂閱/發佈模式實現的。
給 Vue 實例傳入 data
時,Vue 將遍歷data
對象全部的 property
,並使用 Object.defineProperty
把這些屬性所有轉爲 getter
/setter
,在屬性被訪問和修改時追蹤到依賴。每一個組件實例都對應一個 watcher
實例,它會在組件渲染的過程當中把「接觸」過的數據屬性記錄爲依賴。當依賴項的 setter
觸發時,會通知 watcher
,從而使它關聯的組件從新渲染。
Proxy
代理來攔截對目標對象的訪問。 給 Vue 實例傳入
data
時,Vue 會將其轉換爲
Proxy
。它能使 Vue 在訪問或修改屬性時執行
依賴追蹤以及
更改通知。每一個屬性都被視爲一個依賴項。 在初次渲染後,組件將追蹤依賴(也就是它在渲染時訪問過的屬性)。換句話說,組件成爲這些屬性的
訂閱者。當代理攔截到
set
操做時,該屬性將通知其訂閱者從新渲染。
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, prop, receiver) {
track(target, prop) // Track the function that changes it 依賴項跟蹤
return Reflect.get(...arguments)
},
set(target, key, value, receiver) {
trigger(target, key) // Trigger the function so it can update the final value 更改通知
return Reflect.set(...arguments)
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
// intercepted!
// tacos
複製代碼
值得注意的是,原始對象與代理對象是不相等的
const obj = {}
const wrapped = new Proxy(obj, handlers)
console.log(obj === wrapped) // false
複製代碼
聲明響應式狀態reactive
能夠經過reactive
方法建立一個響應式狀態(也就是 Vue2.x 中的 Vue.observable()
),這個方法會返回一個響應式對象。模板編譯的過程當中 render
方法用的就是這些響應式屬性。
import { reactive } from 'vue'
// reactive state
const state = reactive({
count: 0
})
複製代碼
還能夠建立只讀的響應式狀態
const original = reactive({ count: 0 })
const copy = readonly(original)
// mutating original will trigger watchers relying on the copy
original.count++
// mutating the copy will fail and result in a warning
copy.count++ // warning: "Set operation on key 'count' failed: target is readonly."
複製代碼
建立獨立的響應式屬性 refs
能夠經過ref
方法建立一個響應式的獨立的原始值,一樣也會返回一個響應式的可變對象,這個對象只包含一個value
屬性
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
複製代碼
當一個ref
做爲在渲染上下文中返回的屬性且在模板中被訪問時,會自動展開內部的value
,所以無需再使用xx.value
的方式來訪問,這樣就像訪問一個普通屬性同樣。要注意
,自動解析 value
只發生在響應式的對象裏,從數組或集合中訪問時仍須要.value
。
<template>
<div>
<span>{{ count }}</span>
<button @click="count ++">Increment count</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count
}
}
}
</script>
複製代碼
另外若是將一個新的ref
賦值給現有的屬性,那將替換掉舊的ref
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1
複製代碼
computed
方法
能夠經過computed
方法直接建立一個計算值,接收一個getter
函數做爲參數而且返回一個不可變的響應式對象。
const count = ref(1)
const plusOne = computed(() => count.value++)
console.log(plusOne.value) // 2
plusOne.value++ // error
複製代碼
或者能夠傳入一個帶有getter
和setter
方法的對象來建立一個可修改的響應式對象
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
複製代碼
watchEffect
方法
能夠經過watchEffect
方法,它會當即運行傳入的函數,而且跟蹤這個函數的依賴項,當依賴項更新時,當即再次執行這個函數。
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
複製代碼
在組件執行setup()
或生命週期鉤子時會被調用,而且在組件卸載時自動中止。或者能夠經過執行watchEffect
方法返回的中止操做來中止觀察。
const stop = watchEffect(() => {
/* ... */
})
Vue3
// later
stop()
複製代碼
Side Effect Invalidation 附帶的失效方法
有時watchEffect
中執行的方法多是異步的接收一個onInvalidate
函數做爲參數來註冊一個失效時的回調方法,能夠執行一些清理操做,它將會在watchEffect
從新執行時或者watchEffect
終止(即在setup()
或生命週期鉤子
中使用了watchEffect
的組件卸載時)時執行。
watchEffect(onInvalidate => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id has changed or watcher is stopped.
// invalidate previously pending async operation
token.cancel()
})
})
複製代碼
注意setup()
將在組件掛載前調用,所以若是想要在watchEffect
中使用 DOM
(或者組件),請在掛載的鉤子中聲明watchEffect
onMounted(() => {
watchEffect(() => {
// access the DOM or template refs
})
})
複製代碼
還能夠爲watchEffect
傳入額外的對象做爲參數。 好比經過設置flush
來設置監聽方法是異步執行仍是在組件更新前執行
// fire synchronously
watchEffect(
() => {
/* ... */
},
{
flush: 'sync'
}
)
// fire before component updates
watchEffect(
() => {
/* ... */
},
{
flush: 'pre'
}
)
複製代碼
而onTrack
和onTrigger
參數能夠用來調試watchEffect
的方法
watchEffect(
() => {
/* side effect */
},
{
onTrigger(e) {
debugger
}
}
)
複製代碼
watch
相比watchEffect
的區別 watch
是惰性的,只有在被監聽的屬性更新時纔會調用回調,而且能夠訪問被監聽屬性的當前值和原始值。
// watching a getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// directly watching a ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
複製代碼
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
複製代碼
後面再更,晚安