创建 Vue3 工程
- 使用 vue-cli 创建
// @vue/cli@4.5.0 或以上
// 创建项目
vue create vue_test
// 启动
cd vue_test
npm run serve
// or
yarn serve
- 使用 vite 创建工程
// 创建工程
npm init vite-app <project-name>
// or
yarn init vite-app <project-name>
// 安装依赖
cd <project-name>
npm install
// or
yarn
// 运行
npm run dev
// or
yarn dev
- vite 是下一代的前段构建工具
- 其在开发环境中,无需打包操作,可快速的冷启动
- 轻量快速的热重载(HMR)
- 真正的按需编译,不再等待整个应用编译完成
- 传统构建与 vite 构建对比图
Composition API
拉开序幕的 setup
- setup 是 Vue3.0 中的一个新的配置项,值为一个函数
- 所有的 Composition API(组合 API)都会配置在 setup 中
<script> export default { name: 'App', setup(){ // 数据 } } </script>
- setup 函数有两种返回值
- 返回一个对象,则对象中的属性、方法,在模板中均可以使用
- 如返回一个渲染函数,则可以自定义渲染内容 (了解一下就好,会覆盖模板)
<script> import { h } from 'vue' export default { name: 'App', setup(){ ... return ()=> h('h1', 'Hello') } } </script>
- 注意:
- 在 Vue3 中,可以兼容 Vue2 中的配置项,但是不建议这么做,因为在 Composition API 中读取不到 options 配置选项中的数据
- 正常来讲 setup 不能是一个 async 函数,因为返回值不再是 return 的对象,而是 promise,模板看不到 return 对象中的属性,除非配置了异步组件(使用了 Suspense + 异步组件)
- setup 会在 beaforeCreate 之前执行一次, this 是 undefined
- setup 可以接收两个参数:props 和 context
ref 函数
- ref 函数通过 Object.defineproperty 实现 Vue3 中简单数据类型的响应式,经过 ref 处理后的数据,会成为RefImpl(引用实现)的实例对象,简称引用对象(reference 对象)
<script> import { ref } from 'vue' export default { name: 'App', setup(){ let name = ref('张三') let age = ref(18) } } </script>
- 通过 value 读取或者修改引用对象(在模板中读取数据不需要 .value)
... setup(){ let name = ref('张三') let age = ref(18) // name.value age.value }
- ref 函数接收的数据类型可以是基本类型,也可以是对象类型,对于基本类型,响应式依然是依靠 Object.defineProperty() 的 get 与 set 实现的,对于对象类型, ref 函数内部“求助了” Vue3中的 rective 函数
reactive 函数
- reactive 函数只能用于定义一个对象类型的响应式数据
- reactive 定义的响应式数据是“深层次的”
- 从 reactive 的实例(代理)对象中读取或者修改数据时,不需要 .value
- reactive 是依赖 ES6 中的 Proxy 实现的
- 插入一个小技巧 自定义折叠代码块
// #region
...
// #endregion
Vue3 中的响应式原理
- Vue2 的响应式
- 实现原理:
- 对象类型: 通过 Object.defineProperty() 对对象的已有属性值的读取、修改进行拦截(数据劫持)
- 数组类型: 通过重写更新数组的一系列方法来实现拦截(对数组的变异方法进行了包裹)
Object.defineProperty(data, 'count', { get(){}, set(){} })
- 存在的问题:
- 新增属性、删除属性,界面不会更新
- 要通过
this.$set(目标对象,'要添加的属性',属性值)
和 this.$delete() 来处理
- 要通过
- 直接通过下标修改数组,界面不会自动更新
- 要通过 this.$set(目标数组,索引,值) 和 数组的变异方法来处理
- 新增属性、删除属性,界面不会更新
- 实现原理:
- Vue3.0 的响应式
- 实现原理:
- 通过 Proxy(代理): 拦截对象中任意属性的变化,包括属性值的读写、属性的添加和删除
- 通过 Reflect(反射): 对被代理对象的属性进行操作
const person = {name: '张三', age: 18} const p = new window.Porxy(person,{ // 数据被读取时调用 get(target,propName){ // target: 源数据 person propName:读取的属性 console.log(`${target}中的${propName}属性被读取了`) return reflect.get(target, propName) }, // 数据被更新或新增时调用 set(target, propName, value){ console.log(`${target}中的${propName}属性被修改了,更新界面`) reflect.set(target, propName, value) }, // 数据被删除时调用 deleteProperty(target, propName){ console.log(`${target}中的${propName}属性被删除了,更新界面`) return reflect.deleteProperty(target, propName) } })
- 实现原理:
Vue3.0 中的 watch
- Vue3.0 中的 watch 是一个 Composition API,所以使用之前需要引入
- watch 接收三个参数:被监视的数据、回调函数、配置项
- 坑1:当 watch 用于监视 reactive 定义个响应式数据时,无法获取正确的 oldValue
- 坑2:强制开启深度监视,deep 配置无效
import {watch, ref, toRef, toRefs} from 'vue'
export default {
setup(){
let sum = ref(0)
let msg = ref('hello')
let person = reactive({
name: '张三',
age: 18,
job:{
j1:{
salary: 20
}
}
})
// 1. 场景一:监视 ref 所定义的一个响应式数据
watch(sum,(newValue, oldValue)=>{},{immediate: true})
// 2. 场景二:监视 ref 所定义的多个响应式数据
watch([sum,msg],(newValue,oldValue)=>{})
// 3. 场景三:监视 reactive 所定义的一个响应式数据的全部属性
watch(person,(newValue,oldValue)=>{},{deep: false}) // 此时无法获取正确的 oldValue
// 4. 场景四:监视 reactive 所定义的一个响应式数据中的一个属性
watch(()=>person.name,(newValue,oldValue)=>{})
// 5. 场景五:监视 reactive 所定义的一个响应式数据中的多个属性
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{})
// 6. 场景六:监视 reactive 所定义的一个响应式数据中的一个对象属性
watch(()=>person.job,(newValue,oldValue)=>{},{deep:true}) // 此时必须开启深度监视
}
return {
sum,
msg,
person,
/* toRef 的作用是创建一个 ref 对象,其 value 值指向另一个对象中的某个属性,用来使模板更加精简 */
name: toRef(person, 'name'),
age: toRef(person, 'age'),
salary: toRef(person.job.j1, 'salary')
// 或者直接使用 toRefs,与 toRef 功能相似,但可以批量创建多个 ref 对象
...toRefs(person)
}
}
- watchEffect 函数
- watchEffect 不用知名监视哪个属性,在函数的回调中,用到了 setup 中的哪个属性,就监视哪个属性
- watchEffect 所指定的回调中用到的数据发生变化,则直接重新执行回调
- watchEffect 默认开启了 immediate 和 deep
- watchEffect 有点像 computed
- 但 computed 更注重计算出来的值,所以必须写返回值
- watchEffect 更注重过程,不需要写返回值
watchEffect(()=>{ const a = sum.value const b = person.name console,log('watchEffect 指定的回调执行了') })
自定义 hook 函数
- hook 本质是一个函数,把 setup 函数中使用的 Composition API 进行封装,类似 Vue2 中的 mixin
- 自定义 hook 的优势: 复用代码,让 setup 中的逻辑更清晰
- 将需要复用的代码封装在 src/hooks/xxx.js 中,比如实现一个获取鼠标坐标的功能:src/hooks/usePoint.js
// src/hooks/usePoint.js import {onMount, onBeforeUnmount, reactive} from 'vue' export default function(){ let point = reactive({x:0, y:0}) function savePoint(event){ point.x = event.pageX point.y = event.pageY } onMount(){ window.addEventListener('click',savePoint) } onBeforeUnmount(){ window.removeEventListener('click',savePoint) } return point }
- 在组件中使用自定义 hooks
import usePoint from '../hooks/usePoint.js' export default { setup(){ let {x, y} = usePoint() return {x, y} } }
其他 Composition API
-
shallowReactive(只处理对象最外层属性的响应式) 与 shallowRef(只处理基本类型,不进行对象的响应式处理)
- 使用时机:
- shallowReactive:如果有一个对象数据,结构比较深,但变化时只是外层属性变化
- shallowRef: 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换
- 使用时机:
-
readonly (让一个响应式数据变为只读的,深只读) 与 shallowReadonly (浅只读)
-
toRaw :将一个由 reactive 生成的响应式对象转为普通对象,用于读取此普通对象的所有操作,不会引起页面的刷新
-
markRaw: 标记一个对象,使其用于不会再成为响应式对象
- 有些值不应被设置为响应式的,例如复杂的第三方类库
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
-
customRef:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显示控制
- 实现防抖效果:
<template> <imput type="text" v-model="keyword"> <h3>{{keyword}}</h3> </tamplate> <script> import {ref, customRef} from 'vue' export default { name: 'Demo', setup(){ // 自定义一个 myRef function myRef(value,delay){ let timer //节流阀 // 通过 customRef 实现自定义 return customRef((track, trigger)=>{ return { // customRef 中的回调必须返回一个对象 get(){ track() // 通知 Vue 追踪数据的变化 return value }, set(newValue){ clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() // 通知 Vue 重新解析模板 },delay) } } }) } let keyword = myRef(keyword, 1000) return {keyword} } } </script>
-
provide 与 inject : 实现祖孙组件间的通信
- 父组件定义数据:
setup(){ ... let car = reactive({name:'Benz',price:'40W'}) provide('car',car) }
- 后代组件接收:
setup(){ ... const car = inject('car') return {car} }
-
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
- isReadonly: 检查一个对象是否由 readonly 创建的只读代理
- isProxy: 检查一个对象是否由 reactive 或者 readonly 方法创建的代理
新的组件
- Teleport 是一种能够将我们的组件 html 结构移动到指定位置的技术
<teleport to="位置,可以写标签名、CSS选择器等"> <div v-if="isShow" class="mask"> <div> <h3>我是一个弹窗</h3> <button @click="isShow=false">关闭</button> </div> </div> </teleport>
- Suspense 用来解决在异步组件加载过慢时,出现的抖动问题
- 要理解 Suspense,首先要明白异步组件
<template> <h3>App 组件</h3> <!-- 直接将异步组件包裹起来就可以 --> <Suspense> <template v-slot:default> <!-- 成功了展示这个 --> <Child /> </template> <template v-slot:fallback> <!-- 成功之前展示这个 --> <h3>加载中</h3> </template> </Suspense> </tempalte> <script> // import Child from './components/Child.vue' 静态引入,如果引入失败,其子组件均不会展示 // 通过 defineAsyncComponent 动态的引入组件(异步引入) import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue')) export default { name: 'App', compoents:{Child} } </script>
- 要理解 Suspense,首先要明白异步组件