简介
vue3.0 向下兼容 vue2.x 版本,优化了主要核心双向绑定原理和体积大小,并且更加友好的兼容 ts 语法。
工程创建
vue-cil 创建
选择 vue3.0 生成项目即可
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve
使用 vite 创建
Vite 是一个 web 开发构建工具,由于其原生 ES 模块导入方式,可以实现闪电般的冷服务器启动。(这里不详细研究)
通过在终端中运行以下命令,可以使用 Vite 快速构建 Vue 项目。
# npm 6.x
$ npm init vite@latest <project-name> --template vue
# npm 7+,需要加上额外的双短横线
$ npm init vite@latest <project-name> -- --template vue
$ cd <project-name>
$ npm install
$ npm run dev
对比两种方式创建项目
vite 优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 轻量快速的热重载(HMR)。
- 真正的按需编译,不再等待整个应用编译完成。 传统 webpack 编译:每次执行编译时,都会通过入口 entry 先去找到各个路由,再去加载每个路由各自的模块,然后会进行打包成为 bundle.js 文件,最后才通知服务器热更新。所以换句话说就是等所有文件加载就绪之后才去渲染更新页面的==》较慢
vite 编译:与传统构建不同的是,vite 会先准备好服务器更新,再去找到入口文件然后再动态的找到需要加载的路由去编译那个路由下的模块,类似于按需加载,总体体积较小且更新更快。
vue3.0 优点
性能方面
- 打包大小减少
- 初次渲染快 , 更新渲染快
- 内存减少
源码方面
- 移除一些冷门 API,比如 filter、inline-template 等,体积减少
- 引入 tree-shaking 减少打包体积(通过编译阶段的静态分析,找到没有引入的模块并打上标记,并且移除)
- 使用 Proxy 代替 defineProperty 实现响应式。
- Vue3 可以更好的支持 TypeScript
- Composition API(组合 API)可以按需使用,多余勾子配置不用再次打包。
vue3.0 响应式原理
vue2.x 通过 Object.defineProperty()实现的响应式原理存在以下问题
- 新增属性、删除属性, 监听不到变化从而不是响应式数据,页面也不会改变
- 直接通过下标修改数组也监听不到。 对此 vue3.0 解决了这些问题
proxy 简介
vue 响应式原理本质就是 ES6 新语法 proxy 实现的。 Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写. 接收两个参数,var proxy = new Proxy(target, handler);
target 参数表示所要拦截的目标对象,handler 参数也是一个对象,用来定制拦截行为。
// 源对象
let obj = {
name: 'haha',
age: 18,
}
// handler作为拦截配置对象
const p = new Proxy(obj, {
// 读取操作
get(target, propName) {
// get方法接收2个参数 target指源对象,就是obj,propName就是你当前操作的属性
// 返回当前读取的值
return target[propName]
},
// 修改操作,新增属性操作时都会触发
set(target, propName, value) {
// set方法接收3个参数 target指源对象,前面2个与上相同,第三个参数是修改的值
// 通知原对象修改数据
target[propName] = value
},
//删除操作
deleteProperty(target, propName) {
return delete target[propName]
},
})
// 以上 即可实现对象的增删改查的监听,实现数据的相应式
Reflect 简介
Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API。具有反射的意思。 将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。
修改某些 Object 方法的返回结果,让其变得更合理,会有一个布尔返回值,易于捕捉错误(js 单线程,报错会阻塞进程)
// 老写法
try {
Object.defineProperty(target, property, attributes)
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
let obj = {
name: 'haha',
age: 18,
}
// 三个静态方法:
Reflect.get(obj, 'name') // 返回 haha 读取对象的某个属性的值
Reflect.set(obj, 'name', '小明') // 返回布尔 修改某个属性的值
Reflect.deleteProperty(obj, 'name') // 返回布尔 删除某个属性
const p = new Proxy(obj, {
//有人读取p的某个属性时调用
get(target, propName) {
return Reflect.get(target, propName)
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target, propName, value) {
Reflect.set(target, propName, value)
},
//有人删除p的某个属性时调用
deleteProperty(target, propName) {
return Reflect.deleteProperty(target, propName)
},
})
生命周期介绍
通过官网生命周期图可以看出Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有2个钩子发生了改变 - beforeDestroy改名为beforeUnmount(卸载前) - destroyed改名为unmounted(卸载) 与vue2不同的是,vue3中是有了el模板之后才会去初始化,而vue2中是先created之后再去找模板。
分为2种形式的生命周期勾子
组合式 api(Composition API)
setup
它是 vue3 中一个新的配置项,值为一个函数。所有的组合 api 都要在它里面使用。
使用介绍
- 使用变量 或者事件 需要把名字 return 出去即可在模板中使用。
export default {
setup() {
let name = 'zhang'
function at() {
console.log(1)
}
return {
name,
at,
}
},
}
- setup 函数的两种返回值,一种就是上面常规返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用,还有一种就是返回一个函数
// 若返回一个渲染函数:则可以自定义渲染内容
import { h } from 'vue'
export default {
setup() {
return () => h('h1', '你好')
},
}
- 注意 vue3 虽然可以向下兼容 vue2,但是尽量不能混合使用。
- Vue2.x 配置(data、methos、computed...)中可以访问到 setup 中的属性、方法
- 但是由于 setup 中没有 this,所以 setup 中没办法读取 Vue2.x 配置中的数据和方法
- 如果有重名, setup 优先
setup 的注意点
- setup 执行的时机是最早的,在 beforeCreate 之前执行,所以此时 this 是 undefined
- 参数问题 setup 接收 2 个参数
props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 ```this.$attrs```。
- slots: 收到的插槽内容, 相当于 ```this.$slots```。
- emit: 分发自定义事件的函数, 相当于 ```this.$emit```
// 在子组件中
export default {
props: ['msg', 'school'],
emits: ['hello'],
setup(props, context) {
// props接收props配置项中的相应式数据{msg:'',school:''}
// context相当于上下文对象,里面有三个值attrs,slots,emit
//方法
function test() {
// 调用父组件方法
context.emit('hello', 666)
}
return {
test,
}
},
}
ref 函数
作用: 定义一个响应式的数据(主要针对基础类型数据) 方法:引入 ref 函数,const xxx = ref(initValue) 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
处理基本数据类型
RefImpl 对象中.value 是基础类型时,用的是 Object.defineProperty 通过 get 和 set 实现的响应式数据
import { ref } from 'vue'
export default {
setup() {
let name = ref('张三')
function change() {
console.log(name, 'name')
//ref加工之后生成一个 RefImpl引用对象,该对象的原型对象上可以发现,底层其实还是Object.defineProperty通过
// get 和set实现的响应式数据
// 因此改变基本数据需要用到RefImpl引用对象中的value属性
name.value = '小明'
}
return {
name,
change,
}
},
}
处理对象类型
RefImpl 对象中.value 是对象时候,用的是 proxy 代理对象实现的响应式数据
import { ref } from 'vue'
export default {
setup() {
let obj = ref({
name: '小明',
age: 20,
})
function change() {
obj.value.name = '小工'
obj.value.age++
console.log(obj.value)
//可以发现是一个Proxy 对象,其本质其实调用的是```reactive```函数实现Proxy代理响应式对象
}
return {
obj,
change,
}
},
}
reactive 函数
作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref
函数) 方法:const x= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy 的实例对象,简称 proxy 对象) 特点:可以实现数组、深层对象的响应式数据,这是 vue2.0 中无法实现的,底层基于 Proxy
export default {
setup() {
let obj = reactive({
name: '小明',
age: 20,
})
function change() {
console.log(obj, 'obj')
//可以发现obj此时就是一个Proxy的实例对象可以直接修改对象内部属性
obj.name = '小三'
obj.age++
}
return {
obj,
change,
}
},
}
总结 ref 和 reactive
- 从定义数据角度对比
- ref 用来定义:基本类型数据。
- reactive 用来定义:对象(或数组)类型数据。
- 备注:ref 也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。 - 从原理角度对比
- ref 通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive 通过使用 Proxy 来实现响应式(数据劫持), 并通过 Reflect 操作源对象内部的数据。
计算属性与监视
computed函数
与Vue2.x中computed配置功能一致
// 1.直接读取(简写)
import { reactive,computed } from 'vue'
export default {
setup () {
let obj = reactive({
name: 'haha',
age: 18
})
let ages = computed(() => {
return obj.age + 1
})
return {
ages
}
},
}
// 1.计算属性修改情况(完整版)
export default {
setup () {
let obj = reactive({
name: 'haha',
age: 18
})
let fullName = computed({
get () {
return obj.age + 1
},
set (value) {
obj.age = value
}
})
return {
obj,
fullName
}
},
}
watch函数
watch接收三个参数 监听的对象,监听的回调和监视的配置参数
watch('被监听的对象',()=>{},{immediate:'立即监听',deep:'深度监听'})
export default {
setup () {
let age = ref(18)
let name = ref('小明')
let obj = reactive({
money: 100
})
function change () {
obj.money += 100
age.value++
name.value += '-'
}
//情况一:监视ref所定义的一个响应式数据
watch(age, (newValue, oldValue) => {
console.log('age', newValue, oldValue)
},{immediate:true})
//情况二:监视ref所定义的多个响应式数据
watch([age, name], (newValue, oldValue) => {
console.log('age-name', newValue, oldValue)//也是数组形式返回
})
// 情况三:监视reactive所定义的一个响应式数据的全部属性
watch(obj, (newValue, oldValue) => {
// 如果监听的是正规响应式对象的话
// 1.注意:此处无法正确的获取oldValue
// 2.注意:强制开启了深度监视(deep配置无效)
})
// 情况四:监视reactive所定义的一个响应式数据中的某个属性
watch(() => obj.money, (newValue, oldValue) => {
// [()=>person.name,()=>person.age]多参数时候也需要数组
// 监听的参数需要以函数的形式返回才可以监听到
//当监听的参数是深层对象,需要配置deep为true
})
return {
obj,
name,
age,
change
}
},
}
watchEffect函数
不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。 watchEffect有点像computed: - 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。 - 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
watchEffect(() => {
let a = obj.money
console.log('改变了')
//默认是立即监听,当使用的数据改变时,就会触发。可以用于关联数据改变回调方法等场景
})
自定义hook函数
什么是hook: 本质是一个函数,把setup函数中使用的Composition API进行了封装。 特点:类似于vue2.x中的mixin 优势: 复用代码, 让setup中的逻辑更清楚易懂。
toRef和toRefs
创建一个 ref 对象,其value值指向另一个对象中的某个属性。 用法:const name = toRef(obj,'name') 要将响应式对象中的某个属性单独提供给外部使用时,便于简写。
// toRef生成一个对象,本质还是保持对其源对象属性的响应式连接。
export default {
setup () {
let obj = reactive({
take: {
money: 100,
}
})
function change () {
obj.take.money += 100
}
return {
money: toRef(obj.take, 'money'),
//注意:此处不能使用ref(obj.take, 'money'),一般情况下页面虽然效果一样,但是本质上修改的源对象不是reactive里面
// 的obj,而是ref出来的一个新对象,不能使源obj响应式修改。
change
}
},
}
toRefs的使用与toRef一样,不过是处理整个对象的所有属性,批量创建多个 ref 对象
<template>
<div>
{{money}}
{{age}}
<button @click="change">改变 </button>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
setup () {
let obj = reactive({
money: 100,
age: 18,
})
function change () {
obj.money += 100
obj.age++
}
console.log(toRefs(obj));//生成的是一个和obj源对象关联的处理对象集合
return {
...toRefs(obj),
change
}
},
}
</script>
shallowReactive 与 shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
export default {
setup () {
// 只会处理第一层属性
let obj = shallowReactive({
money: 100,
age: 18,
per:{
name:'lili'
}
})
// 此时不会响应式修改这个x对象
let x = shallowRef({
y:0
})
return {
obj,
x
}
},
}
readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
toRaw 与 markRaw
- toRaw 将一个由reactive生成的响应式对象转为普通对象 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
export default {
setup () {
let obj = reactive({
money: 100,
age: 18,
})
function change () {
let p = toRaw(obj)
//此时的p就是一个不具有响应式的原生对象,且只能针对reactive包装后的响应式对象
p.money += 100
p.age++
}
return {
...toRefs(obj),
change
}
},
}
- markRaw 标记一个对象,使其永远不会再成为响应式对象.
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
export default {
setup () {
let obj = reactive({
money: 100,
age: 18,
person: {}
})
function change () {
// 当定义好的对象后面需要再加入新的属性时
let person = { name: 'xixi', class: 'one' }
// obj.person = person //此时也是响应式数据,如果当新增的数据对象非常复杂,只要求展示的时候,
// 就要求不是响应式对象时 :
obj.person = markRaw(person)
}
return {
obj,
...toRefs(obj),
change
}
},
}
customRef
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象.
使用自定义 ref 通过 v-model 实现 debounce(防抖) 的示例
<template>
<input type="text" v-model="keyWord">
<h3>{{keyWord}}</h3>
</template>
<script>
import {ref,customRef} from 'vue'
export default {
name: 'App',
setup() {
//自定义一个ref——名为:myRef
function myRef(value,delay){
let timer
return customRef((track,trigger)=>{
return {
get(){
console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
track() //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
return value
},
set(newValue){
console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
clearTimeout(timer)
timer = setTimeout(()=>{
value = newValue
trigger() //通知Vue去重新解析模板
},delay)
},
}
})
}
// let keyWord = ref('hello') //使用Vue提供的ref
let keyWord = myRef('hello',500) //使用程序员自定义的ref
return {keyWord}
}
}
</script>
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
Composition API 的优势
Options API 存在的问题(vue2)
传统OptionsAPI中,实现一个需求,就需要分别在data,methods,computed里修改,当代码量巨大逻辑复杂时,就会造成代码难以维护和查找。
Composition API 的优势(vue3)
可以更加优雅的组织的代码,函数。让相关功能的代码更加有序的组织在一起,更加有逻辑性可读性
新的内置组件
Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
Teleport
Teleport
是一种能够将我们的组件html结构移动到指定位置的技术
需要 prop,必须是有效的查询选择器或 HTMLElement (如果在浏览器环境中使用)。指定将在其中移动 内容的目标元素
请注意,这将移动实际的 DOM 节点,而不是被销毁和重新创建,并且它还将保持任何组件实例的活动状态。
<teleport to="#some-id" />
<teleport to=".some-class" />
<teleport to="[data-teleport]" />
<teleport to="移动位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
其他改动
- 全局API的转移
- 将全局的API,即:
Vue.xxx
调整到应用实例(app
)上
2.x 全局 API(Vue) | 3.x 实例 API (app) |
---|---|
Vue.config.xxxx | app.config.xxxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
- 其他改动
- 移除keyCode作为 v-on 的修饰符,同时也不再支持
config.keyCodes
- 移除
v-on.native
修饰符 - 父组件中绑定事件
vue <my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
- 子组件中声明自定义事件
vue <script> export default { emits: ['close'] } </script>
- 移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。