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 的函数式设计理念。组合函数,后面有空再讲。
相关推荐
人工智能训练师11 分钟前
在Ubuntu中如何使用PM2来运行一个编译好的Vue项目
linux·运维·服务器·vue.js·ubuntu·容器
六月的可乐12 分钟前
【干货推荐】AI助理前端UI组件-悬浮球组件
前端·人工智能·ui
呼啦啦呼_18 分钟前
Echarts自定义地图显示区域,显示街道学校等区域,对原有区域拆分
前端
浩星27 分钟前
iframe引入界面有el-date-picker日期框,点击出现闪退问题处理
前端·vue.js·elementui
技术钱29 分钟前
element plus 多个form校验
前端
yume_sibai38 分钟前
HTML HTML基础(3)
前端·html
米花丶1 小时前
JSBridge安全通信:iOS/Android桥对象差异与最佳实践
前端·webview
唐•苏凯1 小时前
ArcGIS Pro 遇到严重的应用程序错误而无法启动
开发语言·javascript·ecmascript
萌萌哒草头将军2 小时前
🚀🚀🚀 Oxc 恶意扩展警告;Rolldown 放弃 CJS 支持;Vite 发布两个漏洞补丁版本;Rslib v0.13 支持 ts-go
前端·javascript·vue.js
接着奏乐接着舞。2 小时前
3D地球可视化教程 - 第1篇:基础地球渲染系统
前端·javascript·vue.js·3d·three.js