ref 应用于对象类型的一个案例

一个案例:

html 复制代码
<template>
	
	<el-form :model="loginForm" :rules="rules"
	 ref="ruleForm"  class="login-container" label-position="left"
	 label-width="80px" v-loading="loading"  status-icon>
		<el-text class="mx-1" size="large" >系统登录</el-text>
		<div style="margin: 20px" />
		<el-form-item label="用户名" prop="userName">
		    <el-input v-model="loginForm.userName" placeholder="用户名"></el-input>
		</el-form-item>
		<el-form-item label="密码" prop="password">
		    <el-input  type="password" v-model="loginForm.password" placeholder="密码"></el-input>
		</el-form-item>
		<el-form-item>
		    <el-button type="primary" @click.native.prevent="submitClick">Login</el-button>
		</el-form-item>
	</el-form>	
</template>

<script setup>
import { ref } from "vue";
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { postReq } from '../utils/api'
import { ElMessageBox } from 'element-plus'
//导入用户仓库
import { useUserStore } from '../store/user.js'

//路由
const router = useRouter()
//获取用户仓库对象
const userStore=useUserStore()
const loginForm=ref({
	userName: 'admin',
	password: 'admin123'
})

const rules=ref({
	userName: [
		{ required: true, message: '请输入用户名', trigger: 'blur' },
		{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
	],
	password: [
		{ required: true, message: '请输入密码', trigger: 'blur' },
		{ min: 6, max: 16, message: '长度在 6 到 16 个字符', trigger: 'blur' }
	]
})


// 提交表单数据的函数
const submitClick=()=>{
	postReq("/user/login",loginForm.value).then(resp=>{
		if(resp.data.result){
			let u=resp.data.data;
			u.token=resp.data.token;
			u.isAuth=true;
			//console.log(u);
			userStore.setAuthenticated(u)
			//登录成功后跳转到指定页面
			router.push('/home')
			
		}else{
			ElMessageBox.alert(resp.data.errMsg, '提示',{})
		}
  	})
}

</script>

<style>
 .login-container {
    border-radius: 15px;
    background-clip: padding-box;
    margin: 180px auto;
    width: 350px;
    padding: 35px 35px 15px 35px;
    background: #fff;
    border: 1px solid #eaeaea;
    box-shadow: 0 0 25px #cac6c6;
  }

  .login_title {
    margin: 0px auto 40px auto;
    text-align: center;
    color: #505458;
  }

  .login_remember {
    margin: 0px 0px 35px 0px;
    text-align: left;
  }	
</style>

我们先看看,在 Vue3 中,ref()reactive()的 的区别。当 ref() 应用于对象时,其内部实现和响应式机制与直接使用 reactive() 有本质区别,但最终效果有相似之处。以下是详细解析:

1. ref()reactive() 的核心区别

  • ref() :创建一个响应式引用对象,内部通过 Object.definePropertyProxy 拦截 .value 的读写操作。

    • 无论初始值是基本类型(如 ref(0))还是对象(如 ref({})),ref 始终是一个包含 .value 属性的对象。
    • 当值为对象时,Vue 会自动将该对象转为 reactive 代理,但包裹层 仍是 ref
  • reactive() :直接创建一个深层响应式的对象代理,无需 .value 访问。

    • 只能用于对象或数组,不能用于基本类型。

2. ref() 应用于对象时的内部机制

当你使用 ref() 包裹对象时:

javascript

复制代码
const loginForm = ref({
  userName: 'admin',
  password: 'admin123'
});
  • 外层结构loginForm 是一个 ref 对象,具有 .value 属性。
  • 内层值loginForm.value 是一个通过 reactive() 创建的响应式代理对象。
  • 响应式原理
    1. 访问 loginForm.value.userName 时,先触发 ref.valuegetter,再触发内部 reactive 对象的 userNamegetter
    2. 修改 loginForm.value.password = 'newPassword' 时,触发内部 reactive 对象的 setter,通知依赖更新。

关键区别ref 是一个 "容器",而 reactive 是容器中的 "内容"。

3. 与直接使用 reactive() 的对比

javascript

复制代码
// 方式1:使用 ref() 包裹对象
const loginFormRef = ref({
  userName: 'admin',
  password: 'admin123'
});

// 方式2:直接使用 reactive()
const loginFormReactive = reactive({
  userName: 'admin',
  password: 'admin123'
});
  • 访问方式

    • loginFormRef.value.userName(需通过 .value)。
    • loginFormReactive.userName(直接访问)。
  • 解构后响应性

    • const { userName } = loginFormRef.valueuserName 是普通变量,无响应性。
    • const { userName } = loginFormReactiveuserName 失去响应性(需用 toRefs() 保持)。
  • 类型转换

    • ref 对象可通过 toRefs(loginFormRef.value) 转为响应式引用。
    • reactive 对象可通过 toRef(loginFormReactive, 'userName') 转为单个属性的响应式引用。

4. 应用场景选择

  • 使用 ref() 包裹对象

    • 当需要在组合式函数中返回对象,且保持统一的 .value 访问模式。
    • 当需要在模板中通过 v-model 绑定整个对象(如表单组件)。
  • 直接使用 reactive()

    • 当不需要 .value 语法,希望代码更简洁。
    • 当对象结构复杂,需要深层响应式,且无需解构。

5. 小结

  • ref() 包裹对象时 :外层是 ref 容器,内层是 reactive 代理,本质是 ref + reactive 的组合
  • 直接使用 reactive():对象本身就是响应式代理,无需额外容器。

我们在模版中 这样使用的:

你们可以看到 我们没有用前文提到的 loginFormRef.valueuserName 去访问啊,错了吗?

没错哟~~~

  1. 模板中v-model="loginForm.userName" 正确,因为 Vue 自动解包 loginFormloginForm.value
  2. JavaScript 中postReq("/user/login", loginForm.value) 正确,因为必须显式访问 .value

如果使用 reactive,则代码需要改为:

复制代码
// 使用 reactive
const loginForm = reactive({
  userName: 'admin',
  password: 'admin123'
});

// 模板中仍然直接访问
<el-input v-model="loginForm.userName">

// JavaScript 中直接使用
postReq("/user/login", loginForm); // 无需 .value

我们项目中主要应用的是ref,大部分项目也是这样的,原因是:

  1. 统一访问模式 :所有状态都用 .value,降低复杂度。
  2. 类型安全:TypeScript 推导更直观,减少类型错误。
  3. 组合式适配:更符合 Vue3 的函数式设计理念。组合函数,后面有空再讲。
相关推荐
拾光拾趣录12 分钟前
for..in 和 Object.keys 的区别:从“遍历对象属性的坑”说起
前端·javascript
OpenTiny社区22 分钟前
把 SearchBox 塞进项目,搜索转化率怒涨 400%?
前端·vue.js·github
编程猪猪侠1 小时前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞1 小时前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到112 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构