1. 认识Vue3
源地址:https://24kcs.github.io/vue3_study/00_%E8%AF%BE%E7%A8%8B%E4%BB%8B%E7%BB%8D.html
特点:
- 打包大小减少41%,初次渲染快55%, 更新渲染快133%,内存减少54%
- 支持vue2的大多数特性
- 更好的支持Typescript
- 使用
Proxy
代替defineProperty
实现数据响应式 - 重写虚拟DOM的实现和Tree-Shaking
新增特性
- Composition (组合) API
- setup
- ref 和 reactive
- computed、watch、watchEffect
- 新的生命周期函数
- provide与inject
- 新组件
- Fragment - 文档碎片
- Teleport - 瞬移组件的位置
- Suspense - 异步加载组件的loading界面
- 其它API更新
- 全局API的修改
- 将原来的全局API转移到应用对象(App)
- 模板语法变化
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 <project-name>
cd <project-name>
npm install
npm run dev
3. Vue2与Vue3的响应式原理比较(重要)
1) Vue2的响应式
- 核心:
- 对象: 通过
defineProperty
对对象的已有属性值的读取和修改进行劫持(监视/拦截) - 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持(push、pop、shift、unshift、splice、sort、reverse)
- 对象: 通过
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
- 问题
- 对象直接新添加的属性或删除已有属性, 界面不会自动更新
- 直接通过下标替换元素或更新length(arr[1] = {}), 界面不会自动更新
2) Vue3的响应式
- 核心:
- 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 添加, 删除等
- 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
const user = {
name: "John",
age: 12
};
/*
proxyUser是代理对象, user是被代理对象
后面所有的操作都是通过代理对象来操作被代理对象内部属性
*/
const proxyUser = new Proxy(user, {
get(target, prop) {
console.log('劫持get()', prop)
return Reflect.get(target, prop)
},
set(target, prop, val) {
console.log('劫持set()', prop, val)
return Reflect.set(target, prop, val); // (2)
},
deleteProperty(target, prop) {
console.log('劫持delete属性', prop)
return Reflect.deleteProperty(target, prop)
}
});
// 读取属性值
console.log(proxyUser === user)
console.log(proxyUser.name, proxyUser.age)
// 设置属性值
proxyUser.name = 'bob'
proxyUser.age = 13
console.log(user)
// 添加属性
proxyUser.sex = '男'
console.log(user)
// 删除属性
delete proxyUser.sex
console.log(user)
4. Composition API(常用部分)
文档: https://composition-api.vuejs.org/zh/api.html
1) setup
- setup执行
- 在
beforeCreate
之前执行(一次), 此时组件对象还没有创建 - this是undefined, 不能通过this来访问data/computed/methods / props
- 在
- setup的返回值
- 一般都返回一个对象为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
- 返回对象中的属性、方法会与data函数、methods中的合并
- 如果有重名, setup优先,一般不要混合使用,来源混乱
- methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
- setup不能是一个async函数: 因为返回值不再是对象, 而是promise, 模板看不到return对象中的属性数据(可变成异步组件)
- 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}}
2) ref
- 作用: 通常定义一个基本类型的响应式数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用(reference)对象
- js中操作数据: xxx.value
- 模板中操作数据: 不需要.value
{{count}}
利用ref函数获取组件中的标签元素
// 功能需求: 让输入框自动获取焦点
App
---
3) reactive
- 作用: 定义复杂数据类型的响应式代理
- 语法:
const proxy = reactive(obj)
- 接收一个普通对象然后返回该普通对象的响应式代理器对象
- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
name: {{state.name}}
age: {{state.age}}
wife: {{state.wife}}
4) reactive与ref-细节
- ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
- 如果用ref对象/数组, 内部会自动将xxx.value的对象/数组转换为reactive的代理对象
- ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
- reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
- ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
App
m1: {{m1}}
m2: {{m2}}
m3: {{m3}}
5) computed和watch
- computed函数:
- 与vue2的computed配置功能一致
- 有getter和setter
- watch函数
- 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
- 默认初始时不执行回调, 但可以通过配置
immediate
为true, 来指定初始时立即执行第一次 - 通过配置
deep
为true, 来指定深度监视 - 如果是ref对象, 直接指定;是reactive对象中的属性, 必须通过函数来指定
- watchEffect函数
- 不用直接指定要监视的数据, 自动监视内部响应式数据,发生变化时回调执行
- 默认初始时就会执行第一次, 从而可以收集需要监视的数据
App
fistName:
lastName:
fullName1:
fullName2:
fullName3:
6) 生命周期
vue2.x的生命周期: | vue3的生命周期: |
---|---|
与 2.x 版本生命周期相对应的组合式 API
beforeCreate
-> 使用setup()
created
-> 使用setup()
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestroy
->onBeforeUnmount
// 名称修改destroyed
->onUnmounted
errorCaptured
->onErrorCaptured
而且两种生命周期函数同时存在时,3.0的onXXX函数优先执行
新增的钩子函数
组合式 API 还提供了以下调试钩子函数:
onRenderTracked
onRenderTriggered
msg: {{msg}}
7) toRef
对比ref
toRef
为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步更新- ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
应用: 当要将某个prop 的 ref 传递给复合函数时,toRef 很有用
App
{{state}}
{{foo}}
{{foo2}}
Child
{{foo}}
{{length}}
8) toRefs
把一个响应式对象转换成普通对象,但该普通对象的每个 property 都是一个 ref
应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
App
foo: {{foo}}
bar: {{bar}}
foo2: {{foo2}}
bar2: {{bar2}}
9) customRef
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
需求: 使用 customRef 实现 debounce 的示例
App
{{keyword}}
10) shallowReactive 与 shallowRef
- shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
- shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
- 什么时候用浅响应式呢?
- 一般情况下使用ref和reactive即可
- 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
- 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
App
m1: {{m1}}
m2: {{m2}}
m3: {{m3}}
m4: {{m4}}
11) readonly 与 shallowReadonly
- readonly:
- 深度只读数据
- 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
- 只读代理是深层的:访问的任何嵌套 property 也是只读的。
- shallowReadonly
- 浅只读数据
- 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
- 应用场景:
- 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
App
{{state}}
12) toRaw 与 markRaw
- toRaw
- 返回由
reactive
或readonly
方法转换成响应式代理的普通对象。 - 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
- 返回由
- markRaw
- 标记一个对象,使其永远不会转换为代理。返回对象本身
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
{{state}}
13) 响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
14) 自定义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}}<template><div> <h2>x: {{x}}, y: {{y}}</h2></div></template><script>import { ref } from "vue"/* 在组件中引入并使用自定义hook自定义hook的作用类似于vue2中的mixin技术自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂*/import useMousePosition from './hooks/useMousePosition'export default { setup() { const {x, y} = useMousePosition() return { x, y, } }}</script>
利用TS泛型强化类型检查
需求2: 封装发ajax请求的hook函数
hooks/useRequest.ts
import { ref } from 'vue'import axios from 'axios'/* 使用axios发送异步ajax请求*/export default function useUrlLoader<T>(url: string) { const result = ref<T | null>(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, }}<template><div class="about"> <h2 v-if="loading">LOADING...</h2> <h2 v-else-if="errorMsg">{{errorMsg}}</h2> <!-- <ul v-else> <li>id: {{result.id}}</li> <li>name: {{result.name}}</li> <li>distance: {{result.distance}}</li> </ul> --> <ul v-for="p in result" :key="p.id"> <li>id: {{p.id}}</li> <li>title: {{p.title}}</li> <li>price: {{p.price}}</li> </ul> <!-- <img v-if="result" :src="result[0].url" alt=""> --></div></template><script lang="ts">import { watch} from "vue"import useRequest from './hooks/useRequest'// 地址数据接口interface AddressResult { id: number; name: string; distance: string;}// 产品数据接口interface ProductResult { id: string; title: string; price: number;}export default { setup() { // const {loading, result, errorMsg} = useRequest<AddressResult>('/data/address.json') const {loading, result, errorMsg} = useRequest<ProductResult[]>('/data/products.json') watch(result, () => { if (result.value) { console.log(result.value.length) // 有提示 } }) return { loading, result, errorMsg } }}</script>
5. Composition API 对比Option API
1) Option API的问题
- 在传统的Vue OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 ,滚动条反复上下移动
2) 使用Compisition API
关注点分离:我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起
6. 新组件
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}}
7. 其他新的API
provide 与 inject
- provide和inject提供依赖注入,功能类似 2.x 的provide/inject
- 实现跨多层级组件(祖孙)间通信
父组件
当前颜色: {{color}}
子组件
孙子组件: {{color}}
全新的全局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解析