创建实例
-
vue2 :引入vue构造函数,主要依靠render( (creatElement) => {})创建app实例对象
//引入vue import Vue from 'vue' //引入App import App from './App.vue' //创建vm new Vue({ el:'#app', render: *h* *=>* h(App), router:router })//.$mount('#app')
-
vue3:按需引入,直接用引入的createApp创建实例对象
//引入的不再是vue构造函数,而是叫createApp的工厂函数 import { createApp } from 'vue' import App from './App.vue' //创建应用实例对象-app(类似于v2中的vm,但app比vm更轻)并挂载 createApp(App).mount('#app')
响应式原理
-
Vue2:
实现原理:
- 对象类型:通过Object.definrProperty()对属性的读取、修改进行拦截(数据劫持)。
- 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)
存在的问题:
-
新增属性、删除属性,界面不会更新
新增属性:this.person.sex=‘女’ 界面不更新的,如果想让它自动更新 那么需要写成如下:
this.$set(this.person,‘sex’,‘女’) 或者 引入Vue,然后 Vue.set(this.person,‘sex’,‘女’)
删除属性:delete this.person.sex 属性可以删除,界面不更新,也是需要改变写法:
this.$delete(this.person,‘sex’) 或者 引入Vue, Vue.delete(this.person,‘sex’)
-
直接通过更改下标修改数组,界面不会自动更新
修改数组同理:this.person.hobby[0]=‘学习’ ,不更新,同样的方法:
this.$set(this.person.hobby,0,‘学习’) 或者 引入Vue,然后 Vue.set(this.person.hobby,0,‘学习’) 或者 this.person.hobby.splice(0,1,‘学习’)
模拟Vue2响应式实现原理:
/* let p = {} Object.defineProperty(p,'name',{ configurable:true, get(){ //有人读取name时调用 return person.name }, set(value){ //有人修改name时调用 console.log('有人修改了name属性,我发现了,我要去更新界面!') person.name = value } }) Object.defineProperty(p,'age',{ get(){ //有人读取age时调用 return person.age }, set(value){ //有人修改age时调用 console.log('有人修改了age属性,我发现了,我要去更新界面!') person.age = value } })
-
Vue3:
实现原理:
- 通过Proxy(代理):拦截对象中任意属性的变化,包括增删改查
- 通过Reflect(反射):对源对象的属性进行操作
模拟Vue3响应式实现原理:
const p = new Proxy(person,{ //有人读取p的某个属性时调用 get(target,propName){ console.log(`有人读取了p身上的${propName}属性`) return Reflect.get(target,propName) }, //有人修改p的某个属性、或给p追加某个属性时调用 set(target,propName,value){ console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`) Reflect.set(target,propName,value) }, //有人删除p的某个属性时调用 deleteProperty(target,propName){ console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`) return Reflect.deleteProperty(target,propName) } })
一个小知识:如果我们用Object.defineProperty去给一个对象添加属性,如果重复添加,那么代码就会直接崩了;然而用映射Reflect.defineProperty去添加,不会报错,只是通过返回值true或者false来告诉是否操作成功,后面的代码依然能继续执行
生命周期函数
-
vue2:
- 有了Vue实例对象就先beforeCreate
- created完之后去判断有没有绑定根组件,有没有template模板,如果有就继续走挂载流程beforeMount和mouted,如果没有就停滞了,等到$mount()了再走
- 组件销毁要走beforeDistroy和distroyed
-
vue3:
-
创建实例对象之前就要去检查有没有创建const app = createApp(App),有没有挂载app.mount(’#app’),如果都符合,这时候才开始创建实例对象
-
后面就无需再去判断挂载了,创建完直接判断有没有template模板就进入挂载流程
-
组件不销毁了,变成了卸载beforeUnmount和unmounted
-
.Vue3.0也提供了Composition API形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
如果要用组合式API,是需要import引入的。也就是挂载、更新、卸载的钩子前都加一个on
-
beforeCreate===>setup()
-
created =======> setup()
如果把beforeCreate当作一个配置项写,那么setup的执行是先于beforeCreate的,但是如果想把它当成组合式API写进setup里,则setup里没有这两个API
-
beforeMount ===> onBeforeMount
-
mounted =======> onMounted
-
beforeUpdate===> onBeforeUpdate
-
updated =======> onUpdated
-
beforeUnmount ==> onBeforeUnmount
-
unmounted =====> onUnmounted
-
-
全局API的转移
-
Vue 2.x有许多全局API和配置。
例如:注册全局组件、注册全局指令等。//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: ' <button @click-"count++">Clicked {{count}} times</button> }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
Vue3中对这些API做出了调整,将全局的API调整到应用实例app上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rin2E2EN-1650199822736)(E:\markdown\前端\csdn\20220416_1.png)]
API风格的改变
-
vue2 Options API(选项式API)存在的问题:
使用传统的Options API时,新增或者修改一个需求,就需要分别在data、methods、computed、watch等里面不断地寻找、修改。如果代码量大的时候,修改起来就很痛苦。
-
vue3中 Composition API(组合式API)的优势:
更加优雅的组织我们的代码、函数,让相关功能的代码更加有序的组织在一起,都写在setup里面。甚至可以把他们都变成一个个hook函数,修改那部分就可以直接去对应的文件里修改。把定义数据、方法这部分也添加了模块化封装的思想
根标签
- vue2中组件必须有一个根标签
- Vue3中组件可以没有根标签,内部会自动将多个标签包含在一个Fragment虚拟元素中。
较常用的其他改变
-
data选项应始终被声明为一个函数
-
过度类名的更改,就是在开始的那个位置上加了一个-from
- Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
- Vue3.x写法
v-enter-from, .v-leave-to { opacity: e; } .v-leave-from, .v-enter-to { opacity: 1; }
-
移除了keyCode作为v-on的修饰符,同时也不支持config.keyCodes了(自定义按键别名,别名的定义也是依赖keyCode的,所以一并移除了)
-
移除了v-on.native修饰符
父组件中绑定事件:
<my- component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
子组件中声明自定义事件:子组件emits里面接收的就是自定义事件,不接收就默认是原生事件
<script> export default { emits: ['close'] } </script>
上述为讲课中所整理的部分内容,下面再自己去看看官方文档,来总结一些其他的。
从头到尾读文档
1. 创建实例对象
-
vue2:引入Vue,创建一个实例对象并挂载,渲染的时候
render
函数若存在,则 Vue 构造函数不会从template
选项或通过el
选项指定的挂载元素中提取出的 HTML 模板编译渲染函数。在脚手架中没有render是运行不下去的。因为脚手架里面引入的vue是残缺版的vue。而且我在里面试了,template要搭配el一起使用,而el绑定的标签中写了内容是可以不需要template的。但是有template优先显示template的内容
-
vue3:每个 Vue 应用都是通过
createApp
函数创建一个新的 应用实例,传入根组件,挂载然后渲染二者的实例对象的一个对比
vue2:
vue3:
从图上我们可以看出,vue2是一个Vue的实例对象,而vue3是一个经过Proxy数据劫持过的对象,而且里面只有hanler和target两个属性,里面分别包含着其他。vue3看起来更优雅且简洁了。vue2里面的数据等都是经过加工变成_开头的来赋予get和set,数据表面看经过了加工复制,而vue3通过数据劫持直接就加好了get与set。
这部分都是自己分析的,有不对的地方欢迎指正。
2.数据响应式
-
vue2中,data中写的数据都是响应式的,后追加的数据不是响应式的,可以通过数据代理使后添加的数据为响应式。通过getter和setter实现响应式。
Object.defineProperty(obj2,'x',{ get(){ return obj.x }, set(value){ obj.x = value } })
-
vue3中,只有通过ref()定义的基本类型数据(对象使用ref定义也是响应式的,但这是不规范的)和通过reactive()定义的对象才是响应式的。所以后期直接往对象里添加属性还是响应式的。在组件挂载后才能访问 ref。通过proxy实现响应式。
3.生命周期函数
-
vue2中new了一个Vue(),那么就开始创建了,判断没有绑定el,那就不能走下去进行挂载了。只有绑定了el才可以往下走。
-
最后两个生命周期函数为销毁:beforeDestroy和destroyed。
-
生命周期函数的位置和data,methods等平级。
-
-
vue3中要先判断有没有创建实例对象并绑定,只有都完成了才会开始创建,然后挂载。
-
最后两个生命周期函数改成了卸载:beforeUnmount和unmounted
-
官网最新的写法是
<script setup>
,这样把生命周期函数写在script里面就需要在每个前面加on,比如onMounted
。也可以把setup写在script里面,那么这样就有两种写法:写在setup里加on,或者和setup平级,写法和以前一样。
-
4.侦听器
- vue2中侦听器默认只进行浅层次监听,只监听传入的那一层,深层监听和立即监听要配置deep和immediate
- vue3中如果给侦听器传入响应式对象,就默认对里面的属性进行了深层次监听。但是如果是属性里面还是对象,那么就需要自己配置第三个参数{deep:true}了。第三个参数为配置项。
在事件处理中,vue3废弃了按键码,vue2的后面版本应该也废弃了的
5.组件
- vue2中要求每个组件必须只有一个根元素,否则报错
- vue3中可以写多个元素,他们会被自动包裹在一个Fragment标签中。
6.组件注册
- vue2中全局注册使用
Vue.component('component-a', { /* ... */ })
- vue3中全局注册使用
app.component('MyComponent', MyComponent)
可链式调用
7.自定义事件
- vue2中如果在组件的根元素上监听原生事件,需要.native修饰符
- vue3中,只有emits接收的事件才被认为是自定义事件,所以不再需要.native
8.过渡——transition的类名改变。
-
vue2中,从开始位置直接是v-enter / v-leave
-
vue3中,开始位置改成了v-enter-from / v-leave-from
Vue3新增
1.由于多了<script setup>
而产生的新写法
-
props:组件内多出了一种接收方法:
const props = defineProps(['foo'])
,这样接收和直接写在props里是一样的。 -
emits:组件要触发的事件可以显式地通过
defineEmits()
宏来声明。const emit = defineEmits(['inFocus', 'submit'])
与emits: ['inFocus', 'submit']
相同 -
useAttrs:使用
useAttrs()
API 来访问一个组件的所有透传 attribute。useAttrs需要引入。
2.Attribute 继承
“透传 attribute”是传递给组件的 attribute 或者 v-on
事件监听器,但并没有显式地声明在所接收组件的 props 或 emits 上。个人理解就是在父组件使用子组件时,给组件添加了class、id、style等,这些会和组件原有的合并。
- 透传的 attribute 不会包含
<MyButton>
上声明过的 props 或是针对emits
声明事件的v-on
侦听函数,换句话说,声明过的 props 和侦听函数被<MyButton>
“消费”了。- 透传的 attribute 若符合声明,也可以作为 props 传入
<BaseButton>
。
上面是官方给的注意点,结构应该是这样的 App —— MyButton —— BaseButton。也就是说,App在内部使用MyButton 这个组件时,给他传递了一些class、v-on等,如果MyButton 在 props和 emits 中没接收的部分,也会传递给BaseButton孙组件,当然BaseButton里面也可以使用props去接收。
如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false
。
应用场景:最常见的需要禁用 attribute 继承的场景就是 attribute 需要应用在根节点以外的其他元素上。通过设置 inheritAttrs
选项为 false
,你可以完全控制透传进来的 attribute 如何应用。
这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs
访问到。(这个类似于vue2,接收的在props里面取,没接收的在attrs里面取,但是我不知道vue2能不能传给第三代,所以暂且先放在这里。如果有人指正就修改)
- 和 props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像
foo-bar
这样的一个 attribute 需要通过$attrs['foo-bar']
来访问。- 像
@click
这样的一个v-on
事件监听器将在此对象下被暴露为一个函数$attrs.onClick
。
当有多个根节点时候,那么就会不知道透传给哪一个节点,会有警告,这时要通过$attrs显式接收,警告才消失
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
3.依赖注入(跨级通信)
-
vue2中隔代通信有几种办法:通过props接收,层层传递;通过Vuex,这应该是最常用的方法。在vue2的处理边界情况中,也提到了provide和inject方法,但是我看过的视频里都没有提到过,所以我也没有用过。到vue3这边郑重的提出来了。
-
vue3中:
provide供给:要为组件后代供给数据,需要使用到
provide()
函数,provide要写在setup里面。provide()
函数接收两个参数。第一个参数被称为注入名,可以是一个字符串或是一个Symbol
。后代组件会注入名用来查找期望注入的值。一个组件可以多次调用provide()
,使用不同的注入名,注入不同的依赖值。第二个参数是供给的值,值可以是任意类型,包括响应式的状态,比如一个 ref。除了供给一个组件的数据,我们还可以在整个应用层面做供给,即app.provide()
inject注入:要注入祖先组件供给的数据,需使用
inject()
函数,如果供给的值是一个 ref,注入进来的就是它本身,而不会自动解包。这使得被注入的组件保持了和供给者的响应性链接。注入时第一个参数是注入名,第二个参数是默认值,没有提供数据时使用
4.异步组件
-
vue2中:为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用。
Vue.component('async-webpack-example', function (resolve) { // 这个特殊的 `require` 语法将会告诉 webpack // 自动将你的构建代码切割成多个包,这些包 // 会通过 Ajax 请求加载 require(['./my-async-component'], resolve) })
-
vue3中:提供了一个
defineAsyncComponent
方法,但是要搭配内置的<Suspense>
组件一起使用。defineAsyncComponent
方法接收一个返回 Promise 的加载函数。这个 Promise 的resolve
回调方法应该在从服务器获得组件定义时调用。你也可以调用reject(reason)
表明加载失败。import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent(() => { return new Promise((resolve, reject) => { // ...从服务器获取组件 resolve(/* 获取到的组件 */) }) }) // ... 像使用其他一般组件一样使用 `AsyncComp`
vue2和vue3都支持的就是在局部注册的时候可以直接在里面动态导入一个模块,返回promise对象
Vue.component( 'async-webpack-example', // 这个动态导入会返回一个 `Promise` 对象。 () => import('./my-async-component') )
import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent(() => import('./components/MyComponent.vue') )
5. Teleport·传送门
<Teleport>
是一个内置组件,使我们可以将一个组件的一部分模板“传送”到该组件的 DOM 层次结构之外的 DOM 节点中。就是to属性,to=‘传送位置’, :disabled=“isMobile”,disabled禁用
<button @click="open = true">Open Modal</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
6.Suspense
- 配合异步使用。可以等待的异步依赖有两种:
-
带有异步
setup()
钩子的组件。这也包含了使用<script setup>
时有顶层await
表达式的组件。export default { async setup() { const res = await fetch(...) const posts = await res.json() return { posts } } }
-
异步组件。就是先加载,可以不等该组件加载完先进行显示,加载完后自动显示。
-
<Suspense>
组件有两个插槽:#default
和#fallback
。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。就是说,这个组件内部是封装了两个具名插槽的。default里面应该是要展示的内容,fallback是展示内容未加载完成时显示的内容。
7.性能优化
v-memo
v-memo
是一个内置指令,可以用来有条件地跳过某些大型子树或者 v-for
列表的更新。可以提高性能。可以在元素和组件上使用。该指令需要一个固定长度的依赖值数组来比较记忆。如果数组中的每个值都与上次渲染的值相同,那么将跳过对整个子树的更新。例如:
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
<p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
<p>...more child nodes</p>
</div>
当组件的选定状态发生变化时,即使大多数项保持完全相同,也会创建大量的VNodes。这里的v-memo用法本质上是说“只有当这个项目从非选中变为选中时才更新它,反之亦然”。这允许每个未受影响的项目重用其先前的VNode并完全跳过差异。注意我们不需要在memo依赖数组中包含item.id,因为Vue会自动从item的:key中推断出它。
浅层次响应式—— shallowRef() 和 shallowReactive()
通过使用 shallowRef()
和 shallowReactive()
来选择退出深度响应。浅层式 API 创建的状态只在其顶层是响应式的,并原封不动地显示所有下面层级的对象。这使得对深层级属性的访问变得更快,但代价是,我们现在必须将所有深层级对象视为不可变的,并且只能通过替换整个根状态来触发更新
8.组合式API
用法本质上是说“只有当这个项目从非选中变为选中时才更新它,反之亦然”。这允许每个未受影响的项目重用其先前的VNode并完全跳过差异。注意我们不需要在memo依赖数组中包含item.id,因为Vue会自动从item的:key中推断出它。