vue3中ref响应式变量为什么script中要用.value,而template模板中不需?

手把手教你调试,弄懂vue3中ref响应式变量为什么script中要用.value,而template模板中不需?

1.弄个简单的vue项目

RefTemplate.vue文件

html 复制代码
<template>
  <h1>Hello {{ msg }}!</h1>
</template>

<script setup lang="ts">
  import { ref } from 'vue';
  const msg = ref<string>('world');
  console.log(msg.value);
</script>

main.ts文件

ts 复制代码
import RefTemplate from './RefTemplate.vue';
import { createApp } from 'vue';

createApp(RefTemplate).mount('#app');

2.配置vscode调试

  • .vscode文件夹下添加launch.json,配置调试环境和端口
json 复制代码
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "chrome",
      "request": "launch",
      "name": "Launch Chrome against localhost",
      "url": "http://localhost:8086",
      "webRoot": "${workspaceFolder}"
    }
  ]
}

注意 :项目运行的端口要与launch.json配置的端口一致。

  • 直接命令窗口npm run dev启动项目,然后点击调试按钮,会打开一个新的chrome浏览器窗口用于调试
  • 先不打点,直接加载界面,可以在调试栏的LOAD SCRIPTS中找到编译后文件和源文件

3.读取代码,探究原理

  • 打开编译后的RefTemplate.vue代码

  • 可以看到script setup标签会编译成一个_defineComponent

js 复制代码
const _sfc_main = /* @__PURE__ */ _defineComponent({
  __name: "RefTemplate",
  setup(__props, { expose: __expose }) {
    __expose();
    const msg = ref("world");
    console.log(msg.value);
    const __returned__ = { msg };
    Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });
    return __returned__;
  }
});

所有的函数和响应式变量都会从setup函数中return出来。

  • template编译成render函数
js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  return _openBlock(), _createElementBlock(
    "h1",
    null,
    "Hello " + _toDisplayString($setup.msg) + "!",
    1
    /* TEXT */
  );
}

render函数传入$setup参数,创建虚拟DOM的显示文本运用到响应式变量时直接通过$setup.msg使用,不需要.value

  • 查看vue.jstoDisplayString函数,
js 复制代码
var isRef = (val) => {
  return !!(val && val['__v_isRef'] === true);
};
var toDisplayString = (val) => {
  return isString(val)
    ? val
    : val == null
    ? ''
    : isArray(val) ||
      (isObject(val) && (val.toString === objectToString || !isFunction(val.toString)))
    ? isRef(val)
      ? toDisplayString(val.value)
      : JSON.stringify(val, replacer, 2)
    : String(val);
};

通过toDisplayString读取响应式变量的值,利用isRef判断是否是ref,如果是ref的变量,则会读取其.value的值,转化成字符串文本。

4.调试代码,搞清执行流程

  • toDisplayString打点,刷新一下浏览器,然后右击菜单,Run to Line执行到此行。

  • 可以从调试栏的CALL STACK回调栈中看到执行过程中的所有函数。

  • 然后可以点击每个函数,分别打点一步步执行
  1. mountComponent函数中,createComponentInstance创建初始化组件实例,执行setupComponent,给instance实例对象挂载对应Component组件的响应式变量或函数与render函数
js 复制代码
const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, namespace, optimized) => {
    const instance = (initialVNode.component = createComponentInstance(
      initialVNode,
      parentComponent,
      parentSuspense
    ));
    //...
    setupComponent(instance, false, optimized);
    //...
    setupRenderEffect( instance, initialVNode, container, anchor,  parentSuspense, namespace, optimized );
    //...
 }
  • 1) 在setupStatefulComponent函数中执行Component组件的setup函数return的响应式变量和函数成setupResult,在handleSetupResult函数中给instance.setupState赋予添加响应式的setupResult
js 复制代码
function setupComponent(instance, isSSR = false, optimized = false) {
 //...
  const setupResult = isStateful ? setupStatefulComponent(instance, isSSR) : void 0;
    //...
}
function setupStatefulComponent(instance, isSSR) {
 //...
const { setup } = Component;
  if (setup) {
  //...
    const setupResult = callWithErrorHandling( setup, instance, 0, [ !!(process.env.NODE_ENV !== "production") ? shallowReadonly(instance.props) :  instance.props, setupContext ]
    );
  //...       
 handleSetupResult(instance, setupResult, isSSR);
//...
}
function handleSetupResult(instance, setupResult, isSSR) {
 //...
 instance.setupState = proxyRefs(setupResult);
  //...
  finishComponentSetup(instance, isSSR);
}
  • 2) 在finishComponentSetup函数中给instance.render赋予Component组件的render函数
js 复制代码
function finishComponentSetup(instance, isSSR, skipOptions) {
 //...
instance.render = Component.render || NOOP;
 //...
}
  1. mountComponent函数中,执行setupRenderEffect渲染副作用。
  • 1)setupRenderEffect函数中,给instance组件实例添加副作用函数并执行,用于监测响应式变量改变时渲染。
js 复制代码
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, namespace, optimized) => {
 const componentUpdateFn = () => {
    //...
    const subTree = (instance.subTree = renderComponentRoot(instance));
    //...
    patch(null, subTree, container, anchor, instance, parentSuspense, namespace);
    //...
  };
  //...
  const effect = instance.effect = new ReactiveEffect(componentUpdateFn); 
  //...
    const update = instance.update = effect.run.bind(effect);
 //...
update();
}
  • 2)在componentUpdateFn副作用函数中,利用renderComponentRoot渲染虚拟DOM,并通过patch挂载到页面成真实DOM。
  • 3)在renderComponentRoot函数中,将instance.setupState传入instance.render组件模板渲染函数中执行。
js 复制代码
function renderComponentRoot(instance) {
  const {
    type: Component,
    //...
    render,
    //...
    setupState
    //...
  } = instance;
  //...
  result = normalizeVNode(
    render.call( thisProxy, proxyToUse, renderCache, !!(process.env.NODE_ENV !== 'production') ? shallowReadonly(props) : props,
    setupState,  data,  ctx )
  );
  //...
}
  1. 根据上面的RefTemplate.vue编译后代码,render函数中,利用toDisplayString读取instance.setupState的响应式变量的值,并转化成字符串,创建VNode。

5.总结

vue3中ref响应式变量为什么script中要用.value,而template模板中不需?

现在可以回答了!

  1. 创建Component组件实例instance,将Component setup函数返回的setupState响应式变量或函数挂和render模板渲染函数载在instance组件实例对象上。
  2. instance组件实例对象添加渲染副作用,监听响应式变量的改变,执行渲染更新。
  3. instance组件实例对象中setupState响应式变量或函数传入render模板渲染函数执行,使用toDisplayString函数读取ref响应式变量.value的值,转化成文本。
  4. render模板渲染函数返回的虚拟DOM,patch到页面成真实DOM。
相关推荐
啊啊啊~~5 分钟前
歌词滚动效果
javascript·html
球球和皮皮1 小时前
Babylon.js学习之路《四、Babylon.js 中的相机(Camera)与视角控制》
javascript·3d·前端框架·babylon.js
Java&Develop1 小时前
Vue ElementUI原生upload修改字体大小和区域宽度
vue.js
郭尘帅6662 小时前
vue3基础学习(上) [简单标签] (vscode)
前端·vue.js·学习
njsgcs2 小时前
opencascade.js stp vite webpack 调试笔记
开发语言·前端·javascript
st紫月3 小时前
用vue和go实现登录加密
前端·vue.js·golang
岁岁岁平安3 小时前
Vue3学习(组合式API——计算属性computed详解)
前端·javascript·vue.js·学习·computed·计算属性
刺客-Andy4 小时前
React 第三十九节 React Router 中的 unstable_usePrompt Hook的详细用法及案例
前端·javascript·react.js
Go_going_4 小时前
【js基础笔记] - 包含es6 类的使用
前端·javascript·笔记
工业互联网专业6 小时前
基于springboot+vue的医院门诊管理系统
java·vue.js·spring boot·毕业设计·源码·课程设计·医院门诊管理系统