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:
实现原理:
存在的问题:
新增属性、删除属性,界面不会更新
新增属性: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:
实现原理:
模拟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:
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
Vue 2.x有许多全局API和配置。
例如:注册全局组件、注册全局指令等。
//注册全局组件
Vue.component('MyButton', {
data: () => ({
count: 0
}),
template: '
})
//注册全局指令
Vue.directive('focus', {
inserted: el => el.focus()
}
Vue3中对这些API做出了调整,将全局的API调整到应用实例app上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rin2E2EN-1650199822736)(E:\markdown\前端\csdn\20220416_1.png)]
vue2 Options API(选项式API)存在的问题:
使用传统的Options API时,新增或者修改一个需求,就需要分别在data、methods、computed、watch等里面不断地寻找、修改。如果代码量大的时候,修改起来就很痛苦。
vue3中 Composition API(组合式API)的优势:
更加优雅的组织我们的代码、函数,让相关功能的代码更加有序的组织在一起,都写在setup里面。甚至可以把他们都变成一个个hook函数,修改那部分就可以直接去对应的文件里修改。把定义数据、方法这部分也添加了模块化封装的思想
data选项应始终被声明为一个函数
过度类名的更改,就是在开始的那个位置上加了一个-from
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
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修饰符
父组件中绑定事件:
子组件中声明自定义事件:子组件emits里面接收的就是自定义事件,不接收就默认是原生事件
上述为讲课中所整理的部分内容,下面再自己去看看官方文档,来总结一些其他的。
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。
这部分都是自己分析的,有不对的地方欢迎指正。
vue2中,data中写的数据都是响应式的,后追加的数据不是响应式的,可以通过数据代理使后添加的数据为响应式。通过getter和setter实现响应式。
Object.defineProperty(obj2,'x',{
get(){
return obj.x
},
set(value){
obj.x = value
}
})
vue3中,只有通过ref()定义的基本类型数据(对象使用ref定义也是响应式的,但这是不规范的)和通过reactive()定义的对象才是响应式的。所以后期直接往对象里添加属性还是响应式的。在组件挂载后才能访问 ref。通过proxy实现响应式。
vue2中new了一个Vue(),那么就开始创建了,判断没有绑定el,那就不能走下去进行挂载了。只有绑定了el才可以往下走。
最后两个生命周期函数为销毁:beforeDestroy和destroyed。
生命周期函数的位置和data,methods等平级。
vue3中要先判断有没有创建实例对象并绑定,只有都完成了才会开始创建,然后挂载。
最后两个生命周期函数改成了卸载:beforeUnmount和unmounted
官网最新的写法是,这样把生命周期函数写在script里面就需要在每个前面加on,比如
onMounted
。也可以把setup写在script里面,那么这样就有两种写法:写在setup里加on,或者和setup平级,写法和以前一样。
在事件处理中,vue3废弃了按键码,vue2的后面版本应该也废弃了的
Vue.component('component-a', { /* ... */ })
app.component('MyComponent', MyComponent)
可链式调用
而产生的新写法props:组件内多出了一种接收方法:const props = defineProps(['foo'])
,这样接收和直接写在props里是一样的。
emits:组件要触发的事件可以显式地通过 defineEmits()
宏来声明。const emit = defineEmits(['inFocus', 'submit'])
与emits: ['inFocus', 'submit']
相同
useAttrs:使用 useAttrs()
API 来访问一个组件的所有透传 attribute。useAttrs需要引入。
“透传 attribute”是传递给组件的 attribute 或者 v-on
事件监听器,但并没有显式地声明在所接收组件的 props 或 emits 上。个人理解就是在父组件使用子组件时,给组件添加了class、id、style等,这些会和组件原有的合并。
- 透传的 attribute 不会包含
上声明过的 props 或是针对
emits
声明事件的v-on
侦听函数,换句话说,声明过的 props 和侦听函数被“消费”了。
- 透传的 attribute 若符合声明,也可以作为 props 传入
。
上面是官方给的注意点,结构应该是这样的 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显式接收,警告才消失
...
...
vue2中隔代通信有几种办法:通过props接收,层层传递;通过Vuex,这应该是最常用的方法。在vue2的处理边界情况中,也提到了provide和inject方法,但是我看过的视频里都没有提到过,所以我也没有用过。到vue3这边郑重的提出来了。
vue3中:
provide供给:要为组件后代供给数据,需要使用到 provide()
函数,provide要写在setup里面。provide()
函数接收两个参数。第一个参数被称为注入名,可以是一个字符串或是一个 Symbol
。后代组件会注入名用来查找期望注入的值。一个组件可以多次调用 provide()
,使用不同的注入名,注入不同的依赖值。第二个参数是供给的值,值可以是任意类型,包括响应式的状态,比如一个 ref。除了供给一个组件的数据,我们还可以在整个应用层面做供给,即app.provide()
inject注入:要注入祖先组件供给的数据,需使用 inject()
函数,如果供给的值是一个 ref,注入进来的就是它本身,而不会自动解包。这使得被注入的组件保持了和供给者的响应性链接。注入时第一个参数是注入名,第二个参数是默认值,没有提供数据时使用
vue2中:为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用。
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
vue3中:提供了一个 defineAsyncComponent
方法,但是要搭配内置的
组件一起使用。
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')
)
是一个内置组件,使我们可以将一个组件的一部分模板“传送”到该组件的 DOM 层次结构之外的 DOM 节点中。就是to属性,to=‘传送位置’, :disabled=“isMobile”,disabled禁用
Hello from the modal!
带有异步 setup()
钩子的组件。这也包含了使用 时有顶层
await
表达式的组件。
export default {
async setup() {
const res = await fetch(...)
const posts = await res.json()
return {
posts
}
}
}
异步组件。就是先加载,可以不等该组件加载完先进行显示,加载完后自动显示。
组件有两个插槽:#default
和 #fallback
。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。
就是说,这个组件内部是封装了两个具名插槽的。default里面应该是要展示的内容,fallback是展示内容未加载完成时显示的内容。
v-memo
是一个内置指令,可以用来有条件地跳过某些大型子树或者 v-for
列表的更新。可以提高性能。可以在元素和组件上使用。该指令需要一个固定长度的依赖值数组来比较记忆。如果数组中的每个值都与上次渲染的值相同,那么将跳过对整个子树的更新。例如:
ID: {{ item.id }} - selected: {{ item.id === selected }}
...more child nodes
当组件的选定状态发生变化时,即使大多数项保持完全相同,也会创建大量的VNodes。这里的v-memo用法本质上是说“只有当这个项目从非选中变为选中时才更新它,反之亦然”。这允许每个未受影响的项目重用其先前的VNode并完全跳过差异。注意我们不需要在memo依赖数组中包含item.id,因为Vue会自动从item的:key中推断出它。
通过使用 shallowRef()
和 shallowReactive()
来选择退出深度响应。浅层式 API 创建的状态只在其顶层是响应式的,并原封不动地显示所有下面层级的对象。这使得对深层级属性的访问变得更快,但代价是,我们现在必须将所有深层级对象视为不可变的,并且只能通过替换整个根状态来触发更新
用法本质上是说“只有当这个项目从非选中变为选中时才更新它,反之亦然”。这允许每个未受影响的项目重用其先前的VNode并完全跳过差异。注意我们不需要在memo依赖数组中包含item.id,因为Vue会自动从item的:key中推断出它。