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. refcomputed 的本质

  • 它们都是 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. computedref 在模板里待遇一样,都自动拆

这样写就对了:

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

<script setup>
const name = computed(() => "ac" + "bc")
console.log(name.value) // 这里必须写 .value
</script>
相关推荐
最新资讯动态3 分钟前
想让鸿蒙应用快的“飞起”,来HarmonyOS开发者官网“最佳实践-性能专区”
前端
雾岛LYC听风10 分钟前
3. 轴指令(omron 机器自动化控制器)——>MC_GearInPos
前端·数据库·自动化
weixin_4435669810 分钟前
39-Ajax工作原理
前端·ajax
WebInfra18 分钟前
Rspack 1.3 发布:内存大幅优化,生态加速发展
前端·javascript·github
努力的搬砖人.20 分钟前
axios使用
vue.js
ak啊21 分钟前
Webpack 构建阶段:模块解析流程
前端·webpack·源码
学习OK呀31 分钟前
后端上手学习react基础知识
前端
星火飞码iFlyCode32 分钟前
大模型提效之服务端日常开发
前端
zoahxmy092933 分钟前
Canvas 实现单指拖动、双指拖动和双指缩放
前端·javascript
花花鱼33 分钟前
vue3 动态组件 实例的说明,及相关的代码的优化
前端·javascript·vue.js