ref 应用于对象类型的一个案例
一个案例:
<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.defineProperty
或Proxy
拦截.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()
创建的响应式代理对象。 - 响应式原理:
- 访问
loginForm.value.userName
时,先触发ref
的.value
的getter
,再触发内部reactive
对象的userName
的getter
。 - 修改
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.value
:userName
是普通变量,无响应性。const { userName } = loginFormReactive
:userName
失去响应性(需用toRefs()
保持)。
-
类型转换:
ref
对象可通过toRefs(loginFormRef.value)
转为响应式引用。reactive
对象可通过toRef(loginFormReactive, 'userName')
转为单个属性的响应式引用。
4. 应用场景选择
-
使用
ref()
包裹对象:- 当需要在组合式函数中返回对象,且保持统一的
.value
访问模式。 - 当需要在模板中通过
v-model
绑定整个对象(如表单组件)。
- 当需要在组合式函数中返回对象,且保持统一的
-
直接使用
reactive()
:- 当不需要
.value
语法,希望代码更简洁。 - 当对象结构复杂,需要深层响应式,且无需解构。
- 当不需要
5. 小结
ref()
包裹对象时:外层是ref
容器,内层是reactive
代理,本质是ref
+reactive
的组合。- 直接使用
reactive()
时:对象本身就是响应式代理,无需额外容器。
我们在模版中 这样使用的:
你们可以看到 我们没有用前文提到的 loginFormRef.value
:userName
去访问啊,错了吗?
没错哟~~~
- 模板中:
v-model="loginForm.userName"
正确,因为 Vue 自动解包loginForm
为loginForm.value
。 - 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,大部分项目也是这样的,原因是:
- 统一访问模式:所有状态都用
.value
,降低复杂度。 - 类型安全:TypeScript 推导更直观,减少类型错误。
- 组合式适配:更符合 Vue3 的函数式设计理念。组合函数,后面有空再讲。