QX-AI
GPT-4
QX-AI初始化中...
暂无预设简介,请点击下方生成AI简介按钮。
介绍自己
生成预设简介
推荐相关文章
生成AI简介
Vue3 {#Vue3}
Vue3 是由尤雨溪 等99位贡献者开发的一款前端框架。于 2020/09/18 正式发布,耗时2年多、2600+次提交、30+个RFC、600+次PR,在这里,框架将授予你「声明式 」、「组件化 」的编程模型,导引「响应式 」之力。你将扮演一位名为「CV工程师 」的神秘角色在自由的编码中邂逅样式各异、功能独特的组件库们,和他们一起解决问题,完成多样的需求------同时,逐步发掘「组合式API」的真相。
**注意:**尽管 Vue3 兼容大部分 Vue2 写法,但组合式 API 写法下,最好不要混用
Vue-cli创建工程 {#Vue-cli创建工程}
Vue3 推荐使用 Vite 来创建工程,但刚学完 vue2 还是接着用 Vue-cli 把 Vue3 新的语法学完,再去学习 Vite 和 pinia 的使用吧
工程结构和 vue2 中的一样,但文件内有些变化: main.js
|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10
| // 引入的不再是Vue构造函数 // 而是createApp工厂函数,无需通过new去调用 import { createApp } from 'vue' import App from './App.vue' // 创建应用实例,类似vm,但比vm更轻 const app = createApp(App) // 挂载 app.mount('#app')
|
单文件组件模板可以没有根元素
|-----------------|----------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4
| <template> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </template>
|
初识setup {#初识setup}
setup 是 Vue3 中一个新的配置项 ,值为一个函数。
组件中所有的数据、方法都要配置在 setup 中
setup函数有两种返回值
(1) 返回一个对象 ,对象中的所有属性都可以在组件模板中使用
(2) 返回一个渲染函数,会替换掉组件模板(基本用不到)
setup 执行是在创建实例之前,也就是 beforeCreate (之前)执行,所以 setup 函数中的 this 不是组件的实例,而是undefined,setup是同步的
|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <template> <h2>{{ name }}</h2> <h2>{{ age }}</h2> <button @click="showName">点击</button> </template> <script> export default { name: 'App', // setup配置项 setup(){ // 定义一些数据和方法 let name = 'chuckle'; let age = 20; function showName(){ alert(name); } // 返回一个对象 return { name, age, showName, } } } </script>
|
< script setup > {#lt-script-setup-gt}
在 setup()
函数中手动暴露大量的状态和方法非常繁琐。可以通过构建工具来简化该操作。
给 <script>
标签加上 setup 属性,里面的代码会被编译成 setup()
函数的内容
与普通的 <script>
只在组件被首次引入的时候执行一次不同,<script setup>
中的代码会在每次组件实例被创建的时候执行
|------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <h2>{{ name }}</h2> <h2>{{ age }}</h2> <button @click="showName">点击</button> </template> <script setup> // 定义一些数据和方法 let name = 'chuckle'; let age = 20; function showName() { alert(name); } </script>
|
setup带来的改变:
- 解决了vue2的data和methods方法相距太远,无法组件之间复用
- 提供了script标签引入共同业务逻辑的代码块,顺序执行
- script变成setup函数,默认暴露给模版
- 组件直接挂载,无需注册
- 自定义的指令也可以在模版中自动获得
- this不再是这个活跃实例的引用
- 带来的大量全新api,比如defineProps,defineEmits,withDefault,toRef,toRefs
响应式 {#响应式}
对比探究 Vue3 中的响应式使用与实现
ref函数 {#ref函数}
setup()
返回的数据并没有自带响应式 的效果,在 Vue3 中要实现响应式,需要使用 ref()
对数据进行处理
作用: 用 ref()
定义响应式 变量,将传入的值 包装为一个带 value 属性的 ref 对象(引用实现对象 ),允许我们创建可以使用任何值类型 的响应式数据
JS 中需要 .value 才能获取或修改数据,模板中直接写 ref 对象名
|------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <h2>{{ name }}</h2> <h2>{{ age }}</h2> <button @click="change">改变</button> </template> <script setup> // Vue3 遵循按需引入的理念,所以许多 vue 中的东西需要引入后才能使用 import { ref } from 'vue'; // 定义一些数据和方法 let name = ref('chuckle'); let age = ref(20); // 修改数据,响应式 function change() { name.value = 'qx'; age.value = 19; } </script>
|
ref()
传入值为基本数据类型 时,其响应式本质和 vue2 一样,是通过 Object.defineProperty()
的 getter 和 setter 进行数据劫持 和数据代理 实现的
|---------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 'chuckle', _value: 'chuckle'} dep: Set(1) {ReactiveEffect} __v_isRef: true __v_isShallow: false _rawValue: "chuckle" _value: "chuckle" // 熟悉的(...)数据代理 value: (...) // "chuckle" [[Prototype]]: Object constructor: class RefImpl // 熟悉的(...)数据劫持,添加get和set value: (...) //"chuckle" // 熟悉的 getter 和 setter get value: ƒ value() set value: ƒ value(newVal) [[Prototype]]: Object
|
ref()
传入值为对象类型 时,会去调用 reactive()
处理 value ,而 reactive()
返回一个 Proxy 类型的对象
暂时看不懂的东西:
|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: {...}, _value: Proxy(Object)} dep: undefined __v_isRef: true __v_isShallow: false _rawValue: {a: 1, b: 2} _value: Proxy(Object) [[Handler]]: Object [[Target]]: Object [[IsRevoked]]: false value: Proxy(Object) [[Handler]]: Object [[Target]]: Object a: 1 b: 2 [[Prototype]]: Object [[IsRevoked]]: false [[Prototype]]: Object
|
reactive函数 {#reactive函数}
reactive()
传入一个对象类型 ,返回该对象类型的代理对象(Proxy 的实例对象,简称proxy对象)
reactive()
仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的原始类型无效
proxy 对象直接用变量名访问数据,无需 .value
可以直接通过下标响应式 地修改数组数据,这是通过 defineProperty 实现的响应式所做不到的
|---------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <template> <h2>{{ name }}</h2> <h2>{{ age }}</h2> <button @click="change">改变</button> <h3>{{ obj.a }}</h3> <h3>{{ obj.b }}</h3> <h3>{{ arr.toString() }}</h3> </template> <script setup> import { ref, reactive } from 'vue'; // 定义一些数据和方法 let name = ref('chuckle'); let age = ref(20); let obj = reactive({ a: 1, b: 2, }); let arr = [1,2,3]; // 修改数据,响应式 function change() { name.value = 'qx'; age.value++; // proxy对象无需.value,可以直接访问 obj.a = 10; obj.b++; // 可以直接通过下标响应式地修改数组 arr[0] = 10; } </script>
|
在定义响应式数据 时,通常基本数据类型用 ref()
,引用数据类型用 reactive()
但由于 ref()
定义的响应式数据需要 .value 才能获取和修改数据值,所以也通常将基本数据类型扔进一个对象中,然后用 reactive()
,可以省去 .value
Vue2的响应式原理 {#Vue2的响应式原理}
依靠 Object.defineProperty()
实现原理:
- 基本数据类型: 通过 defineProperty 进行数据劫持与代理
- 对象类型: 深度遍历对象,通过 defineProperty 进行数据劫持与代理
- **数组类型:**通过重写更新数组的一系列方法(push等)
存在问题:
- 深度遍历对象存在性能与效率问题
- 对象新增、删除属性,无法响应式,需用 $set 和 $delete
- 直接通过下标修改数组,无法响应式
Vue3的响应式原理 {#Vue3的响应式原理}
依靠 ES6 中新的 API window.Proxy()
构造函数,解决了 Vue2 响应式中的问题
Proxy()
返回 proxy 对象,它可以代理对源数据 中任何属性 的任何操作
Proxy()
传入两个参数,第一个:要代理的源数据,第二个:一个配置对象,其中有 get、set 等方法,用于设置拦截
如果Proxy的第二个参数(配置对象)没有设置任何拦截,就等同于直接访问原对象
尝试使用 Proxy()
实现代理数据操作:
|------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| // 源数据 let obj = { name: 'chuckle', age: 20 } // 通过Proxy实例代理对源数据的增删改查操作 const agent = new Proxy(obj, { // 对操作的拦截配置 // 获取属性值 get(target, propName){ console.log(`读取了${propName}属性`); // 返回源数据的值 return target[propName] }, // 修改属性值、新增属性 set(target, propName, value){ console.log(`修改或新增了${propName}属性,去响应式地更新视图`); // 实际地修改源数据的值 target[propName] = value }, // 删除属性 deleteProperty(target, propName){ console.log(`删除了${propName}属性,去响应式地更新视图`); // 实际地删除源数据地属性,返回删除成功与失败地布尔值 return delete target[propName] } });
|
**注意:**后续在 proxy 对象上添加新属性也是响应式的
虽然上面已经模仿实现了"响应式",但 Vue3 中还用到了 window.Reflect
对象
Proxy()
用于代理 (代理对数据的操作),Reflect
用于反射(实际对数据的操作)
Reflect 用法:
Reflect.get(target, prop)
获取某个对象(target)中某个属性(prop)的值Reflect.set(target, prop, value)
修改某个对象中某个属性的值为valueReflect.deleteProperty(target, prop)
删除某个对象中的某个属性
Reflect 身上还有很多方法,ECMA 也在将原本 Object 中的方法移植到 Reflect 中
|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| // 源数据 let obj = { name: 'chuckle', age: 20 } // 通过Proxy代理对源数据的增删改查操作 const agent = new Proxy(obj, { // 对操作的拦截配置 // 获取属性值 get(target, propName){ console.log(`读取了${propName}属性`); // 返回源数据的值 return Reflect.get(target, propName) }, // 修改属性值、新增属性 set(target, propName, value){ console.log(`修改或新增了${propName}属性,去响应式地更新视图`); // 实际地修改源数据的值 return Reflect.set(target, propName, value) }, // 删除属性 deleteProperty(target, propName){ console.log(`删除了${propName}属性,去响应式地更新视图`); // 实际地删除源数据地属性,返回删除成功与失败地布尔值 return Reflect.deleteProperty(target, propName) } });
|
总结 Vue3 响应式的实现原理:
- **通过Proxy(代理):**拦截对象中任意属性的变化, 包括增删改查等。
- **通过Reflect(反射):**对源对象的属性进行实际操作。
props声明 {#props声明}
和 Vue2 一样,一个组件需要显式声明它所接受的 props
<script setup>
的单文件组件中,props 可以使用 defineProps()
宏来声明
|-------------------|-------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5
| <script setup> import { defineProps } from 'vue'; const props = defineProps(['foo']) console.log(props.foo) </script>
|
没有使用 <script setup>
的组件中,prop 可以使用 props 选项来声明:
|-----------------------|---------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7
| export default { props: ['foo'], // 声明props setup(props) { // setup() 接收 props 作为第一个参数 console.log(props.foo) } }
|
props 也是一个 proxy 对象,也能实现响应式
注意: 所有的 props 都遵循着单向绑定 原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,不应该在子组件中去更改一个 prop。
context上下文 {#context上下文}
context 是 setup(props, context)
的第二个参数,代表上下文
context 中有一些属性和方法
- attrs 没有接收的 prop 都会出现在里面
emit()
触发父组件绑定来的自定义事件- slots 没有接收的 slot 都会出现在里面
<script setup>
中 useContext() 已经废弃
需要使用独立的 API 获取原本 context 中的属性和方法:
useAttrs()
没有接收的 prop 都会出现在里面defineEmits()
接收父组件绑定的自定义事件useSlots()
获取父组件中插槽传递的所有虚拟Dom对象,按插槽名字区分
需要引入后使用:
|-----------|---------------------------------------------------------------|
| 1
| import { useAttrs,defineEmits, useSlots } from 'vue';
|
1、 useAttrs()
|---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13
| <!-- 父组件 --> <student :a="1" b="giggles"></student> <!-- 子组件 --> <script setup> // 接收a const props = defineProps(['a']) console.log(props) // 没接收的b会在里面 const attrs = useAttrs(); console.log(attrs) // 一个proxy对象 </script>
|
2、 defineEmits()
|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!-- 父组件 --> <student @emitTest="emitTest"></student> <script setup> function emitTest(value) { console.log(value); } </script> <!-- 子组件 --> <script setup> // 接收自定义事件 const emit = defineEmits(['emitTest']); // 触发自定义事件 emit('emitTest', name) </script>
|
3、 useSlots()
|------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!-- 父组件 --> <student> <template #slotTest> <h2>插槽</h2> </template> </student> <!-- 子组件 --> <slot name="slotTest"></slot> <script setup> const slots = useSlots(); console.log(slots) </script>
|
defineExpose函数 {#defineExpose函数}
子组件通过 defineExpose()
向父组件暴露数据
当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样 { a: number, b: number } (ref 会和在普通实例中一样被自动解包)
|------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <!-- 父组件 --> <student ref="stu"></student> <script setup> // 接收子组件 let stu = ref(null); onMounted(() => { console.log(stu.value.name); // chuckle console.log(stu.value.city); // 北京 }) </script> <!-- 子组件 --> <script setup> let name = ref("chuckle"); let city = ref("北京"); // 暴露数据 defineExpose({ name, city }) </script>
|
computed计算属性 {#computed计算属性}
computed()
接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。
|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { computed } from 'vue'; let person = reactive({ firstName: '张', lastName: '三' }) let name = computed(()=>{ return person.firstName + person.lastName; }) // 可读写的: let refObj = computed({ get(){}, set(value){} })
|
watch函数 {#watch函数}
watch()
监视一个或多个响应式数据源 ,并在数据源变化时调用所给的回调函数。
返回值是一个用来停止监听的的函数
基本使用:
|---------------------|------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6
| watch(refObj, (newValue, oldValue)=>{ console.log(newValue, oldValue); }, { // 配置对象 deep: true, })
|
1、可以同时监视多个响应式数据,回调函数接受两个数组,分别对应来源数组中的新值和旧值:
|-----------|--------------------------------------------------------------------------|
| 1
| watch([name, age], ([newName, newAge], [oldName, oldAge]) => {})
|
2、当监听一个 reactive 定义的 proxy 响应式对象时,侦听器会强制启用深层模式 ,但回调函数收到的新值 和旧值参数将是同一个对象
|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11
| let person = reactive({ firstName: '张', lastName: '三' }) watch(person, (newValue, oldValue)=>{ console.log(newValue,oldValue); // Proxy(Object){firstName: '张一', lastName: '三'} //Proxy(Object){firstName: '张一', lastName: '三'} console.log(newValue===oldValue); // true })
|
3、监听 proxy 对象中某一个基本数据类型 的属性,新值 和旧值 可以正常获取,但监听对象需写成函数返回值形式
|---------------------|----------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6
| let person = reactive({ a: { b: 1, c: 2 } }) watch(()=>person.a.b, (newValue, oldValue)=>{ console.log(newValue, oldValue); })
|
4、监听 proxy 对象中某一个对象类型 的属性,新值 和旧值是同一个对象
|---------------------|----------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6
| let person = reactive({ a: { b: 1, c: 2 } }) watch(person.a, (newValue, oldValue)=>{ console.log(newValue, oldValue); })
|
5、若监视一个使用 ref()
定义的对象类型 的响应式数据 ,则需要 deep: true 开启深度监视,或去监视其 .value(此时value是一个proxy对象,会自动开启深度监视)
|---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13
| let person = ref({ a: { b: 1, c: 2 } }) watch(person, (newValue, oldValue)=>{ console.log(newValue, oldValue); },{ deep: true, // 直接监视ref对象需要手动开启深度监视 }) // 也可以去监视其.value watch(person.value, (newValue, oldValue)=>{ console.log(newValue, oldValue); })
|
watchEffect函数 {#watchEffect函数}
watchEffect()
立即运行 一个回调函数,同时响应式地追踪其依赖 ,并在依赖更改 时重新执行回调
返回值是一个用来停止监听的的函数
|---------------|----------------------------------------------------------------------------------------------------------------------|
| 1 2 3
| const count = ref(0) watchEffect(() => console.log(count.value)) // 立即执行回调,输出 0 count.value++ // 重新执行回调,输出 1
|
和计算属性有些像,计算属性重在最后的返回值,而watchEffect重在逻辑过程,而返回值确定
Vue3生命周期 {#Vue3生命周期}
Vue3 生命周期钩子名称改为了 on 开头,但作用和 Vue2 中的差不多,但也添加了一些新钩子,有需要在文档中查看用法
Vue3 生命周期图:
自定义hook {#自定义hook}
hook 本质是一个函数。
自定义hook ,就是将 setup 中使用过的组合式API 进行封装,写在一个单独的 JS 文件中并暴露出去,实现代码复用,命名通常以 use 开头,集中放在 hooks 文件夹中
作用: 在 Vue3 中通过组合式函数 实现 JS 代码复用,而避免使用 mixin 混入
组合式函数: 是一个利用 Vue 的组合式 API 来封装和复用有状态 和相关逻辑 的函数。
**案例:**写一个实时返回鼠标坐标的 hook,让任何组件引入即可使用 /hooks/useMouse.js
|---------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { reactive, onMounted, onBeforeUnmount } from "vue" // 暴露出该hook函数 export default function (){ // 状态 let coordinate = reactive({ x: 0, y: 0, }) // 修改状态 function monitorMouse(event){ coordinate.x = event.pageX; coordinate.y = event.pageY; } // 组件挂载完开始监听 onMounted(()=>{ window.addEventListener('mousemove', monitorMouse) }) // 组件卸载前结束监听 onBeforeUnmount(()=>{ window.removeEventListener('mousemove', monitorMouse) }) // 将状态返回出去给组件使用 return coordinate }
|
组件中展示鼠标坐标,非常方便,引入hook并执行,获取状态,然后使用状态
|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11
| <template> <!-- 使用hook函数返回的状态 --> <span>X:{{ coordinate.x }} Y:{{ coordinate.y }}</span> </template> <script setup> // 引入hook import useMouse from '@/hooks/useMouse'; // 执行hook函数获取数据 let coordinate = useMouse(); </script>
|
toRef与toRefs {#toRef与toRefs}
toRef()
可以将值、refs 或 getters 规范化为 refs
也可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然
|-----------|---------------------------------------------------|
| 1
| let refObj = toRef(proxyObj, 'propName');
|
为什么要用 toRef()
:
当直接解构 proxy 响应式对象时,对于属性值是基本数据类型的属性,传的是值,而非引用地址,解构后的变量会失去响应式的功能
|------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <h3>{{ a }}</h3> <h3>{{ b }}</h3> </template> <script setup> let obj = reactive({ a: { b: 1, c: 2, } }) // 解构obj let a = obj.a; // 传的地址,响应式仍在 console.log(a); // Proxy(Object) {b: 1, c: 2} let b = obj.a.b; // 传的数值,响应式丢失 console.log(b); // 1 </script>
|
使用 toRef()
可以不用重新将解构的值通过 ref()
创建一个独立 的 ref 对象,而是直接用源对象中的属性,通过引用关系,创建一个对应的 ref 对象,与其源属性保持同步
|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7
| let obj = reactive({ a: { b: 1, c: 2 } }) // 创建一个对应的 ref let b = toRef(person.a, 'b'); console.log(b); // ObjectRefImpl {_object: Proxy(Object), _key: 'b', _defaultValue: undefined, __v_isRef: true}
|
toRefs()
功能与 toRef()
差不多,但可以批量处理某个 proxy 对象中的所有属性(浅层),返回一个对象,对象中存着源对象属性对应的 ref 对象
|-----------|----------------------------------------|
| 1
| let refArr = toRefs(proxyObj);
|
总结: toRef()
创健一个 ref 对像,其 value 值指向另一个对象中的某个属性
其它组合式API {#其它组合式API}
介绍较不常用的组合式API
响应式数据的判断 {#响应式数据的判断}
1、isRef()
检查某个值是否为 ref 对象
2、unref()
如果参数是 ref,则返回其 value 值,否则返回参数本身
2、isProxy()
检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理
3、isReactive()
检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理
浅层响应式 {#浅层响应式}
shallowReactive()
reactive() 的浅层作用形式,只对浅层属性进行响应式处理
shallowRef()
ref() 的浅层作用形式,只有浅层的 value 是响应式的
只读API {#只读API}
readonly()
深层的只读,接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理
shallowReadonly()
浅层的只读
toRaw、markRaw {#toRaw、markRaw}
toRaw()
根据一个 Vue 创建的 proxy 代理对象返回其原始对象的引用地址
markRaw()
将一个对象标记为不可被转为 proxy 代理。返回该对象本身。可以让某些数据追加到 proxy 对象上后没有响应式功能。
自定义ref {#自定义ref}
customRef()
创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
接收一个工厂函数作为参数,这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象
track()
追踪数据变化,trigger()
重新解析模板
**作用:**用于防抖、延迟更新视图等需求
|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { customRef } from 'vue' function myRef(value){ return customRef((track, trigger)=>{ return { get() { track() // 追踪数据变化 return value }, set(newValue) { value = newValue trigger() // 重新解析模板 } } }) }
|
依赖注入 {#依赖注入}
provide()
祖先组件提供一个值,可以被后代组件注入。接受两个参数:第一个参数是要注入的 key(名字),第二个参数是要注入的值
inject()
注入一个由祖先组件或整个应用 (app.provide()) 提供的值。
通过这两个 API 可以实现祖先与后代组件的通信,非常方便
|---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13
| // 祖先组件 import { provide } from 'vue'; let person = reactive({ firstName: '张', lastName: '三', }) provide('person',person) // 后代组件 import { inject } from 'vue'; let person = inject('person'); console.log(person); // 获取成功
|
注意: provide()
、inject()
必须在组件的 setup() 阶段同步调用
Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会"覆盖"链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回 undefined,除非提供了一个默认值。
Fragment组件 {#Fragment组件}
在 Vue2 中,组件模板必须有一个根标签
在 Vue3 中,若模板中没有根标签,会将模板包含在一个 fragment 虚拟元素中,不会被实际渲染出来
Teleport组件 {#Teleport组件}
<teleport to="">
可以将组件模板、插槽移动到指定的任意元素中(尾插),to 中写 css 选择器
一个简单的弹窗组件:
|---------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <template> <div> <button @click="showPopUp">点击显示弹窗</button> <teleport to='body'> <div class="popup-mask" v-show="popup.isShow"> <div class="popup"> <h3>弹窗内容</h3> <button @click="showPopUp">关闭弹窗</button> </div> </div> </teleport> </div> </template> <script setup> import { reactive } from 'vue'; let popup = reactive({ isShow: false, }) function showPopUp() { popup.isShow = !popup.isShow; } </script> <style lang="less" scoped> .popup-mask { position: fixed; width: 100%; height: 100%; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.6); } .popup { position: relative; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 200px; height: 300px; background: rgb(60, 60, 60); text-align: center; } </style>
|
Suspense组件(实验) {#Suspense组件-实验}
**作用:**异步地动态加载子组件,父组件加载完就直接显示,不等待异步的子组件
将要异步加载的子组件使用 <Suspense>
包裹
<Suspense>
提供两个有名插槽,default 最终要加载的子组件,fallback 子组件还未加载出来时展示的模板
|------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div class="app"> <h3>我是App组件</h3> <Suspense> <!-- default插槽 --> <template v-slot:default> <Child/> </template> <!-- fallback插槽 --> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
|
除了 <Suspense>
还可以使用 defineAsyncComponent()
异步引入组件
|-------------|-----------------------------------------------------------------------------------------------------------------------------|
| 1 2
| import { defineAsyncComponent } from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
|
异步引入组件后:
(1) 该组件的 setup() 就可以是 async 函数,可以用 await,可以返回 promise 对象。
(2) <script setup>
中也可以使用顶层 await。结果代码会被编译成 async setup()
Vue3的其它变化 {#Vue3的其它变化}
1、全局 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 |
2、Vue动画中过度类名的更改: Vue2 写法
|-------------------------|-------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8
| .v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
|
Vue3 写法
|-------------------------|-----------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8
| .v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
|
3、 keyCode 不再作为 v-on 的修饰符,同时也不再支持 config.keyCodes
4、 移除 v-on.native
修饰符
5、 移除过滤器 filter
6、 父组件给子组件绑定自定义事件,子组件中需要接收才能使用,
(1) defineEmits()
接收父组件绑定的自定义事件
(2) 或者选项式中的 emits 配置项
THE END