一个案例:
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.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 的函数式设计理念。组合函数,后面有空再讲。