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。
相关推荐
傻小胖14 分钟前
ES6 Proxy 用法总结以及 Object.defineProperty用法区别
前端·javascript·es6
横冲直撞de36 分钟前
高版本electron使用iohook失败(使用uiohook-napi替代)
前端·javascript·electron
_Eden_37 分钟前
认识Electron 开启新的探索世界一
前端·javascript·electron
~怎么回事啊~38 分钟前
electron中调用C++
前端·javascript·electron
海上彼尚40 分钟前
electron-vite 构建后路由失效问题
前端·javascript·electron
HsuYang1 小时前
Vite源码学习(十一)——热更新(中)
前端·javascript·架构
不怕麻烦的鹿丸1 小时前
web前端录制canvas视频和video的声音,并合并成一个文件进行下载
前端·javascript·音视频·canvas
m0_748248771 小时前
Docker Compose一键部署Spring Boot + Vue项目
vue.js·spring boot·docker
我不当帕鲁谁当帕鲁2 小时前
arcgis for js实现平移立体效果
前端·javascript·arcgis
码视野2 小时前
基于 SpringBoot 和 Vue 的智能腰带健康监测数据可视化平台开发(文末联系,整套资料提供)
vue.js·spring boot·信息可视化