尤大宣布 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]) => {
/* ... */
})
复制代码
后面再更,晚安