Vue 3 响应式原理:computed的模板解包机制

前言

vue中的计算属性相关知识:

核心概念对比

特性 脚本环境(JS) 模板环境(Template)
ref 访问 .value 必需 自动解包(无需 .value)
computed 访问 .value 必需 自动解包(无需 .value)
原始对象访问 直接访问 直接访问

二话不说先上代码,大家觉得会输出什么呢?

JS 复制代码
<template>
  <div>{{ firstName }}+{{lastName}}</div>
  <div>{{ name }}</div>
  <div>{{ name.value }}</div>
</template>

<script setup>
import { ref, computed } from "vue";
const firstName = ref("ac");
const lastName = ref("bc");

console.log(firstName);

const name = computed(() => {
  return firstName.value + lastName.value;
});
console.log(name);
console.log(name.value);
</script>

<style>
</style>

结果如下所示,大家可能有点疑惑?接下来就看下面知识点:

js 复制代码
const firstName = ref("ac");   
// 创建响应式引用,value = "ac"
const lastName = ref("bc");    
// 创建响应式引用,value = "bc"

控制台首次输出

javascript 复制代码
console.log(firstName);  // 输出 RefImpl 对象
console.log(name);       // 输出 ComputedRefImpl 对象
console.log(name.value); // 输出计算值 "acbc"

模板渲染机制

模板编译结果对比

模板写法 实际渲染值 原理说明
{{ firstName }} "ac" 自动解包 ref,等价于 firstName.value
{{ name }} "acbc" 自动解包 computed,等价于 name.value
{{ name.value }} 空/undefined 错误用法,因为 name 解包后得到字符串,字符串没有 .value 属性

为什么表现不同?

  1. Ref 对象结构:

    typescript 复制代码
    interface Ref<T> {
      value: T
      __v_isRef: true
      // ...其他内部属性
    }
  2. Computed 对象结构:

    typescript 复制代码
    interface ComputedRef<T> extends Ref<T> {
      readonly effect: ReactiveEffect<T>
      // ...其他计算属性特有属性
    }
  3. 模板自动解包规则:

    • 遇到 {{ variable }} 时:

      javascript 复制代码
      function renderValue(target) {
        return isRef(target) ? target.value : target
      }

控制台输出详解

1. console.log(firstName)

输出示例:

javascript 复制代码
RefImpl {
  __v_isRef: true,
  _value: "ac",
  value: "ac"  // 通过 getter 访问
}

2. console.log(name)

输出示例:

javascript 复制代码
ComputedRefImpl {
  __v_isRef: true,
  _dirty: false,
  _value: "acbc",  // 缓存的计算结果
  effect: ReactiveEffect {...}
}

3. console.log(name.value)

输出结果:

arduino 复制代码
"acbc"  // 直接获取计算值

正确用法总结

脚本区域(<script setup>)

javascript 复制代码
// 必须使用 .value 访问
const fullName = firstName.value + lastName.value
console.log(name.value) // 正确

模板区域(<template>)

vue 复制代码
<!-- 直接使用变量名 -->
<div>{{ name }}</div>  

<!-- 等价于 -->
<div>{{ name.value }}</div>  <!-- 错误! -->

常见误区解释

  1. 为什么模板不能写 .value?

    • Vue 的模板编译器已经自动处理解包
    • 写 name.value 相当于尝试访问字符串的 .value 属性
  2. computed 和普通 ref 的区别:

    特性 常规 ref computed
    值获取 直接赋值 通过计算函数获取
    依赖追踪 无 自动追踪
    模板使用 自动解包 自动解包
  3. 调试建议:

    vue 复制代码
    <!-- 调试 computed 对象 -->
    <pre>{{ { computedObject: name } }}</pre>

说人话:

1. ref 和 computed 的本质

  • 它们都是 Vue 的**"魔法盒子"**,里面装着值(比如字符串、数字等)
  • 但你要从盒子里拿值,在 JS 里 必须用 .value 打开盒子
  • 在模板里,Vue 会自动帮你打开盒子,你直接拿就行

2. 你的代码问题

html 复制代码
<div>{{ name.value }}</div>  <!-- 错误! -->

相当于:

  1. Vue 先自动打开 name 盒子 → 得到字符串 "acbc"
  2. 然后你让 Vue 去拿 "acbc".value → 字符串哪有 .value 啊!(报错)

3. 正确姿势对比

场景 JS 代码里 模板里 原因
普通 ref firstName.value {{ firstName }} 模板自动拆盒子
computed name.value {{ name }} 模板自动拆盒子
原始值 直接 "ac" {{ "ac" }} 本来就不是盒子

4. 通俗比喻

  • JS 环境 :就像你在厨房做菜(需要自己动手开冰箱拿食材)

    js 复制代码
    // 必须自己开盒子!
    const food = fridge.value 
  • 模板环境 :就像你点外卖(商家自动帮你打包好)

    html 复制代码
    <!-- 直接吃现成的 -->
    <div>{{ food }}</div>  

5. 记住三句话

  1. 在 <script setup> 里:必须写 .value(手动开盒)
  2. 在 <template> 里:永远不要写 .value(自动拆盒)
  3. computed 和 ref 在模板里待遇一样,都自动拆

这样写就对了:

vue 复制代码
<template>
  <div>{{ name }}</div>  <!-- 显示 "acbc" -->
</template>

<script setup>
const name = computed(() => "ac" + "bc")
console.log(name.value) // 这里必须写 .value
</script>
相关推荐
老徐聊GEO1 分钟前
把“安徽“写进题面之后:多平台AI推荐名单的区域扰动测量(2026)
前端·人工智能·算法·geo优化
小冯不疯6 分钟前
688 / 拼多多 / 淘特三类供货链路:从采购单到应付凭证的 4 段对账
javascript
moshuying12 分钟前
DeppInfer系列之 - 矩阵、注意力与 GPU:拆解 DeepGEMM 与 FlashMLA
前端·javascript·面试
燕落南枝35 分钟前
CSS 分组选择器与链接伪类选择器详解
前端·css
其实防守也摸鱼1 小时前
网络安全法竞赛知识总结与题库
前端·网络·人工智能·安全·web安全·重构·nc
山雀~1 小时前
浏览器兼容方案(Web 项目的三层实践)
前端
Jack N1 小时前
【Strapi 系列之五】前端怎么接?Next.js + Tailwind 拉取 Strapi 数据(含代码)
开发语言·前端·javascript·strapi
__sjfzllv___1 小时前
在职前端Leader学习/转行 AI Agent -DAY85
前端
来一首_We_go1 小时前
React Flow Pro 高级交互实现
前端·react.js
weedsfly1 小时前
React 不可变数据:为什么页面“明明数据变了却不更新
前端·javascript·react.js