vue2.x:
通过new Vue()创建vue根实例,从同一个vue构造函数创建的每一个根实例共享相同的全局配置
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
vue3.x:
vue3.x引入createApp一个新的全局API,调用该API返回一个应用实例
import { createApp } from 'vue';
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App).use(router).use(store).mount('#app')
由于vue3.x不存在全局根实例,那么伴随根实例的全局API就被迁移到了应用实例上。应用程序实例暴露当前全局API的子集,任何改变vue行为的API现在都被迁移到应用实例上
以下是全局API及其相应实例API的表:
2.x全局API | 3.x实例API |
Vue.config | app.config |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
如上图所示,vue2.x的main.js中的这段代码:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
在vue3.x中这样呈现:
import { createApp } from 'vue';
import App from './App.vue'
import router from './router'
import store from './store'
const app = createApp();//创建app实例
app.use(router); //注入router
app.use(store);
app.mount('#app);//挂载页面
//等同于该链式调用
createApp(App).use(router).use(store).mount('#app')
vue2.x:
多个节点时提示错误
vue3.x时,不再提示:
组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中(Vue3.x中Fragment(片断)的内容)
好处:减少标签层级,减小内存占用
建议使用方法调用或计算属性代替过滤器(呵呵。。。)
vue2.x中同时使用v-if和v-for,时v-for优先级更高;但是vue3.x中v-if优先级高于v-for ,则更容易促进该写法(虽然平时也是这样写的。。)
{{ item }}
vue2.x一直都是这样使用nextTick的
Vue.nextTick(() => {});
this.nextTick(() => {});
但是vue3.x中,全局API现在只能作为ES模块构建的命名导出进行访问,如下面所示:
据我所知,setup(){}中是没有this的;至于setup外部,。。。暂未实验过,晚点试试
//先导出
import { nextTick } from 'vue'
//再使用
nextTick(() => {});
//或者
this.nextTick(() => {});//该方式依然可行,有点懵
vue2.x中:
//vue2.x中,this.$refs.btns的取值会是一个数组,数组的内容是循环的每一个button的dom元素
vue3.x中,ref的值则不在是数组,而是让你传一个函数,通过接收该函数的形参得到一个数组,如下:
//接收的形参未循环的每一个button的dom
setBtnDoms(dom){
this.list.push(dom);
}
vue2.x | vue3.x |
bind | beforeMount |
inserted | mounted |
无 | beforeUpdate(元素本身更新之前调用) |
update | 无(update被移除) |
componentUpdated | updated |
无 | beforeUnmount(卸载元素之前调用) |
unbind | unmounted |
Vue Router4.0提供了vue3支持 ,VueRouter追随vue3抛弃了之前new VueRouter的写法,提供了createRouter的创建函数
import { createRouter,createWebHashHistory } from 'vue-router'
const router = createRouter({
//负责管理历史的属性,有三种历史可选
//hash,history,memory
history: createWebHashHistory(),
routes
});
export default router
//代码中编程式导航也是通过createRouter获取router的,route也一样
vuex4.0提供了vue3支持,唯一的变化是初始化
import { createStore } from 'vuex'
const store = createStore();
export default store
cdn方式:
vite脚手架:
npm init vite-app demo
vue-cli脚手架:
vue create demo
tips:若使用vue create demo方式创建项目未跳转到vue3选项,则可先创建vue2.x项目,然后cd到项目根目录,再进行 vue add vue-next 即可将vue2.x新建项目转化为vue3.x项目;或是升级vue-cli脚手架,则在新建项目时有vue3.x的选项
在vue2.x中使用vue3.x的API
npm install @vue/composition-api
在main.js文件中:
import VueCompositionAPI from '@vue/composition-api';
Vue.use(VueCompositionAPI);
使用时导入:
import { ref,reactive } from '@vue/composition-api';
组合API的入口,只在初始化时执行一次;函数中若返回对象,对象中的属性或方法,可在模板中直接使用
export default {
setup(){
// 非响应式数据(响应式数据:数据变化,页面跟着渲染变化)
let count = 0;
// 方法
let updateCount = () => {
console.log('updateCount');
count++;
}
// 返回一个对象
return{
//属性
count,
updateCount
}
}
}
- 在beforeCreate执行一次,此时组件并未创建
- 因组件并未创建,则this为undefined;不能用this来访问data/methods/props;所以setup函数中不能使用this
- 其余所有compositionAPI相关回调函数中也不可以使用this
示例:
export default {
beforeCreate(){
console.log('beforeCreate',this);
},
created(){
console.log('created',this);
},
setup(){
console.log('setup',this);
return{
}
}
}
执行结果:
可以看到setup中没有this;且vue3.x中的this做了处理,是一个proxy对象
tips:网上还有setup执行时机是在beforeCreate和created之间这种说法?
从上图打印效果最直观的讲,setup的执行在beforeCreate和created之前
其次,beforeCreate和created内部是有this存在的,而setup中this为undefined,这也验证了setup的执行在beforeCreate和created之前
在Vue3.x中生命周期函数发生了语义化的变化,比如beforeDestroy变更为了beforeUnmount;其他生命周期均小有变化,且其他生命周期均在setup中执行;
而beforeCreate和created还维持原本,这说明Vue3.x中本身并没考虑为beforeCreate和created留有位子,而是使用setup来代替这两者,之所以Vue3.x中还有这两个钩子函数,主要是为了向下兼容
- 一般都返回一个对象
- 返回对象中的属性会与data函数返回对象合并成为组件对象的属性
- 返回对象中的方法会与methods中的方法合成为组件对象的方法
- 如有重名,setup优先
- 不推荐混用,methods中可以访问setup提供的属性余方法;而setup中却不能访问data/methods
- setup不推荐是一个async函数:因为返回只不再是return的对象,而是promise,模板看不到return对象中的属性数据
tips:setup可不可以是一个async函数?
可以是一个async函数,但是由于是异步,使得数据不会立即给到模板,可以使Suspense标签解决该问题,代码示例如下:
在异步组件中的setup中使用async,在未得到数据前会有一个加载中的过渡效果
Suspense的使用
加载中...
参考:https://zhuanlan.zhihu.com/p/137364605
- setup(props,context) / setup(props,{attrs,slots,emit})
- props:包含props配置中声明的属性的对象,可用于父传子传值
- attrs:包含没有在props配置中声明的属性的对象,相当于this.$attrs
- slots:包含所有传入的插槽内容的对象,相当于this.$slots
- emit:用来分发自定义事件的函数,相当于this.$emit,可用于子传父传值
tips:setup中接收的props是响应式,当传入新的props时,会及时更新,由于是响应式的,所以不可以使用ES6解构,解构会消除响应式
同理,setup中声明的响应式对象也不可解构,而是返回的时候使用toRefs来进行转换
示例:
export default {
name: 'HelloWorld',
props: {
msg: String
},
setup(props,context){
console.log('props:',props);
console.log('props.msg:',props.msg);
console.log('context:',context);
return {
}
}
}
执行结果:
- 定义一个数据的响应式 ;创建一个包含响应式数据的引用(reference)对象 / ref对象
- const xxx = ref(initVal);
- js中操作:xxx.value
- 模板中操作:xxx;不需要.value
- 通常用来定义一个基本类型的响应式数据
示例:
This is an about page
{{ count }}
ref另一个作用:可以获取页面中的元素
html代码:
This is an about page
js代码:
import { ref,reactive, computed,watch,watchEffect, onMounted } from 'vue';
export default {
setup(){
//页面加载完毕,组件已经存在,获取元素
const h1 = ref(null);
onMounted(() => {
console.log('h1',h1.value.innerText);
});
return{
h1
}
}
}
- 定义多个数据的响应式
- const proxy = reactive(obj) :接收一个普通对象然后返回该普通对象的响应式代理对象
- 响应式转换是‘深层的’:会影响对象内部所有嵌套的属性
- 内部基于ES6的proxy实现,通过代理对象操作源对象内部数据都是响应式的
示例:
This is an about page
{{ user }}
执行结果:
执行前显示:
执行后显示:
- 是vue3 compositionAPI中两个最重要的响应式API
- ref通常用来处理基本类型数据,reactive用来处理对象(递归深层响应式)
- 若用ref对象/数组,内部则自动将对象/数组转换为reactive的代理对象
- ref内部:给value属性添加getter/setter来实现对数据的劫持
- reactive内部:使用proxy来实现对对象内部所有数据的劫持,并通过Reflect(反射)操作对象内部数据
- ref数据操作:在js中.value;在模板中在不需要(内部解析模板时会自动添加.value)
tips:ref内部value的劫持是如何来实现的?
示例:
This is an about page
{{ user }}
执行结果:
ref若是对象,则内部经过reactive的处理,形成了proxy类型对象
tips:ref和reactive有什么区别?
- 把一个响应式对象转换成普通对象,该普通对象的每个property都是一个ref
- 应用:当从合成函数返回响应式对象时,使用toRefs可以在不丢失响应式的情况下对返回的对象进行分解
- 由于reactive对象取出的所有属性值都是非响应式的;利用toRefs可以将一个响应式reactive对象的所有原始属性转换为响应式的ref属性
示例:
This is an about page
代码书写形式与vue2.x不同,其余区别不大
示例:
This is an about page
执行结果:
代码书写形式与vue2.x不同,其余区别不大
//监听一个值
watch(user,(val) => {
fullName3.value = user.name + '++' + user.bro.name;
});
深监听/初始化执行
watch(user,(val) => {
fullName3.value = user.name + '++' + user.bro.name;
},{
immediate: true,//默认执行一次
deep: true //深监听
});
监听多个数据
当监听非响应式数据时,该数据需以函数返回值的形式呈现
watch([() => user.name,() => user.age],() => {
console.log('zzzz');
},{
immediate: true,
deep: true
});
watchEffect
本身默认执行一次;无需指定监听对象,有数据变动则自动监听
watchEffect(() => {
fullName3.value = user.name + '$$' + user.bro.name;
});
与vue2.x生命周期相对应的组合式API:
vue2.x | vue3.x |
beforeCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestory | onBeforeUnmounted |
destoryed | onUnmounted |
errorCaptured | onErrorCaptured |
新增钩子函数:
用于调试
- onRenderTracked
- onRenderTriggered
- shallowReactive:只处理对象内最外层的响应式(浅响应式)
- shallowRef:只处理value的响应式,不对对象进行reactive处理
示例:
tips:单独修改数据时,shalowReactive和shallowRef如上述所言;可多数据修改时,shalowReactive和shallowRef依然深响应式,为什么?
readonly
- 深度只读数据
- 获取一个对象(响应式或纯对象)或ref对象并返回原始代理的只读代理
- 只读代理是深层的:访问的任何嵌套property也是只读的
shallowReadonly
- 浅只读数据
- 创建一个代理,使其自身的property为只读,但不执行嵌套对象的深度只读转换
示例:
tips:单独修改数据时,readonly与shallowReadonly如上述所言;可多数据修改时,shallowReadonly依然无法深度修改,为什么?
toRow
- 返回reactive或readonly方法转换成响应式代理的普通对象
- 这是一个还原方法,可用于临时读取,访问不会被代理/追踪,写入时也不会触发界面更新
markRow
- 标记一个对象,使其永远不会转换为代理;返回对象本身
- 应用场景:有些值不应被设置为响应式,例如复杂第三方类实例或Vue组件对象;当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能
示例:
为源响应式对象上的某个属性创建一个ref对象,二者内部操作的是同一个数据值,更新时二者是同步的(ref不会同步)
应用:当要将某个prop的ref传递给复合函数时,toRef很有用
示例:
创建一个自定义的ref,并对其依赖项跟踪和更新触发显示控制
示例:
这里使用了自定义hook函数(Vue3.x中的概念),这里穿插一下自定义hook的东西:
自定义hook函数
- 使用Vue3.x组合API封装的可复用的功能函数
- 自定义hook类似于Vue2.x中的mixin(简单来说,其实就是封装公共的函数方法或者可以分离提取的功能,自定义hook叫做功能模块感觉更适合一点)
- 好处:代码可复用,简洁
customRef
{{ keyword }}
provide和inject提供依赖注入,实现跨层级组件(祖孙)间通信
示例:
这里以父子间传值为例
父组件:
provide与inject
当前颜色:{{color}}
子组件:
{{ msg }}
For a guide and recipes on how to configure / customize this project,
check out the
vue-cli documentation.
Installed CLI Plugins
Essential Links
Ecosystem
效果:
- isRef:检查一个值是否为一个ref对象
- isReactive:检查一个对象是否由reactive创建
- isReadonly:检查一个对象是否是由readonly创建的只读代理
- isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理
Teleport提供了一种干净的方法,让组件的html在父组件界面外的特定标签(很可能是body)下插入显示;Teleport上的to属性指定特定标签
通俗来讲,就是给组件指定父元素
示例:
子组件:
//HelloWorld组件
弹窗本体
父组件:
效果:
由于该html代码本身位于body中,效果不明显,也可尝试将该标签瞬移到html中
允许应用程序在等待异步组件时渲染一些后备内容,可以得到一个平滑的用户体验
通俗来讲,就是用Suspense这个标签来实现过渡异步请求的效果
示例:
子组件:
//HelloWorld组件
AsyncComponent 子组件
{{ msg }}
父组件:
Suspense的使用
加载中...