51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Vue笔记[六]-Vue3

QX-AI
GPT-4
QX-AI初始化中...
暂无预设简介,请点击下方生成AI简介按钮。
介绍自己
生成预设简介
推荐相关文章
生成AI简介

Vue3 {#Vue3}

Vue3 是由尤雨溪99位贡献者开发的一款前端框架。于 2020/09/18 正式发布,耗时2年多、2600+次提交30+个RFC600+次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带来的改变:

  1. 解决了vue2的data和methods方法相距太远,无法组件之间复用
  2. 提供了script标签引入共同业务逻辑的代码块,顺序执行
  3. script变成setup函数,默认暴露给模版
  4. 组件直接挂载,无需注册
  5. 自定义的指令也可以在模版中自动获得
  6. this不再是这个活跃实例的引用
  7. 带来的大量全新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()gettersetter 进行数据劫持数据代理 实现的

|---------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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()

实现原理:

  1. 基本数据类型: 通过 defineProperty 进行数据劫持与代理
  2. 对象类型: 深度遍历对象,通过 defineProperty 进行数据劫持与代理
  3. **数组类型:**通过重写更新数组的一系列方法(push等)

存在问题:

  1. 深度遍历对象存在性能与效率问题
  2. 对象新增、删除属性,无法响应式,需用 $set 和 $delete
  3. 直接通过下标修改数组,无法响应式

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 用法:

  1. Reflect.get(target, prop) 获取某个对象(target)中某个属性(prop)的值
  2. Reflect.set(target, prop, value) 修改某个对象中某个属性的值为value
  3. Reflect.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) } }); |

MDN 文档: ProxyReflect

总结 Vue3 响应式的实现原理:

  1. **通过Proxy(代理):**拦截对象中任意属性的变化, 包括增删改查等。
  2. **通过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上下文}

contextsetup(props, context) 的第二个参数,代表上下文

context 中有一些属性和方法

  1. attrs 没有接收的 prop 都会出现在里面
  2. emit() 触发父组件绑定来的自定义事件
  3. slots 没有接收的 slot 都会出现在里面

<script setup> 中 useContext() 已经废弃

需要使用独立的 API 获取原本 context 中的属性和方法:

  1. useAttrs() 没有接收的 prop 都会出现在里面
  2. defineEmits() 接收父组件绑定的自定义事件
  3. 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,显式声明对其依赖追踪和更新触发的控制方式。

接收一个工厂函数作为参数,这个工厂函数接受 tracktrigger 两个函数作为参数,并返回一个带有 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

赞(0)
未经允许不得转载:工具盒子 » Vue笔记[六]-Vue3