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 开发中,有没有过类似的 "真香" 或 "踩坑" 经历?欢迎评论区唠一唠~

相关推荐
开源框架3 小时前
建设银行模拟器,最新逆向教程演示,附文件哈!
前端
90后的晨仔3 小时前
Vue3 组件完全指南:从零开始构建可复用UI
前端·vue.js
布列瑟农的星空3 小时前
CSS5中的级联层@layer
前端·css
Bella_a3 小时前
挑战100道前端面试题--Vue2和Vue3响应式原理的核心区别
vue.js
薄雾晚晴3 小时前
大屏开发实战:用 autofit.js 实现 1920*1080 设计稿完美自适应,告别分辨率变形
前端·javascript·vue.js
yannick_liu3 小时前
vue项目打包后,自动部署到服务器上面
前端
布列瑟农的星空3 小时前
升级一时爽,降级火葬场——tailwind4降级指北
前端·css
谁黑皮谁肘击谁在连累直升机3 小时前
for循环的了解与应用
前端·后端
不系舟同学3 小时前
Three.js + CSS3DSprite 首帧精灵图模糊问题排查、解决
前端