Vue3 H5 开发碎碎念:reactive 真香!getCurrentInstance 我劝你慎行

最近扎进 Vue3 H5 项目的 "坑" 里快两周了,接手的项目(原维护人提桶跑路了),从一开始对着ref和reactive纠结半天,到现在写组件时下意识用reactive封装状态;从看到项目里getCurrentInstance能 "万能访问" 时的兴奋,到踩完坑后乖乖回归props和emit------ 这波开发体验,简直像坐了趟过山车,满肚子感慨不吐不快!

先唠唠:为啥我觉得 reactive 比 ref "香"?

刚开始学 Vue3 时,我总搞不懂:明明ref能处理所有类型数据,为啥还要有reactive?直到写 H5 页面时要处理一堆表单数据,才明白reactive的 "爽点"------ 它就像个 "收纳盒",能把零散的状态归拢到一起,不用到处写.value,代码清爽到飞起!

场景 1:处理表单数据,reactive 赢麻了

比如做一个用户注册的 H5 表单,要存用户名、手机号、密码,还有协议勾选状态。用ref写是这样的:

xml 复制代码
<template>
  <div class="register-form">
    <input v-model="username" placeholder="请输入用户名" />
    <input v-model="phone" placeholder="请输入手机号" type="tel" />
    <input v-model="password" placeholder="请输入密码" type="password" />
    <label>
      <input v-model="isAgree" type="checkbox" /> 同意用户协议
    </label>
    <button @click="handleSubmit">注册</button>
  </div>
</template>
<script setup>
import { ref } from 'vue'
// 一堆ref,每个都要单独声明,用的时候还得加.value
const username = ref('')
const phone = ref('')
const password = ref('')
const isAgree = ref(false)
const handleSubmit = () => {
  // 提交时要一个个取.value,手都敲酸了
  const formData = {
    username: username.value,
    phone: phone.value,
    password: password.value,
    isAgree: isAgree.value
  }
  console.log('提交数据:', formData)
}
</script>

看着满屏的ref和.value,我总担心自己漏写一个,而且后期要加个 "确认密码" 字段,又得多声明一个ref,太麻烦了!

直到换成reactive,代码瞬间 "呼吸顺畅" 了:

xml 复制代码
<template>
  <div class="register-form">
    <!-- 模板里直接用"对象.属性",不用变 -->
    <input v-model="form.username" placeholder="请输入用户名" />
    <input v-model="form.phone" placeholder="请输入手机号" type="tel" />
    <input v-model="form.password" placeholder="请输入密码" type="password" />
    <label>
      <input v-model="form.isAgree" type="checkbox" /> 同意用户协议
    </label>
    <button @click="handleSubmit">注册</button>
  </div>
</template>
<script setup>
import { reactive } from 'vue'
// 一个reactive对象搞定所有表单状态,归类清晰
const form = reactive({
  username: '',
  phone: '',
  password: '',
  isAgree: false
})
const handleSubmit = () => {
  // 直接传form就行,不用再一个个.value!
  console.log('提交数据:', form)
  // 后期加"确认密码"?直接在form里加个属性,一步到位
  // form.confirmPassword: ''
}
</script>

你看!状态全装在form这个 "收纳盒" 里,声明时不用重复写ref,用的时候不用记.value,后期扩展字段也只需要改form对象 ------ 这种 "一站式管理" 的快乐,谁用谁知道!

场景 2:处理复杂状态,reactive 更 "聪明"

还有做类似商品详情页,要管理 "商品信息""规格选择""加入购物车状态" 这三类数据。用reactive可以直接按模块拆分,逻辑比ref零散声明清晰 10 倍:

php 复制代码
// 用reactive按模块组织状态,一目了然
const goodsState = reactive({
  info: { name: 'iPhone 16', price: 5999, stock: 100 }, // 商品基础信息
  selectedSpec: { color: '黑色', storage: '256G' },     // 选中的规格
  cart: { isAdding: false, count: 1 }                  // 购物车相关状态
})
// 要修改"是否正在加入购物车",直接点到底,不用记多个变量
goodsState.cart.isAdding = true

如果用ref,得声明goodsInfo、selectedSpec、cartIsAdding、cartCount四个变量,后期维护时找个状态都得翻半天 ------ 对比下来,reactive的 "聚合能力" 简直是为复杂状态量身定做的!

再吐个槽:getCurrentInstance 看着香,用着 "坑"

刚熟悉项目时,看到getCurrentInstance能在< script setup >里拿到ctx、proxy,甚至直接访问父组件的方法时,我当场直呼 "神器"!想着以后不用写props传值、不用emit触发事件,直接 "跨组件调用" 多方便 ------ 结果没爽两天,就踩了一串坑。

坑 1:依赖 "实例上下文",调试时一脸懵

比如我写了个 "地址选择" 子组件,想直接调用父组件的setAddress方法,用getCurrentInstance是这样写的:

xml 复制代码
<!-- 子组件:AddressSelect.vue -->
<script setup>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
const handleSelect = (address) => {
  // 直接通过实例调用父组件方法
  instance.parent.proxy.setAddress(address)
}
</script>

刚开始运行没问题,可后来我把父组件的setAddress改名为updateAddress,忘了改子组件的代码 ------ 结果页面点了没反应,控制台还不报错!查了半天才发现是子组件调用的方法名不对。

如果用emit,父组件传方法时会显式声明,子组件调用错了会有警告,根本不会出现这种 "隐式依赖" 的坑:

xml 复制代码
<!-- 子组件:AddressSelect.vue -->
<script setup>
const emit = defineEmits(['selectAddress'])
const handleSelect = (address) => {
  // 显式触发事件,父组件没监听的话会有警告
  emit('selectAddress', address)
}
</script>
<!-- 父组件:Profile.vue -->
<template>
  <!-- 显式绑定事件,方法名改了会直接报错 -->
  <AddressSelect @selectAddress="updateAddress" />
</template>
<script setup>
const updateAddress = (address) => {
  console.log('更新地址:', address)
}
</script>

对比下来,emit的 "显式通信" 就像 "明码标价",父组件传什么、子组件用什么,一眼看明白;而getCurrentInstance的 "隐式调用" 像 "暗箱操作",出了问题都不知道在哪查。

坑 2:跨层级调用时,"链" 断了就崩

还有,我做了个三级组件嵌套:Parent -> Child -> GrandChild,想在GrandChild里调用Parent的getData方法,用getCurrentInstance写了个 "链式调用":

ini 复制代码
// GrandChild.vue 里的代码
const instance = getCurrentInstance()
// 父组件是Child,爷爷组件是Parent,所以要写两层.parent
const parentInstance = instance.parent.parent
const getData = () => {
  parentInstance.proxy.getData()
}

结果后来我调整了组件结构,在Child和GrandChild之间加了个Middle组件 ------ 这下instance.parent.parent指向的变成了Child,调用getData直接报错 "方法不存在"!

如果用props和emit,组件通信是 "逐层传递" 的,结构变了只需要调整中间组件的传值,不会出现这种 "层级依赖" 的问题:

xml 复制代码
<!-- Parent.vue 传方法给Child -->
<Child @fetchData="getData" />
<!-- Child.vue 接收并传给GrandChild -->
<template>
  <GrandChild @fetchData="$emit('fetchData')" />
</template>
<!-- GrandChild.vue 直接触发 -->
<script setup>
const emit = defineEmits(['fetchData'])
const handleClick = () => emit('fetchData')
</script>

虽然多写了一次emit,但胜在稳定 ------ 组件结构再怎么变,只要按 "props 传值、emit 触发" 的规则来,就不会出现 "链式调用断裂" 的情况。

官方都劝退:getCurrentInstance 不是 "常规操作"

后来翻 Vue3 官方文档才发现,人家早就说了:getCurrentInstance主要用于开发插件或工具库,不推荐在常规业务组件中使用!因为它会破坏组件的 "封装性"------ 子组件直接依赖父组件的实现细节,一旦父组件改了点东西,子组件就可能崩掉,完全违背了 "组件解耦" 的设计理念。

反观props和emit,它们是 Vue 官方推荐的 "组件通信规范":props负责 "父传子",明确子组件需要什么数据;emit负责 "子传父",明确子组件会触发什么事件 ------ 这种 "约定式通信" 让组件之间像 "邻里相处" 一样,边界清晰,互不打扰,后期维护时也不用 "猜来猜去"。

最后总结:适合自己的,才是最好的

写了这么多,不是说ref不好用、getCurrentInstance不能用 ------ 比如处理单个基础类型数据(像const count = ref(0)),ref比reactive更简洁;开发组件库时需要访问实例内部方法,getCurrentInstance也确实有用。

但在日常 H5 开发中,面对的大多是 "表单状态""页面模块状态" 这类聚合型数据,reactive的 "收纳能力" 能让代码更整洁;而组件通信时,props和emit的 "显式约定" 能避免很多隐性 bug------ 这也是我为啥越用越觉得 "前者真香,后者慎行" 的原因。

毕竟写代码就像过日子,不是越 "花哨" 越好,而是越 "顺手、稳定" 越舒服。不知道你们在 Vue3 开发中,有没有过类似的 "真香" 或 "踩坑" 经历?欢迎评论区唠一唠~

相关推荐
崔庆才丨静觅11 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅12 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅13 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊13 小时前
jwt介绍
前端
爱敲代码的小鱼13 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax