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

Published 2025-03-27 23:29 1062 words 6 min read

做产品前,先别急着写代码:我是怎么判断一个点子值不值得做的想偷卷?但微信不支持md文档?这个软件助你!新手小白如何开发练手项目?前端常见安全问题 + 防御方法 + 面试回答吉比特(雷霆游戏)前端二面问题总结吉比特27前端实习面试纷享销客前端实习一面08-刚体约束 Constraint07-监听碰撞事件和获取碰撞信息06 - 物体休眠与休眠事件详解(Cannon.js)05-碰撞与碰撞组04 - 弹性与接触材质详解03 - Cannon 材质与摩擦系数设置02 - Cannon 引擎基础碰撞讲解01 - Cannon.js 引擎的用途和基本使用一款减轻前端图片命名工作量的图片转换器总结:一个双非大学生的两年,是怎么过来的?数学公式Input-输入框组件实现详解用HTML5实现实时ASCII艺术摄像头object-fit: contain - CSS 属性详解GSAP (GreenSock Animation Platform) 动画库学习指南Webview通信系统学习指南实现手势操控3D粒子动画?太好玩了!东田数码科技前端面试前端性能优化面试回答技巧(一)React与Vue表单的对比差异融山科技前端面经·React的入门学习(一)九方前端面试JavaScript 核心:数组/字符串 API 精要指南JavaScript的常用数组API原理解析前端练习小网站-新手突破之路前端の骚操作代码合集 (二)| 让你的网页变得有趣前端Threejs入门(五)Vue 3 响应式原理:computed的模板解包机制Vue 监听器 watch 深度解析Axios 深入解析从0到1配置vue项目跟git仓库连接!前端Three.js入门(四)你懂keep-alive吗?见过Vue的小区保安吗?前端Three.js入门(三)前端Three.js入门(二)前端Three.js入门(一)-Three的第一个简单的页面 Git 大冒险:解锁代码管理的秘密武器如何将网站加载速度提升50%?前端性能优化全攻略前端の骚操作代码合集 | 让你的网页充满恶趣味进击蓝桥杯!2025web前端HTML5uniapp-页面生命周期
This post is not yet available in English. Showing the original.
Vue 3 响应式原理:computed的模板解包机制 作者: 前端AC | 原文: https://juejin.cn/post/7486414913055162378 前言 vue中的计算属性相关知识: 核心概念对比 | 特性 | 脚本环境(JS) | 模板环境...

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

作者: 前端AC | 原文: https://juejin.cn/post/7486414913055162378

前言

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

核心概念对比

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

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

<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>

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

image-20250327204715671.png
image-20250327204715671.png
const firstName = ref("ac");   
// 创建响应式引用,value = "ac"
const lastName = ref("bc");    
// 创建响应式引用,value = "bc"

控制台首次输出

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 对象结构

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

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

    • 遇到 {{ variable }} 时:
      function renderValue(target) {
        return isRef(target) ? target.value : target
      }

控制台输出详解

1. console.log(firstName)

输出示例:

RefImpl {
  __v_isRef: true,
  _value: "ac",
  value: "ac"  // 通过 getter 访问
}

2. console.log(name)

输出示例:

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

3. console.log(name.value)

输出结果:

"acbc"  // 直接获取计算值

正确用法总结

脚本区域(<script setup>

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

模板区域(<template>

<!-- 直接使用变量名 -->
<div>{{ name }}</div>  

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

常见误区解释

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

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

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

    <!-- 调试 computed 对象 -->
    <pre>{{ { computedObject: name } }}</pre>

说人话:

1. refcomputed 的本质

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

2. 你的代码问题

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

相当于:

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

3. 正确姿势对比

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

4. 通俗比喻

  • JS 环境:就像你在厨房做菜(需要自己动手开冰箱拿食材)
    // 必须自己开盒子!
    const food = fridge.value 
  • 模板环境:就像你点外卖(商家自动帮你打包好)
    <!-- 直接吃现成的 -->
    <div>{{ food }}</div>  

5. 记住三句话

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

这样写就对了:

<template>
  <div>{{ name }}</div>  <!-- 显示 "acbc" -->
</template>

<script setup>
const name = computed(() => "ac" + "bc")
console.log(name.value) // 这里必须写 .value
</script>