【寒假自学之路】Vue2到Vue3的迁移学习
安装
- 通过脚手架Vite【vue的轻量级前端开发与构建工具】
npm init vite hello-vue3 -- --template vue
- 通过vue-cli脚手架
vue create hello-vue3
- 通过CDN引入
<script src="https://unpkg.com/vue@next"></script>
新特性
- 组合式API
- Teleport
- 片段
- 触发组件选项
- 来自
@vue/runtime-core的createRendererAPI,用于创建定义渲染器 - 单文件组合式API语法糖(
<script setup>) - 单文件组件状态驱动的CSS变量(
<style>中的v-bind) - SFC
<style scoped>现在可以包含全局规划或只针对插槽内容的规则 - Suspense(实验阶段)
vue2到vue3中的变化
1. vue实例创建
在vue3中,没有组件构造器的概念
//vue 2
const app = new Vue({})
//vue 3
const app = createApp(App)
2. vue-router的使用
需要配置vue-router4.0以上版本
//必须在app.mount('#app');之前
app.use(router); // 使用的是router的实例,而不是构造器。
//router.js
import { createRouter, createWebHashHistory } from "vue-router";
import Ref from './components/Ref.vue';
const routes = [{
path: '/ref',
component: Ref,
}];
const router = createRouter({
history: createWebHashHistory(),
routes,
})
export default router;
3. vuex的使用
需要配置vuex4.0以上版本
// 必须在app.mount('#app');之前
app.use(store);
import { createStore } from 'vuex'
const store = createStore({
state: {
message: "this is the message from vuex store",
}
})
export default store;
4. v-for中的ref数组
在vue2中,使用的
refattribute会用ref数组填充对应的$refproperty。如果存在v-for嵌套的情况下,行为不明确且效率低
在vue3中不再自动创建
$ref数组,需要从单个绑定获取多个ref,即将ref绑定到一个灵活的函数上
<ul>
<li v-for='movie in movies'
:key='movie.id'
:ref="setMovieVNs">
{{ movie.name }}</li>
</ul>
export default {
name: "Ref",
data() {
return {
movies:[{
id:1,
name:"长津湖"
},{
id:2,
name:"铁道游击队"
},{
id:3,
name:"金刚川"
},],
movieNVs:[],
}
},
methods:{
setMovieVNs(el){ //需要手动将创建的虚拟节点,放入自创数组中。
if(el){
this.movieNVs.push(el);
}
},
showRef(){
console.log(this.movieNVs);
}
}
}
5. 异步组件
概述:
- 新的
defineAsyncComponent,用于显式地定义异步组件 component选项被重命名为loader- Loader函数本身不再接收
resolve和reject参数,且必须返回一个Promise
什么是异步组件?
就是当有需要的时候,才会去进行加载,并且把结果缓存起来,供未来重载使用。
一般配合路由使用(Vue Router 2.4.0+)。但路由的懒加载,不能使用
defineAsyncComponent
vue2
const AsyncHomeComponent = () => import('./src/App.vue'); //简单写法
// 高阶写法
const AsyncHomeComponent = () => {
// 需要加载的组件
component: import ('./src/App.vue'),
// 正在加载的时候响应的组件
loading: LoadingComponent,
// 加载失败的时候响应的组件
error: ErrorComponent,
// 显示加载时组件的延时时间。(默认200ms)
delay: 200,
// 超时时间,超时则显示error组件。(默认`Infinity`)
timeout: 3000,
}
vue3
import { defineAsyncComponent } from 'vue';
const AsyncHomeComponent = defineAsyncComponent({
// 需要加载的组件
laoder: () =>
import ('./src/App.vue'),
// 正在加载的时候响应的组件
loading: LoadingComponent,
// 加载失败的时候响应的组件
error: ErrorComponent,
// 显示加载时组件的延时时间。(默认200ms)
delay: 200,
// 超时时间,超时则显示error组件。(默认`Infinity`)
timeout: 3000,
})
6. attribute强制行为
不影响绝大多数开发人员
attr='false’并不会移除,需要attr=null 或 attr=undefined
7. $attrs包含了class和style
$attrs包含了所有attribute
vue2:原本
class和style会被添加到根元素中。因为$attr不包括class和style。需要特殊处理
App.vue
<template>
<div id="app">
<router-link to="/ref">ref测试</router-link>
<router-view></router-view>
<attr-test id="myAttr" class="myAttr" style="color:red" />
</div>
</template>
<script>
import AttrTest from './components/AttrTest.vue'
export default {
components: { AttrTest },
}
</script>
AttrTest.vue
<template>
<div>
<p>AttrTest</p>
<input type="text" v-bind="$attrs">
</div>
</template>
<script type="text/javascript">
export default {
name: "AttrTest",
inheritAttrs: false
}
</script>
vue2渲染结果:
<div class="myAttr" style="color: red;">
<p>AttrTest</p>
<input type="text" id="myAttr">
</div>
vue3渲染结果:
<div>
<p>AttrTest</p>
<input type="text" id="myAttr" class="myAttr" style="color: red;" >
</div>
8. $children移除
建议使用
$refs访问子组件
9. 自定义指令
进行了钩子命名的修改或者添加新的钩子
<p v-heighlight="'yellow'">高亮显示此文本</p>
在vue2中,有如下钩子:
- bind:指令绑定到元素后执行一次
- inserted:元素插入父DOM后执行一次
- update:元素更新,子元素未更新时,调用一次
- componentUpdated:组件和子元素都被更新,调用一次
- unbind:指令移除,调用一次
Vue.directive('highlight',{
bind(el,binding,vnode){
el.style.background = binding.value;
const vm = vnode.context; //vue2中获取实例的方法
}
})
在vue3中,对钩子做了一定的修改:
- created:元素的attribute或事件监听器被应用之前调用
- beforeMount:绑定到元素后执行一次【原来的bind】
- mounted:元素插入父DOM后执行一次【原来的inserted】
- beforeUpdate:元素本身被更新之前,调用一次
- updated:元素更新,子元素未更新,调用一次
- beforeUnmount:指令被移除前,调用一次
- unmounted:指令移除,调用一次。【原来的unbind】
Vue.directive('highlight',{
beforeMount(el,binding,vnode){
el.style.background = binding.value;
const vm = binding.instance; //vue3中获取实例的方法
}
})
10. 自主定义元素
使用如
Web Components API定义的自主定义元素<new-button></new-button>,如何将其变为vue的自定义元素
vue2:
Vue.config.ignoredElements = ['new-button']; //声明外部自定义元素
vue3:
const app = createApp({});
app.config.compilerOptions.isCustomElement = tag => tag === 'new-button'
11. 定制内置元素【is的表达不同】
对于<button is='new-button'>而言
vue2渲染后:
<new-button></new-button>
vue3:
document.createElement('button',{is:'new-button'}); //通过这个来渲染原生的button
12.特殊DOM内模板解析问题
在ul、ol、table、select中,他们的子元素时有所限制的,只能是li、tr、option
在vue2中,使用is来绕过限制
<table>
<tr is="blog-post-row"></tr>
</table>
在vue3中,由于is的行为变化,将元素解析为vue组件需要添加
vue:前缀
<table>
<tr is="vue:blog-post-row"></tr>
</table>
13.DATA选项
vue3中,只接受
function定义、返回object的data选项【换言之,不管子组件还是父组件,都需要用函数的形式定义选项data】
14.Mixin合并行为
个人感觉,不能使用Mixin,可读性太差了.
vue3中的Mixin以组件内数据为主,比如组件中说user对象具有id属性,而混合的对象中既有id也有name
对于vue2而言,会将二者融合,冲突数据以组件为主
对于vue3而言,不会融合,组件内有什么属性,就是什么.
15.新增emits选项
与
props类似,emits可以用于声明组件可以触发那些事件,同时也具有验证器.
强烈推荐使用
emits,因为移除了.native修饰符.任何未在emits中声明的事件监听器都会被算入到$attrs中,默认绑定在根节点上.
16.移除事件API
- $on
- $off
- $once
被移除
17.移除过滤器filters
建议采用computed 或 methods替代
18.新增片段
支持多根节点(片段)
19.函数创建组件
vue2
export default{
functional:true,
props:['level'],
render(h,{props,data,children}){
return h(`h${prorps.level}`,data,children);
}
}
或
<template functional>
<component
:is="`h${props.level}`"
v-bind="attrs"
v-on="listeners"
/>
</template>
<script>
export default {
props: ['level']
}
</script>
vue3:
- 函数创建,h变成了全局变量,attrs\slots位于context中.
import {h} from 'vue';
const Heading = (props,context)=>{
return h(`h${props.level}`, context.attrs, context.slots);
}
Heading.props = ['level'];
export default Heading;
- 单文件,不需要写functional;
$attrs囊括了几乎所有attribute.包括listeners
<template>
<component
v-bind:is="`h${$props.level}`"
v-bind="$attrs"
/>
</template>
<script>
export default {
props: ['level']
}
</script>
20.全局API
- Vue.config
被
app.config替代
- Vue.config.productionTip > 被移除
大多数脚手架已经正确配置环境了,没有必要.
- Vue.config.ignoredElements
被
app.config.isCustomElement替代
- Vue.component
被
app.component
- Vue.directive
被
app.directive替代
- Vue.mixin
被
app.mixin替代,强烈推荐使用组合API代替mixin
- Vue.prototype
被
app.config.globalProperties替代
- Vue.extend > 被移除
其实可以使用
defineComponent或defineAsyncComponent来替代
21. 全局API Treeshaking(没看懂,先放着)
22. 移除内联模版Attribute
vue2中
<my-component inline-template>
<div>
<p>它们将被编译为组件自己的模板,</p>
<p>而不是父级所包含的内容。</p>
</div>
</my-component>
23. key Attribute
在vue2中,需要手动添加key,相当于给一个唯一标识.提高效率;
在vue3中,会自动添加key;key应该被设置在
<template>中.
24. 按键修饰符
- 不支持数字作为
v-on的修饰符 - 不支持
config.keyCodes
在vue3中,可以使用kebab-cased或者使用符号代替
<!-- 回车键提交 -->
<input v-on:keyup.enter='submit'>
<!-- 会同时匹配Q和q -->
<input v-on:keyup.q='quit'>
关于符号问题,除了
",',/,=,>,和.之外,其他估计都可以直接用符号代替如
<input v-on:keypress.,='commaPress'>
25. 移除$listeners
listenersr融合到listenersr融合到listenersr融合到attrs中
<!-- vue2 -->
<input type="text" v-bind="$attrs" v-on="$listeners" />
<!-- vue3 -->
<input type="text" v-bind="$attrs"/>
26. 挂载元素问题
vue2中,被挂载元素会被替代
vue3中,挂载的模板会渲染到被挂载元素中,当作
innerHTML
27. 移除propsData
在vue2中,可以使用propsData给props传值
const Home = Vue.extend({
props:['title'],
render(h){return h('div',title)}
});
new Home({
propsData:{
title:'NEW PASSAGE'
}
})
在vue3中,可以使用
createApp的第二个参数:
import { createApp , h } from 'vue';
const Home = createApp({
props:['title'],
render(){return h('div',title)}
},{
title:'NEW PASSAGE'
})
18. prop的默认函数中不能访问this
- 组件接收到原始的prop将作为参数传递给默认函数
- inject API可以在默认函数中使用
import { inject } from 'vue';
export default{
props:{
theme:{
default(props){
return inject('theme','default-theme');
}
}
}
}
19.Provide/Inject
provide(name,value)用于上层组件向下抛出变量
// 常适用于于组合API中
import { provide } from 'vue';
export default{
setup(){
provide('id','flevena'); //provide(抛出的名字,抛出的值)
provide('score',{
math:90,
english:90,
average:90
})
}
}
inject(name[,default-value])用于下层组件接收上层组件抛出的变量。以此实现了跨级组件的通信。
import { inject } from 'vue';
export default{
setup(){
const studentId = inject('id',1); // 接收抛出的id,并将其值设为1
const studentScores = inject('score');
return {
studentId,
studentScores
}
}
}
为了使得provide值和inject值之间具有响应性,可以在provide值时候,使用ref或reactive
import { provide , reactive , ref } from 'vue';
export default{
setup(){
const location = ref('North Pole');
const geolocation = reactive({
longitude:90,
latitude:135
})
provide('location',location);
provide('geolocation',geolocation);
}
}
但是provide的值最好只能在提供者组件内部进行修改,其他子组件最好只能阅读。这样确保了数据的私密性。【java的setter、getter逻辑】
import { provide , reactive , ref , readonly } from 'vue';
export default{
setup(){
const location = ref('North Pole');
const geolocation = reactive({
longitude:90,
latitude:135
});
// 提供修改的方法
const updateLocation = (newLocation) =>{
location.value = newLocation;
}
// 抛出变量和方法
provide('location',readonly(location));
provide('geolocation',readonly(geolocation));
provide('updateLocation',updateLocation);
}
}
20. 渲染函数API
h变成了全局导入
import { h } from 'vue',不再作为render的参数
由于
render不再接收参数,所以一般h用于setup()内部。因为可以访问作用域内声明的响应式状态和函数以及setup()的参数
import { h, reactive } from 'vue';
export default{
setup(props,{solts,attrs,emit}){
const state = reactive({
count:0,
});
function increment(){
state.count++;
}
//返回渲染函数
return ()=>{
h('div',{
onClick:increment
},state.count);
}
}
}
21. 注册组件
vue2中
Vue.component('new-button',{});
export default(){
render(h){
return h('new-button');
}
}
vue3中,由于VNode是上下文无关的,所以不能用字符串找到组件。需要
resolveComponent方法解决
import { h, resolveComponent } from 'vue';
export default{
setup(){
const newButton = resolveComponent('new-button');
return ()=>h(newButton);
}
}
22. 插槽统一
this.$slots,插槽作为函数公开.- 移除
this.$scopedSlots
在vue2中,
//在LayoutComponent中,创建两个作用域插槽,名为header和content。
h(LayoutComponent,[
h('div',{slot:'header'},this.header),
h('div',{slot:'content'},this.content)
])
// 引用作用域插槽:
this.$scopedSlots.header;
vue3中
h(LayoutComponent,{},{
header:()=> h('div',this.hader),
content:()=> h('div',this.content)
// vue3引用
this.$slots.header();
})
23. 过渡的class名更改了
v-enter 改为 v-enter-from
v-leave 改为 v-leave-from
v-enter-active思路:从
v-enter-from到v-enter-to
.v-enter-from,
.v-leave-to{
opacity:0
}
v-leave-active思路:从
v-leave-from到v-leave-to
.v-leave-from,
.v-enter-to{
opacity:1
}
24. Transition
<transition>作为更根节点的组件,从外部切换时将不再触发过渡效果。
25. Transition Group
<transition-group>不再默认渲染根元素,但仍然可以用tagattribute 创建根元素。
26. 移除v-on.native
可以使用**新增选项
emits**定义允许被触发的事件
27. v-if和v-for的优先级问题
vue2中,
v-for优先于v-if
vue3中,
v-if优先于v-for
28. v-bind合并行为
绑定顺序会影响渲染结果
- vue2:
<!-- 模板 -->
<div id='red' v-bind="{id:'blue'}"></div>
<!-- 结果 -->
<div id='red'></div>
- vue3:具有顺序的,后面会覆盖前面
<!-- 模板 -->
<div id='red' v-bind="{id:'blue'}"></div>
<!-- 结果 -->
<div id='blue'></div>
29. hook: 改为了 vnode-
<!-- vue2 -->
<child-component @hook:updated='onUpdated'>
<!-- vue3 -->
<child-component @vnode-updated='onUpdated'>
30. 侦听数组
侦听数组的时候,只有数组被替代才会触发回调函数。
在vue3中,添加
deep:true选项,数组改变就会触发回调函数
watch: {
bookList: {
handler(val, oldVal) {
console.log('book list changed')
},
deep: true
},
}
Composition API
特点:
- Composition API是基于函数的,可以有效地组织和编写可重用代码(可以通过
provideinject共享变量、函数) - 通过将共享逻辑分离为功能来提高代码的可读性
- 实现代码分离
- 在Vue中更好地使用TypeScript
setup() 选项实现组合式API;组件创建前,props被解析后,setup就会被作为组合式API入口
-
注意一下几点:
- 由于进入
setup()的时候,组件还未被创建,所以不能通过this获取data、computed、methods、refs的property setup()是一个接收props和context的函数setup()返回的内容,会暴露给组件的其他部分(计算属性、方法、生命周期钩子等待)和组件的模板
- 由于进入
-
props,是setup()的第一个参数- props的是响应式的,有新值,就会被更新
- 因为props是响应式的,因此不能被
ES6解构 - 如果需要对props进行结构,需要引入并使用
toRefs(props)
-
context,是setup()的第二个参数(包含attrsslotsemitsexpose)context是一个普通的JavaScript对象,里面包含:- attrs
- slots
- emit
- expose(后面会分析)
context是一个对象,所以可以解构- 建议对
context进行解构,方便以attrs.x、slots.x的形式引用property. - 不能对解构的
attrs等,再进行解构。
-
在模板中,使用
setup()中返回的值,不需要.value,因为在模板中访问时是被自动浅解包的 -
setup()的返回内容,可以是内部定义的值、方法。还可以是渲染函数。
import { ref , h } from 'vue';
export default {
setup(props,context){
const count =ref(0);
const increment = ()=> ++count.value;
return ()=> {
h('div',count.value);
}
}
}
问题来了,要是返回的是一个
渲染函数,里面的函数怎样通过模板暴露给父组件?答案就是:expose函数
import { ref , expose , h } from 'vue';
export default {
setup(props,context){
const count =ref(0);
const increment = ()=> ++count.value;
expose({
increment, // 这个 increment 方法现在将可以通过父组件的模板 ref 访问。
})
return ()=> {
h('div',count.value);
}
}
}
ref()和reactive() 都可以将setup()中的数据创建一个响应式引用。变量的传递变成了传递引用
ref()推荐定义基本数据类型reactive()推荐定义复杂的数据类型
setup() 内生命周期钩子
相较于选项里面的钩子,
setup()中只需要加一个on就可以
本来应该还有
beforeCreatecreated,由于setup()正处于这两个周期中,所以在setup()中被取消了。而其中的逻辑代码可以写在setup()中。
- onBeforeMount
- onMounted
- onBeforeUpdate
- onUpdated
- onBeforeUnmount
- onUnmounted
- onErrorCaptured
- onRenderTracked
- onRenderTriggered
- onActivated
- onDeactivated
这些函数接收一个
回调函数 callFunction,当钩子被组件调用的时候,该回调函数会被执行。
watch响应式更改
- 注意以下几点:
watch需全局导入watch接收三个参数- 需要侦听的响应式引用或getter函数
- 回调函数
- 可选的配置选项【deep:true】
- 侦听单个数据源
import { ref , watch } from 'vue';
export default{
setup(){
const count = ref(0);
watch(count,(newValue,oldValue)=>{
// 进行操作
})
}
}
- 侦听多个数据源【使用数组】
const firstName = ref('')
const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues)
})
// 单个修改
firstName.value = 'John' // logs: ["John", ""] ["", ""]
lastName.value = 'Smith' // logs: ["John", "Smith"] ["John", ""]
// 一次性多个修改
const changeValues = ()=>{
firstName.value = 'John'
lastName.value = 'Smith'
// 打印 ["John", "Smith"] ["", ""]
}
- 侦听数组、对象
由于数据是响应的,要求有一个由值构成的副本
const numbers = reactive([1,2,3,4]);
watch(
()=>[...numbers], //getter函数
(numbers,prevNumbers)=>{
console.log(numbers,prevNumbers);
}
)
numbers.push(5); //logs: [1,2,3,4,5] [1,2,3,4]
对于深度嵌套对象或数组,需要添加
deep:true的选项
const state = reactive({
id: 1,
attributes: {
name: '',
}
})
watch( // 无效,没有deep不能修改深度嵌套的对象
() => state, //getter函数
(state, prevState) => {
console.log('not deep', state.attributes.name, prevState.attributes.name)
}
)
watch( // 有效
() => state, //getter函数
(state, prevState) => {
console.log('deep', state.attributes.name, prevState.attributes.name)
},
{ deep: true }
)
state.attributes.name = 'Alex' // 日志: "deep" "Alex" ""
为了完全深度侦听深度嵌套的对象或数组,需要使用外部工具进行深度拷贝
import _ from 'lodash'
const state = reactive({
id:1,
attributes:{
name:'',
}
});
watch(
() => _.cloneDeep(state), //深度拷贝
(state,prevState) =>{
console.log(state.attributes.name, prevState.attributes.name);
}
)
state.attributes.name = 'Alex' // 日志: "Alex" ""
computed函数
允许在组件外部创建计算属性。
import { ref , computed } from 'vue';
const count = ref(0);
const twiceTheCount = computed( ()=> count.value * 2 );
coute.value++;
console.log(count.value); //1
console.log(twiceTheCount.value); //2
组合API的总结
个人理解,其实就是把组件内的
选项化为函数,将然后将一些通用的、共享的一些数据、方法提取出来。实现代码的复用、分离。
通过一个类似于钩子函数的setup()进行整合,抛出。
更多推荐
所有评论(0)