当前位置: 首页 > news >正文

vue3笔记(1)自用

目录

一、ref和reactive的区别

1. ref

2. reactive

3、核心区别

4、原理差异

1. ref 的实现

2. reactive 的实现

5、常见误区

二、计算属性(Computed)

1. 基本用法

2. 计算属性的 setter 用法

3. 计算属性的特点

三、Watch 的使用

1. 基础监听(watch 函数)

2. 深度监听(对象属性)

3. 立即执行监听(immediate 选项)

4. watchEffect 自动追踪依赖

四、计算属性 vs Watch 的选择

 五、生命周期API

六、组合式API下的组件间数据传递

一、父传子:通过 props 传递数据

二、子传父:通过 emits 触发事件

三、双向绑定:v-model 语法糖

四、provide/inject:跨层级通信

五、使用 $parent 和 $children(不推荐)

六、使用事件总线或状态管理

总结

七、组合式API下的模板引用

一、组合式 API 中的模板引用

示例:获取 DOM 元素

示例:获取组件实例

二、使用 $refs(选项式 API 风格)

三、动态绑定引用

四、组件引用与跨组件访问

五、注意事项

总结


一、ref和reactive的区别

1. ref

  • 用途:创建一个响应式的值类型(如 numberstringboolean)或引用类型(如对象、数组)。
  • 语法const count = ref(0)
  • 访问方式:通过 .value 访问值(如 count.value)。

2. reactive

  • 用途:创建一个响应式的对象数组(引用类型)。
  • 语法const state = reactive({ count: 0 })
  • 访问方式:直接访问属性(如 state.count)。

3、核心区别

特性refreactive
适用类型任意类型(值类型或引用类型)仅对象或数组(引用类型)
创建方式ref(initialValue)reactive(object)
访问方式通过 .value 访问(如 count.value直接访问属性(如 state.count
响应式原理使用 Object.defineProperty() 或 Proxy仅使用 Proxy
深层响应式引用类型(对象 / 数组)自动深层响应式自动深层响应式
解构后响应性解构后失去响应性,需使用 toRefs 保留解构后仍保持响应性
性能基本类型(值类型)更高效对象 / 数组的操作更高效

4、原理差异

1. ref 的实现

  • 本质是一个对象,包含 value 属性:
    class RefImpl {constructor(value) {this._value = value; // 原始值this.value = value;  // 响应式值// 通过 getter/setter 拦截 value 的读写}
    }
    
  • 对于基本类型,使用 Object.defineProperty() 拦截 value 的读写。
  • 对于引用类型,内部调用 reactive() 转为深层响应式对象。

2. reactive 的实现

  • 使用 ES6 的 Proxy 拦截整个对象的属性读写:
    function reactive(target) {return new Proxy(target, {get(target, key) {// 依赖收集return target[key];},set(target, key, value) {// 触发更新target[key] = value;return true;}});
    }
    
  • 自动深层响应式:访问嵌套对象时,递归创建 Proxy。

5、常见误区

  1. ref 在模板中无需 .value
    Vue 3 模板会自动解包 ref,直接使用 {{ count }} 即可。

  2. reactive 不能替代 ref
    reactive 无法处理基本类型(如 number),必须使用 ref

  3. 解构 reactive 的注意事项
    解构后属性的响应性依赖于原始对象,若重新赋值会失去响应性。

二、计算属性(Computed)

计算属性是 Vue3 中用于处理复杂数据逻辑的重要特性,基于响应式依赖进行缓存,只有当依赖的值发生变化时才会重新计算。

1. 基本用法

计算属性通过 computed 来定义,接受一个 getter 函数或包含 getter/setter 的对象:

<template><div><p>原值: {{ firstName }} {{ lastName }}</p><p>计算属性值: {{ fullName }}</p><p>计算属性(缓存演示): {{ cachedComputed }}</p><p>普通函数: {{ getFullName() }}</p></div>
</template><script setup>
import { ref, computed } from 'vue';const firstName = ref('John');
const lastName = ref('Doe');
const age = ref(30);// 基础计算属性(getter 形式)
const fullName = computed(() => {return firstName.value + ' ' + lastName.value;
});// 带缓存的计算属性演示
const cachedComputed = computed(() => {console.log('计算属性被调用');return age.value > 18 ? '成年人' : '未成年人';
});// 普通函数(非响应式缓存)
const getFullName = () => {console.log('普通函数被调用');return firstName.value + ' ' + lastName.value;
};
</script>

2. 计算属性的 setter 用法

计算属性不仅可以读取,还可以通过对象形式定义 setter 实现双向绑定:

<template><div><input v-model="fullName" /><p>名: {{ firstName }}</p><p>姓: {{ lastName }}</p></div>
</template><script setup>
import { ref, computed } from 'vue';const firstName = ref('');
const lastName = ref('');// 带 setter 的计算属性
const fullName = computed({get() {return firstName.value + ' ' + lastName.value;},set(value) {const [first, last] = value.split(' ');firstName.value = first;lastName.value = last;}
});
</script>

3. 计算属性的特点

  • 响应式依赖:只在依赖的响应式数据变化时重新计算
  • 缓存机制:避免重复计算,提升性能
  • 简洁语法:相比函数调用更直观,适合处理模板中的复杂逻辑

三、Watch 的使用

Watch(监听器)用于监听数据变化,并在变化时执行回调函数,适用于需要在数据变化时触发副作用的场景。

1. 基础监听(watch 函数)

<template><div><input v-model="message" /><p>输入内容: {{ message }}</p></div>
</template><script setup>
import { ref, watch } from 'vue';const message = ref('');// 监听单个 ref 类型数据(注意ref对象在watch中不用加.value)
watch(message, (newValue, oldValue) => {console.log('值变化了:', newValue, '旧值:', oldValue);// 执行副作用操作
});// 监听多个数据(数组形式)
const count = ref(0);
watch([message, count], ( [newcount, newmessage], [oldcount, oldmessage]) => {console.log('message 或 count 变化了');
});
</script>

2. 深度监听(对象属性)

对于对象类型的数据,需要深度监听才能捕获属性变化:

<template><div><input v-model="user.name" /><input v-model="user.age" /></div>
</template><script setup>
import { ref, reactive, watch } from 'vue';const user = reactive({name: '张三',age: 18
});// 深度监听对象属性(方法一:开启 deep 选项)
watch(() => user,(newValue, oldValue) => {console.log('用户信息变化了');},{ deep: true }
);// 深度监听对象属性(方法二:直接监听属性)
watch(() => user.name,(newName) => {console.log('用户名变化了:', newName);}
);
</script>

注意当监听的是reactive对象时,watch的第一个参数 需要写为函数的形式。

当监听的是ref对象时,watch的第一个参数为普通的变量名的形式。

监听目标第一个参数写法是否需要 deep 选项
ref 变量count

ref 对象的单个属性(精确监听)() => user.value.age
reactive 对象的属性() => user.name
整个 reactive 对象() => user
多个数据源[count, () => user.age]部分情况需要
计算属性式的监听值() => obj.a * obj.b

3. 立即执行监听(immediate 选项)

<template><div><p>当前状态: {{ status }}</p></div>
</template><script setup>
import { ref, watch } from 'vue';const status = ref('init');// 组件挂载后立即执行一次监听回调
watch(status,(newStatus) => {console.log('状态变化:', newStatus);},{ immediate: true }
);
</script>

4. watchEffect 自动追踪依赖

watchEffect 会自动追踪回调函数中的响应式依赖,适用于需要立即执行且自动追踪依赖的场景:

<template><div><input v-model="count" /><p>计算结果: {{ result }}</p></div>
</template><script setup>
import { ref, watchEffect } from 'vue';const count = ref(0);
let result = 0;// 自动追踪 count 的变化
watchEffect(() => {result = count.value * 2;console.log('依赖变化,重新计算');
});
</script>

四、计算属性 vs Watch 的选择

场景计算属性Watch
数据转换✅(推荐)
异步操作✅(推荐)
副作用(如 API 调用)✅(推荐)
多依赖组合✅(推荐)
立即执行✅(通过 immediate 选项)

 五、生命周期API

阶段选项式 API组合式 API触发时机
创建阶段beforeCreate相当于setup实例创建之前,数据观测和事件配置尚未初始化。
created实例创建完成,数据观测和事件已配置,但 DOM 尚未生成。
挂载阶段beforeMountonBeforeMount模板编译完成,即将开始渲染 DOM 前。
mountedonMountedDOM 挂载完成后触发,可访问 DOM 元素。
更新阶段beforeUpdateonBeforeUpdate数据更新导致组件重新渲染前,此时 DOM 尚未更新。
updatedonUpdated组件更新完成,DOM 已同步更新后触发。
卸载阶段beforeUnmountonBeforeUnmount组件即将卸载前,可用于清理定时器、事件监听等资源。
unmountedonUnmounted组件已卸载,所有子组件也已卸载完成。
错误处理errorCapturedonErrorCaptured捕获到组件或子组件的错误时触发,可用于错误日志记录。
服务器渲染serverPrefetchonServerPrefetch(仅服务端渲染)组件在服务器端渲染前触发,用于数据预获取。

六、组合式API下的组件间数据传递

一、父传子:通过 props 传递数据

父组件通过 defineProps 声明并传递数据,子组件通过 defineProps 接收。

父组件

<!-- Parent.vue -->
<template><Child :message="parentMessage" :count="parentCount" />
</template><script setup>
import { ref } from 'vue';
import Child from './Child.vue';const parentMessage = ref('Hello from parent');
const parentCount = ref(100);
</script>

子组件

<!-- Child.vue -->
<template><div><p>Received message: {{ message }}</p><p>Received count: {{ count }}</p></div>
</template><script setup>
//import { defineProps } from 'vue'; 可以不引入// 定义 props 类型和默认值
const props = defineProps({message: String,//定义类型count: {type: Number,default: 0}
});// 使用 props
console.log(props.message); // "Hello from parent"
</script>

二、子传父:通过 emits 触发事件

子组件通过 defineEmits 声明事件,通过 emit 触发,父组件监听事件并处理。

子组件

<!-- Child.vue -->
<template><button @click="sendDataToParent">Send to Parent</button>
</template><script setup>
//import { defineEmits } from 'vue'; 可以不用引入// 定义可触发的事件
const emit = defineEmits(['updateData', 'customEvent']);// 触发事件并传递数据
const sendDataToParent = () => {emit('updateData', 'Data from child');emit('customEvent', { key: 'value' });
};
</script>

父组件

<!-- Parent.vue -->
<template><Child @updateData="handleUpdate" @customEvent="handleCustom" />
</template><script setup>
import Child from './Child.vue';// 处理子组件事件
const handleUpdate = (data) => {console.log('Received from child:', data); // "Data from child"
};const handleCustom = (payload) => {console.log('Custom event payload:', payload); // { key: 'value' }
};
</script>

三、双向绑定:v-model 语法糖

通过 v-model 实现父子组件的双向数据流动,子组件通过 update:propName 事件更新父组件数据。

父组件

<!-- Parent.vue -->
<template><!-- 默认 v-model --><Child v-model="parentValue" /><!-- 自定义 prop 和事件名 --><Child v-model:title="parentTitle" />
</template><script setup>
import { ref } from 'vue';
import Child from './Child.vue';const parentValue = ref('Initial value');
const parentTitle = ref('Initial title');
</script>

子组件

<!-- Child.vue -->
<template><input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /><input type="text" :value="title" @input="$emit('update:title', $event.target.value)" />
</template><script setup>
import { defineProps, defineEmits } from 'vue';// 默认 v-model 对应 modelValue prop
const props = defineProps({modelValue: String,title: String
});const emit = defineEmits(['update:modelValue', 'update:title']);
</script>

四、provide/inject:跨层级通信

父组件通过 provide 提供数据,任意层级的子组件通过 inject 获取数据,无需逐级传递。

祖先组件

<!-- Ancestor.vue -->
<template><ChildComponent />
</template><script setup>
import { provide, ref } from 'vue';
import ChildComponent from './ChildComponent.vue';// 提供响应式数据
const sharedData = ref('Shared data from ancestor');// 提供方法
const updateSharedData = (newValue) => {sharedData.value = newValue;
};// 注入 provide
provide('sharedData', sharedData);
provide('updateSharedData', updateSharedData);
</script>

任意层级子组件

<!-- AnyChild.vue -->
<template><div><p>Shared data: {{ sharedData }}</p><button @click="updateData">Update Shared Data</button></div>
</template><script setup>
import { inject } from 'vue';// 注入数据
const sharedData = inject('sharedData');
const updateSharedData = inject('updateSharedData');const updateData = () => {updateSharedData('New value from child');
};
</script>

如果想让子孙组件修改父组件中的参数,可以把方法写在父组件中,通过provide和inject把方法传递给子孙组件,子孙组件调用这个方法来修改参数 

五、使用 $parent 和 $children(不推荐)

通过 $parent 访问父组件实例,通过 $children 访问子组件实例。这种方式破坏了组件封装性,不推荐在大型项目中使用。

子组件

<!-- Child.vue -->
<script setup>
import { getCurrentInstance } from 'vue';const instance = getCurrentInstance();// 访问父组件数据或方法
const parentData = instance.parent.data;
instance.parent.someMethod();
</script>

六、使用事件总线或状态管理

对于复杂场景,可使用第三方库(如 Pinia、Vuex)或自定义事件总线实现组件通信。

总结

通信方式适用场景优点缺点
props父 → 子单向数据流简单直观,类型安全只能单向传递
emits子 → 父事件触发语义明确,便于调试多层级时需逐级传递
v-model双向数据绑定语法简洁,代码量少依赖特定事件名(update:propName)
provide/inject跨层级数据共享无需逐级传递,支持响应式依赖注入键,调试困难
\(parent/\)children直接访问组件实例(不推荐)快速获取实例破坏封装性,耦合度高

在实际开发中,推荐优先使用 props 和 emits 实现单向数据流,复杂场景使用 provide/inject 或状态管理库。

七、组合式API下的模板引用

模板引用允许在 JavaScript 中直接访问 DOM 元素或组件实例,通常用于

  • 手动操作 DOM(如聚焦、滚动)
  • 调用子组件的方法
  • 获取组件状态

一、组合式 API 中的模板引用

在组合式 API 中,模板引用通过 ref() 创建,并通过 v-bind 绑定到元素或组件上。

示例:获取 DOM 元素

<template><div><input ref="inputRef" type="text" /><button @click="focusInput">聚焦输入框</button></div>
</template><script setup>
import { ref, onMounted } from 'vue';// 创建模板引用
const inputRef = ref(null);// 访问 DOM 元素
const focusInput = () => {inputRef.value.focus(); // 调用 DOM 方法
};// 在 mounted 钩子中访问 DOM
onMounted(() => {console.log(inputRef.value); // <input type="text">
});
</script>

示例:获取组件实例

<template><ChildComponent ref="childRef" /><button @click="callChildMethod">调用子组件方法</button>
</template><script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';// 获取子组件实例
const childRef = ref(null);const callChildMethod = () => {childRef.value.someMethod(); // 调用子组件方法
};
</script>

二、使用 $refs(选项式 API 风格)

在组合式 API 中,也可以通过 getCurrentInstance 访问 $refs

<template><div ref="container">容器</div>
</template><script setup>
import { getCurrentInstance, onMounted } from 'vue';const instance = getCurrentInstance();onMounted(() => {console.log(instance.refs.container); // 访问 $refs
});
</script>

三、动态绑定引用

模板引用可以动态绑定到不同元素:

<template><div><button @click="changeRef">切换引用</button><div v-if="showA" ref="currentRef">元素 A</div><div v-else ref="currentRef">元素 B</div></div>
</template><script setup>
import { ref } from 'vue';const currentRef = ref(null);
const showA = ref(true);const changeRef = () => {showA.value = !showA.value;// 切换后,currentRef 会指向新的元素
};
</script>

四、组件引用与跨组件访问

在子组件中暴露方法供父组件调用:

<!-- ChildComponent.vue -->
<script setup>
//import { defineExpose } from 'vue'; 可以不引入const count = ref(0);const increment = () => {count.value++;
};// 暴露方法和属性给父组件
defineExpose({increment,count
});
</script>
<!-- ParentComponent.vue -->
<template><ChildComponent ref="childRef" /><button @click="childRef.value.increment()">调用子组件方法</button>
</template><script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';const childRef = ref(null);
</script>

五、注意事项

  1. 引用值的延迟
    模板引用在初始渲染时为 null,只有在组件挂载后才会指向实际元素。建议在 onMounted 或之后访问。

  2. 与响应式数据的区别
    模板引用不是响应式的,其值变化不会触发重新渲染。

  3. 组合式 API 与选项式 API 的区别

    • 组合式 API:通过 ref() 创建引用变量。
    • 选项式 API:通过 this.$refs 访问引用。
  4. 函数式组件的引用
    函数式组件需要显式接受 ref 参数并通过 forwardRef 转发。

总结

特性说明
创建引用使用 ref(null) 创建,初始值为 null
绑定到元素使用 ref="refName" 绑定到 DOM 元素或组件。
访问引用通过 refName.value 访问实际元素或组件实例。
组件暴露使用 defineExpose() 暴露子组件的方法和属性。
动态引用支持动态绑定到不同元素,值会自动更新。

相关文章:

  • 开源、免费、美观的 Vue 后台管理系统模板
  • 当简约美学融入小程序 UI 设计:开启高效交互新篇
  • 将vmware下旧的黑群晖nas迁移到别的服务器,并对硬盘进行扩容
  • 深度学习:PyTorch简介
  • Qt绘制温度计源码分享
  • Vulkan学习笔记1—环境搭建
  • jsoncpp ubuntu编译问题
  • 安卓9.0系统修改定制化____系列讲解导读篇
  • Pico Unity开发记录
  • 做好机房整改前的准备工作
  • 【开源解析】:Python打造专业级USB安全弹出工具(附完整源码)
  • 基于uniapp实现自定义日历页面、年份月份选择、动态日历渲染、日期标记及备忘录、无组件依赖、多端兼容
  • 构建高效开发节奏:我的IDEA休息提醒插件实践
  • uniapp请求接口封装
  • vue定义的组件在外部引入时的问题
  • Hadoop 2.7.7 单机伪分布式安装与配置教程(JDK 8)
  • LSTM-XGBoost回归预测,多输入单输出(Matlab完整源码和数据)
  • 洛谷B3612 【深进1.例1】求区间和
  • matlab脉冲信号并绘制波形2025.6.11
  • java每日精进 6.11【消息队列】
  • 上海品牌网站开发/天津网络优化推广公司
  • 新疆工程建设云平台/天津seo建站
  • 360网站推广电话/武汉网站制作推广
  • wordpress static page/网页优化seo广州
  • 有什么网站可以接活做设计标志/百度竞价推广流程
  • 网站代理怎么赚钱/北京网站优化策略