Vue2迁移变更
提示: Vue3更新了全局API和一些重要的模版指令,贸然的更新Vue2程序,需要花费较高成本来进行兼容;最主要是现有的Vue生态支持3.0的并不多。 官方文档也明确指出:我们仍在开发 Vue 3 的专用迁移版本,该版本的行为与Vue2.x 兼容,运行时 警告不兼容。如果你计划迁移一个非常重要的 Vue 2 应用程序,我们强烈建议你等待迁移版本完成以获得更流畅的体验
全局API
1. createApp
Vue 2 通过 new Vue() 创建的根 Vue 实例,同一个 Vue 实例共享相同的全局配置;全局配置很容易意外地污染其他测试用例。
// 这会影响两个根实例
Vue.mixin({
/* ... */
})
const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })
调用 createApp 返回用户实例
import { createApp } from 'vue'
const app = createApp({})
// 使用生产版提示,仅在使用“dev + full build”才会显示
app.config.productionTip = false
// Vue之外定义的自定义组件
Vue.config.ignoredElements = ['my-el',/^ion-/]
app.config.isCustomElement = tag => tag.startsWith('ion-')
// 卸载应用程序实例的根组件
app.mount("#app")
setTimeout(() => app.unmount('#app'), 5000)
// 基本与Vue2.x相似
app.component('breadcrumb', {})
app.directive('loading',{
beforeMount(el,binding,vnode){},
mounted(el,binding,vnode){},
...
})
app.provide('guide', 'Vue 3 Guide')
app.mixin()
app.use()
...
2. treeShaking
Vue 2.x 中的这些全局 API 受此更改的影响
- Vue.nextTick
- Vue.observable ( Vue.reactive 替代 )
- Vue.version
- Vue.compile
- Vue.set
- Vue.delete
Vue 2.x写法
// 还能使用
this.$nextTick(()=>{
/* ... */
})
// undefined
import Vue from 'vue'
Vue.nextTick(() => {
/* ... */
})
Vue 3.0写法
import { nextTick } from 'vue'
nextTick(() => {
/* ... */
})
3. 按键修饰符
KeyboardEvent.keyCode被弃用
Vue 2.x写法
// 均不支持
<input @keyup.13="submit" />
Vue.config.keyCodes = { f1: 112 }
<input @keyup.f1="submit" />
Vue 3.0写法
直接将 KeyboardEvent.key暴露的任意有效按键名 转换为 kebab-case 来作为修饰符。
Vue按键别名仍可使用。
<input @keyup.page-up="confirmDelete" />
// 按键别名
<input @keyup.enter="submit" />
4. 事件API
已废除
- $on
- $off
- $once 还存在
- $emit
5. 过滤器
已废除,建议用计算属性替代
<template>
<p>{{ create_time | formatTime }}</p>
</template>
<script>
import { formatUTC } from "@/utils/dateTime.js";
export default {
prop: {
create_time: {
type: Number,
required: true
}
},
filters: {
formatTime(value) {
return formatUTC(value)
}
}
}
</script>
全局过滤器
<p>{{ $filters.formatTime(create_time) }}</p>
app.config.globalProperties.$filters = {
formatTime(value) {
return formatUTC(value)
}
}
6. 内联模板 Attribute
已废除
<my-component inline-template>
<div>
<p>它们被编译为组件自己的模板</p>
<p>不是父级所包含的内容。</p>
</div>
</my-component>
默认slot
<!-- 2.x 语法 -->
<my-comp inline-template :msg="parentMsg">
{{ msg }} {{ childState }}
</my-comp>
<!-- 默认 Slot 版本 -->
<my-comp v-slot="{ childState }">
{{ parentMsg }} {{ childState }}
</my-comp>
模版指令
1. v-model
减少开发者使用 v-model 指令时混淆,增加v-model参数,更具有灵活性。
Vue2.0绝大多数的 v-model 需要更改为 v-model:value Vue 2.x写法
// v-model 等同于绑定了 value的props和input事件
<ChildComponent v-model="pageTitle" />
// 等同于
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
// sync 对props进行双向绑定
<ChildComponent :title.sync="pageTitle" />
this.$emit('update:title', newValue)
Vue 3.0写法
// 添加参数区分 v-model 绑定的值
<ChildComponent v-model:value="pageTitle" v-model:content ="pageContent" />
// 等同于
<ChildComponent
:value="pageTitle"
@update:value="pageTitle = $event"
:content="pageContent"
@update:content="pageContent = $event"
/>
// 支持修饰符
<ChildComponent v-model:title.trim="pageTitle" />
2. key
v-if、v-else、v-else-if节点上,会自动生成唯一的key ;
若手动提供key,每个分支必须使用唯一的key ;
template节点上使用v-for的key Vue 2.x写法
<template v-for="item in list">
<div :key="item.id">...</div>
<span :key="item.id">...</span>
</template>
Vue 3.0写法
<template v-for="item in list" :key="item.id">
<div>...</div>
<span>...</span>
</template>
3. v-if与v-for的优先级
在Vue 2.x,节点需要同时使用v-if和v-for的情况下,v-for会优先作用;
3.0 变更了优先顺序
<div v-if="boolean" v-for="item in list" :key="item.id">
/* ... */
</div>
4. v-bind
Vue 2.x中,定义了v-bind="object" 和一个单独的property, 单独的property总是会覆盖object中的绑定;
3.0 而会根据绑定顺序来决定覆盖;
<div id="red" v-bind="{ id: 'blue' }">
/* ... */
</div>
// 2.x props: red
// 3.0 props: blue
5. v-for中的ref
Vue 2.x中,v-for上的ref会自动创建数组;
3.0中 ref 只会在最后一个节点上;要绑定多个 ref 请将 ref 绑定到一个更灵活的函数上( 3.0 新特性 )
<div v-for="item in list" :ref="setItemRef">
/* ... */
</div>
函数式组件
functional被移除;更多详细信息
渲染函数
1. render
Vue 2.x写法
export default {
render(h) {
return h('div')
}}
Vue 3.0写法
import { h } from 'vue'
export default {
render() {
return h('div')
}}
vue-router中 component 使用该方法并不起作用?
import { h } from 'vue'
component: { render: () => h("router-view") }
2. slot统一
移除 this.slots
3. 自定义指令
生命周期
Vue.directive('highlight', {
beforeMount(el, binding, vnode) {},
mounted(el, binding, vnode) {}
...
})
- bind → beforeMount
- inserted → mounted
- beforeUpdate
- update → 移除
- componentUpdated → updated
- beforeUnmount
- unbind → unmounted
4. 过渡&动画
class变更
- v-enter → v-enter-from
- v-leave → v-leave-from
其他改动
- data始终需要是一个函数
data(){
return {
}
}
- /deep/ 建议改成 ::v-deep,VueCli build会产生警告
/deep/.i-icon{}
::v-deep(.i-icon){}
- template 中允许存在多个元素
<template>
<div>1</div>
<div>2</div>
</template>
对比Vue 2.x
- 对TypeScript支持友好
- 对虚拟DOM进行了重写,对模板的编译进行了优化操作
- 更方便支持jsx
- CompositionAPI,受ReactHook启发
- 性能提升
- 体积变小 关于性能是怎样提升的,引入掘金另一位大佬的博客喔
组合式API
Vue2.x的组织逻辑在大多数情况下都有效;然而,当我们的组件变得更大时,逻辑关注点的列表也会增长,尤其是对于那些刚开始就没有编写过这些组件的人来说,不能有效的看到每一块逻辑层面。 每种颜色都代表一个逻辑关注点,需要根据data的值上下反复查阅
一句话来说,组合式API 做到的就是: 将与同一个逻辑关注点相关的代码配置在一起
这种写法不是绝对的!vue2.x的options api依旧可以使用!
setup
整合所有逻辑写在setup内,将渲染上下文需要的数据通过return暴露出来
<template>
<div>
<input type="text" v-model="newItem" />
<button @click="addItem(newItem)">新增</button>
</div>
<div>
<h3>您的代办事项:</h3>
<h3>共 {{ count }} 项</h3>
<ul>
<li v-for="(item, index) in list" :key="index">
<span>{{ item }}</span>
<span style="color: red; margin-left: 20px" @click="removeItem(index)"
>X</span
>
</li>
</ul>
</div>
</template>
<script>
import { computed, ref } from "vue";
export default {
name: "todo",
setup() {
const newItem = ref("");
const list = ref([]);
const addItem = (newItem) => {
list.value.push(newItem);
};
const removeItem = (index) => {
list.value.splice(index, 1);
};
const count = computed(() => list.value.length);
return {
newItem,
list,
addItem,
removeItem,
count,
};
},
};
</script>
生命周期
之前的生命周期之前加上“on”
因为 setup 是围绕 beforeCreate和 created 来运行的,所以不需要定义他们,逻辑应该直接在setup中编写;从视角看来,setup 在 beforeCreate 之前就被调用;所以使用"this",是拿不到任何实例引用
<script>
import { onBeforeMount, onMounted ... } from "vue";
export default {
setup() {
onBeforeMount(()=> {})
onMounted(() => {})
onBeforeUpdate(()=> {})
onUpdated(() => {})
onBeforeUnmount(()=> {})
onUnmounted(() => {})
onErrorCaptured(()=> {})
return {}
}
}
</script>
参数
setup接受两个参数,props,context
props不多介绍了,值得一提的是,它是响应式的,当传入新的props,它将被更新
context是一个普通的对象,它暴露三个组件的property,可被解构 { attrs, slots, emit }
export default {
props: {
title: String
},
setup(props, context) {
console.log(props.title)
// Attribute (非响应式对象)
console.log(context.attrs)
// 插槽 (非响应式对象)
console.log(context.slots)
// 触发事件 (方法)
console.log(context.emit)
}
}
模板引用refs
由于setup不能通过this获取实例引用,refs 获取真实dom元素的用法有所改变,这个和react的用法一样,为了获得对模板内元素或组件实例的引用,需要像往常一样在setup()中声明一个ref并返回它
<template>
<div ref="node"></div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const node = ref(null);
// console.log(node)
return { node };
}
};
</script>
在v-for中
<template>
<div v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }">
{{ item }}
</div>
</template>
<script>
import { ref, reactive, onBeforeUpdate } from 'vue'
export default {
setup() {
const list = reactive([1, 2, 3])
const divs = ref([])
// 确保在每次更新之前重置ref
onBeforeUpdate(() => {
divs.value = []
})
return {
list,
divs
}
}
}
</script>
响应式系统
Vue2.x 是通过 Object.defineProperty结合订阅/发布模式实现的。
而 Vue3.0 则是采用 ES6 的 Proxy 代理来拦截对目标对象的访问。
以下内容是在组合式API Setup()内执行的
创建
1. reactive
reactive 相当于 Vue 2.x 中的 Vue.observable() API;
它接收一个普通对象然后返回该普通对象的响应式代理(Proxy);
响应式转换是“深层”的,会影响对象内部所有嵌套的属性。基于 ES2015 的 Proxy 实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象;
import { reactive } from 'vue'
// 响应式状态
const state = reactive({
count: 0
})
2. ref
接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性value
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
ref在渲染上下文时,会自动展开内部值,不需要再追加 .value
<template>
<div>
<span>{{ count }}</span>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count
}
}
}</script>
被reactive object嵌套访问更改,同样会展开内部值;
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
3. 解构reactive
es6语法解构获取我们想要的property,在reactive中无法作用
import { reactive } from 'vue'
const book = reactive({
author: 'Vue',
title: 'Vue 3 Guide',
})
let { author, title } = book
// console.log(author) 丢失
使用 toRefs 来转换为ref,这些 ref 将保留与源对象的响应式关联
import { toRefs } from 'vue'
let { author, title } = toRefs(book)
4. readonly
防止更改reactive
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// 转换copy 将导失败并导致警告
copy.count++
// 警告: "Set operation on key 'count' failed: target is readonly."
计算
computed,用法与vue2.0基本一致
import { computed } from 'vue'
const count = ref(1)
const plusOne = computed(() => count.value++);
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
监听
1. watchEffect
为了根据反应状态自动应用和重新应用副作用,我们可以使用 watchEffect 方法。它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
Vue2.x写法
computed: {
routeTitle() {
return this.$route.meta.title;
},
breadcrumb() {
return this.$route.matched.filter(data => data.meta.breadcrumb);
},
openKeys() {
return this.$route.meta.nav.parent ? [this.$route.meta.nav.parent] : []
}
}
Vue3.0写法
let routeTitle = ref("");
let openKeys = ref([]);
let breadcrumb = ref([]);
watchEffect(() => {
routeTitle.value = route.meta.title;
openKeys.value = route.meta.nav.parent ? [route.meta.nav.parent] : [];
breadcrumb.value = route.matched.filter(data => data.meta.breadcrumb);
});
停止侦听
const stop = watchEffect(() => {})
stop()
2. watch
watch是惰性的,即只有当被侦听的源发生变化时才执行回调,更具体地说明什么状态应该触发侦听器重新运行。
// 侦听一个 getter
const state = reactive({ count: 0 })
watch(() => state.count,(count, prevCount) => {
/* ... */
})
// 直接侦听ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
其他响应性API
-
isProxy 检查对象是 reactive 还是 readonly创建的代理
-
isReactive 检查对象是否是 reactive创建的响应式 proxy。
import { reactive, isReactive } from 'vue'
export default {
setup() {
const state = reactive({
name: 'John'
})
console.log(isReactive(state)) // -> true
}
}
-
isReadonly 检查对象是否是由readonly创建的只读代理
-
toRaw 返回 reactive 或 readonly 代理的原始对象。这是一个转义口,可用于临时读取而不会引起代理访问/跟踪开销,也可用于写入而不会触发更改。
-
markRaw 标记一个对象,使其永远不会转换为代理。返回对象本身。
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 嵌套在其他响应式对象中时也可以使用
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
-
unref 如果参数为 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val
-
toRef 可以用来为源响应式对象上的 property 性创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接。
-
toRefs 将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的ref。
-
isRef 检查对象是否为ref创建对象
生态系统
Vue-router 4.0
1. 兼容性改动
createRouter
无需调用new Router(),而是调用createRouter
import { createRouter } from 'vue-router'
const router = createRouter({
// ...
})
mode
- history → createWebHistory()
- hash → createWebHashHistory()
- abstract → createMemoryHistory()
import { createRouter, createWebHistory } from 'vue-router'
createRouter({
history: createWebHistory(),
routes: [],
})
2. 结合组合式API
setup内部使用路由,route 依然存在,模版内仍可使用
- useRouter
- useRoute
import { useRouter, useRoute } from "vue-router";
export default {
setup(){
//相当于 this.$router
const router = useRouter();
let handleLinkTo = name => {
router.push({ name });
};
//相当于 this.$route
const route = useRoute();
let id = route.params.id;
return {
handleRouter,
id
}
}
}
导航卫士
- onBeforeRouteLeave
- onBeforeRouteUpdate
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
export default {
setup() {
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
if (!answer) return false
})
const userData = ref()
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
}
}
useLink
Vue Router将RouterLink的内部行为公开为Composition API函数
import { RouterLink, useLink } from 'vue-router'
export default {
name: 'AppLink',
props: {
...RouterLink.props,
inactiveClass: String,
},
setup(props) {
const { route, href, isActive, isExactActive, navigate } = useLink(props)
const isExternalLink = computed(
() => typeof props.to === 'string' && props.to.startsWith('http')
)
return { isExternalLink, href, navigate, isActive }
},}
Vite
- 快速的冷启动
- 即时的模块热更新
- 真正的按需编译 代码仓库