目录 Vue3总览 1. 认识Vue3 1) 了解相关信息 2) 性能提升: 3) 新增特性 2. 创建vue3项目 1) 使用 vue-cli 创建 2) 使用 vite 创建 Composition API 1. Composition API(常用部分) 1) setup 2) ref 3) reactive 4) 比较Vue2与Vue3的响应式(重要) 5) setup细节 6) reactive与ref-细节 7) 计算属性与监视 8) 生命周期 9) 自定义hook函数 10) toRefs 11) ref获取元素 2. Composition API(其它部分) 1) shallowReactive 与 shallowRef 2) readonly 与 shallowReadonly 3) toRaw 与 markRaw 4) toRef 5) customRef 6) provide 与 inject 7) 响应式数据的判断 3. 手写组合API 1) shallowReactive 与 reactive 2) shallowRef 与 ref 3) shallowReadonly 与 readonly 4) isRef, isReactive 与 isReadonly 4. Composition API VS Option API 1) Option API的问题 2) 使用Compisition API 其它新组合和API 1. 新组件 1) Fragment(片断) 2) Teleport(瞬移) 3) Suspense(不确定的) 2. 其他新的API Vue3总览 1. 认识Vue3 1) 了解相关信息 Vue.js 3.0 "One Piece" 正式版在今年9月份发布 2年多开发, 100+位贡献者, 2600+次提交, 600+次PR Vue3支持vue2的大多数特性 更好的支持Typescript 2) 性能提升: 打包大小减少41% 初次渲染快55%, 更新渲染快133% 内存减少54% 使用Proxy代替defineProperty实现数据响应式 重写虚拟DOM的实现和Tree-Shaking 3) 新增特性 Composition (组合) API setup ref 和 reactive computed 和 watch 新的生命周期函数 provide与inject ... 新组件 Fragment - 文档碎片 Teleport - 瞬移组件的位置 Suspense - 异步加载组件的loading界面 其它API更新 全局API的修改 将原来的全局API转移到应用对象 模板语法变化 2. 创建vue3项目 1) 使用 vue-cli 创建 文档: https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
## 安装或者升级
npm install -g @vue/cli
## 保证 vue cli 版本在 4.5.0 以上
vue --version
## 创建项目
vue create my-project
然后的步骤
Please pick a preset - 选择 Manually select features
Check the features needed for your project - 选择上 TypeScript ,特别注意点空格是选择,点回车是下一步
Choose a version of Vue.js that you want to start the project with - 选择 3.x (Preview)
Use class-style component syntax - 直接回车
Use Babel alongside TypeScript - 直接回车
Pick a linter / formatter config - 直接回车
Use history mode for router? - 直接回车
Pick a linter / formatter config - 直接回车
Pick additional lint features - 直接回车
Where do you prefer placing config for Babel, ESLint, etc.? - 直接回车
Save this as a preset for future projects? - 直接回车
2) 使用 vite 创建
文档: https://v3.cn.vuejs.org/guide/installation.html
vite 是一个由原生 ESM 驱动的 Web 开发构建工具。
在开发环境下基于浏览器原生 ES imports 开发, 它做到了***本地快速开发启动***, 在生产环境下基于 Rollup 打包。
快速的冷启动,不需要等待打包操作; 即时的热模块更新,替换性能和模块数量的解耦让更新飞起; 真正的按需编译,不再等待整个应用编译完成,这是一个巨大的改变。
npm init vite-app
cd
npm install
npm run dev
Composition API
1. Composition API(常用部分)
文档:
https://composition-api.vuejs.org/zh/api.html
1) setup
新的option, 所有的组合API函数都在此使用, 只在初始化时执行一次
函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
2) ref
作用: 定义一个数据的响应式
语法: const xxx = ref(initValue):
创建一个包含响应式数据的引用(reference)对象
js中操作数据: xxx.value
模板中操作数据: 不需要.value
一般用来定义一个基本类型的响应式数据
{{count}}
3) reactive 作用: 定义多个数据的响应式 const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象 响应式转换是“深层的”:会影响对象内部所有嵌套的属性 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
name: {{state.name}}
age: {{state.age}}
wife: {{state.wife}}
4) 比较Vue2与Vue3的响应式(重要) vue2的响应式 核心: 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截) 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
问题
对象直接新添加的属性或删除已有属性, 界面不会自动更新
直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}
Vue3的响应式
核心:
通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等...
通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
文档:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'tom'
Proxy 与 Reflect
5) setup细节 setup执行的时机 在beforeCreate之前执行(一次), 此时组件对象还没有创建 this是undefined, 不能通过this来访问data/computed/methods / props 其实所有的composition API相关回调函数中也都不可以 setup的返回值 一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性 返回对象中的方法会与methods中的方法合并成功组件对象的方法 如果有重名, setup优先 注意: 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据 setup的参数 setup(props, context) / setup(props, {attrs, slots, emit}) props: 包含props配置声明且传入了的所有属性的对象 attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots emit: 用来分发自定义事件的函数, 相当于 this.$emit
App
msg: {{msg}}
{{n}}
{{m}}
msg: {{msg}}
msg2: {{$attrs.msg2}}
6) reactive与ref-细节 是Vue3的 composition API中2个最重要的响应式API ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式) 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象 ref内部: 通过给value属性添加getter/setter来实现对数据的劫持 reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据 ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
App
m1: {{m1}}
m2: {{m2}}
m3: {{m3}}
7) 计算属性与监视 computed函数 与computed配置功能一致 只有getter 有getter和setter watch函数 与watch配置功能一致 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次 通过配置deep为true, 来指定深度监视 watchEffect函数 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据 默认初始时就会执行第一次, 从而可以收集需要监视的数据 监视数据发生变化时回调
App
fistName:
lastName:
fullName1:
fullName2:
fullName3:
8) 生命周期 vue2.x的生命周期 lifecycle_2 vue3的生命周期 lifecycle_3 与 2.x 版本生命周期相对应的组合式 API beforeCreate -> 使用 setup() created -> 使用 setup() beforeMount -> onBeforeMount mounted -> onMounted beforeUpdate -> onBeforeUpdate updated -> onUpdated beforeDestroy -> onBeforeUnmount destroyed -> onUnmounted errorCaptured -> onErrorCaptured 新增的钩子函数 组合式 API 还提供了以下调试钩子函数: onRenderTracked onRenderTriggered
msg: {{msg}}
App
09) 自定义hook函数 使用Vue3的组合API封装的可复用的功能函数 自定义hook的作用类似于vue2中的mixin技术 自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂 需求1: 收集用户鼠标点击的页面坐标 hooks/useMousePosition.ts
import { ref, onMounted, onUnmounted } from 'vue'
/*
收集用户鼠标点击的页面坐标
*/
export default function useMousePosition () {
// 初始化坐标数据
const x = ref(-1)
const y = ref(-1)
// 用于收集点击事件坐标的函数
const updatePosition = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
// 挂载后绑定点击监听
onMounted(() => {
document.addEventListener('click', updatePosition)
})
// 卸载前解绑点击监听
onUnmounted(() => {
document.removeEventListener('click', updatePosition)
})
return {x, y}
}
x: {{x}}, y: {{y}}
利用TS泛型强化类型检查 需求2: 封装发ajax请求的hook函数 hooks/useRequest.ts
import { ref } from 'vue'
import axios from 'axios'
/*
使用axios发送异步ajax请求
*/
export default function useUrlLoader(url: string) {
const result = ref(null)
const loading = ref(true)
const errorMsg = ref(null)
axios.get(url)
.then(response => {
loading.value = false
result.value = response.data
})
.catch(e => {
loading.value = false
errorMsg.value = e.message || '未知错误'
})
return {
loading,
result,
errorMsg,
}
}
LOADING...
{{errorMsg}}
- id: {{p.id}}
- title: {{p.title}}
- price: {{p.price}}
10) toRefs 把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref 应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用 问题: reactive 对象取出的所有属性值都是非响应式的 解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
App
foo: {{foo}}
bar: {{bar}}
foo2: {{foo2}}
bar2: {{bar2}}
11) ref获取元素 利用ref函数获取组件中的标签元素 功能需求: 让输入框自动获取焦点
App
---
2. Composition API(其它部分) 1) shallowReactive 与 shallowRef shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式) shallowRef: 只处理了value的响应式, 不进行对象的reactive处理 什么时候用浅响应式呢? 一般情况下使用ref和reactive即可 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
App
m1: {{m1}}
m2: {{m2}}
m3: {{m3}}
m4: {{m4}}
2) readonly 与 shallowReadonly readonly: 深度只读数据 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
只读代理是深层的:访问的任何嵌套 property 也是只读的。
shallowReadonly 浅只读数据 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 应用场景: 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
App
{{state}}
3) toRaw 与 markRaw toRaw 返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。
这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
markRaw 标记一个对象,使其永远不会转换为代理。
返回对象本身 应用场景: 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
{{state}}
4) toRef 为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响 应用: 当要将 某个prop 的 ref 传递给复合函数时,toRef 很有用
App
{{state}}
{{foo}}
{{foo2}}
Child
{{foo}}
{{length}}
5) customRef 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制 需求: 使用 customRef 实现 debounce 的示例
App
{{keyword}}
6) provide 与 inject provide和inject提供依赖注入,功能类似 2.x 的provide/inject 实现跨层级组件(祖孙)间通信
父组件
当前颜色: {{color}}
子组件
孙子组件: {{color}}
7) 响应式数据的判断 isRef: 检查一个值是否为一个 ref 对象 isReactive: 检查一个对象是否是由 reactive 创建的响应式代理 isReadonly: 检查一个对象是否是由 readonly 创建的只读代理 isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理 3. 手写组合API 1) shallowReactive 与 reactive
const reactiveHandler = {
get (target, key) {
if (key==='_is_reactive') return true
return Reflect.get(target, key)
},
set (target, key, value) {
const result = Reflect.set(target, key, value)
console.log('数据已更新, 去更新界面')
return result
},
deleteProperty (target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('数据已删除, 去更新界面')
return result
},
}
/*
自定义shallowReactive
*/
function shallowReactive(obj) {
return new Proxy(obj, reactiveHandler)
}
/*
自定义reactive
*/
function reactive (target) {
if (target && typeof target==='object') {
if (target instanceof Array) { // 数组
target.forEach((item, index) => {
target[index] = reactive(item)
})
} else { // 对象
Object.keys(target).forEach(key => {
target[key] = reactive(target[key])
})
}
const proxy = new Proxy(target, reactiveHandler)
return proxy
}
return target
}
/* 测试自定义shallowReactive */
const proxy = shallowReactive({
a: {
b: 3
}
})
proxy.a = {b: 4} // 劫持到了
proxy.a.b = 5 // 没有劫持到
/* 测试自定义reactive */
const obj = {
a: 'abc',
b: [{x: 1}],
c: {x: [11]},
}
const proxy = reactive(obj)
console.log(proxy)
proxy.b[0].x += 1
proxy.c.x[0] += 1
2) shallowRef 与 ref
/*
自定义shallowRef
*/
function shallowRef(target) {
const result = {
_value: target, // 用来保存数据的内部属性
_is_ref: true, // 用来标识是ref对象
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
/*
自定义ref
*/
function ref(target) {
if (target && typeof target==='object') {
target = reactive(target)
}
const result = {
_value: target, // 用来保存数据的内部属性
_is_ref: true, // 用来标识是ref对象
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
/* 测试自定义shallowRef */
const ref3 = shallowRef({
a: 'abc',
})
ref3.value = 'xxx'
ref3.value.a = 'yyy'
/* 测试自定义ref */
const ref1 = ref(0)
const ref2 = ref({
a: 'abc',
b: [{x: 1}],
c: {x: [11]},
})
ref1.value++
ref2.value.b[0].x++
console.log(ref1, ref2)
3) shallowReadonly 与 readonly
const readonlyHandler = {
get (target, key) {
if (key==='_is_readonly') return true
return Reflect.get(target, key)
},
set () {
console.warn('只读的, 不能修改')
return true
},
deleteProperty () {
console.warn('只读的, 不能删除')
return true
},
}
/*
自定义shallowReadonly
*/
function shallowReadonly(obj) {
return new Proxy(obj, readonlyHandler)
}
/*
自定义readonly
*/
function readonly(target) {
if (target && typeof target==='object') {
if (target instanceof Array) { // 数组
target.forEach((item, index) => {
target[index] = readonly(item)
})
} else { // 对象
Object.keys(target).forEach(key => {
target[key] = readonly(target[key])
})
}
const proxy = new Proxy(target, readonlyHandler)
return proxy
}
return target
}
/* 测试自定义readonly */
/* 测试自定义shallowReadonly */
const objReadOnly = readonly({
a: {
b: 1
}
})
const objReadOnly2 = shallowReadonly({
a: {
b: 1
}
})
objReadOnly.a = 1
objReadOnly.a.b = 2
objReadOnly2.a = 1
objReadOnly2.a.b = 2
4) isRef, isReactive 与 isReadonly
/*
判断是否是ref对象
*/
function isRef(obj) {
return obj && obj._is_ref
}
/*
判断是否是reactive对象
*/
function isReactive(obj) {
return obj && obj._is_reactive
}
/*
判断是否是readonly对象
*/
function isReadonly(obj) {
return obj && obj._is_readonly
}
/*
是否是reactive或readonly产生的代理对象
*/
function isProxy (obj) {
return isReactive(obj) || isReadonly(obj)
}
/* 测试判断函数 */
console.log(isReactive(reactive({})))
console.log(isRef(ref({})))
console.log(isReadonly(readonly({})))
console.log(isProxy(reactive({})))
console.log(isProxy(readonly({})))
4. Composition API VS Option API
1) Option API的问题
在传统的Vue OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 ,滚动条反复上下移动
2) 使用Compisition API 我们可以更加优雅的组织我们的代码,函数。
让相关功能的代码更加有序的组织在一起
其它新组合和API 1. 新组件 1) Fragment(片断) 在Vue2中: 组件必须有一个根标签 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中 好处: 减少标签层级, 减小内存占用
aaaa
aaaa
2) Teleport(瞬移) Teleport 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签(很可能是body)下插入显示 ModalButton.vue
I'm a teleported modal!
(My parent is "body")
App.vue
App
3) Suspense(不确定的) 它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
LOADING...
AsyncComp.vue
AsyncComp22
{{msg}}
AsyncAddress.vue
{{data}}
2. 其他新的API 全新的全局API createApp() defineProperty() defineAsyncComponent() nextTick() 将原来的全局API转移到应用对象 app.component() app.config() app.directive() app.mount() app.unmount() app.use() 模板语法变化 v-model的本质变化 prop:value -> modelValue; event:input -> update:modelValue; .sync修改符已移除, 由v-model代替 v-if优先v-for解析
