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 的函数式设计理念。组合函数,后面有空再讲。
相关推荐
小满zs3 小时前
Zustand 第五章(订阅)
前端·react.js
涵信4 小时前
第一节 基础核心概念-TypeScript与JavaScript的核心区别
前端·javascript·typescript
谢尔登4 小时前
【React】常用的状态管理库比对
前端·spring·react.js
编程乐学(Arfan开发工程师)4 小时前
56、原生组件注入-原生注解与Spring方式注入
java·前端·后端·spring·tensorflow·bug·lua
小公主5 小时前
JavaScript 柯里化完全指南:闭包 + 手写 curry,一步步拆解原理
前端·javascript
姑苏洛言6 小时前
如何解决答题小程序大小超过2M的问题
前端
TGB-Earnest7 小时前
【leetcode-合并两个有序链表】
javascript·leetcode·链表
GISer_Jing7 小时前
JWT授权token前端存储策略
前端·javascript·面试
开开心心就好7 小时前
电脑扩展屏幕工具
java·开发语言·前端·电脑·php·excel·batch
拉不动的猪7 小时前
es6常见数组、对象中的整合与拆解
前端·javascript·面试