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。
相关推荐
程序员小刚25 分钟前
基于SpringBoot + Vue 的图书馆座位预约系统
vue.js·spring boot·后端
岁岁岁平安29 分钟前
SpringBoot3+Vue3实战(Vue3快速开发登录注册页面并对接后端接口)(4)
javascript·vue.js·elementui·java-ee·intellij-idea·springboot
Hermione_log34 分钟前
解决百度地图渲染中“Cannot read properties of undefined (reading ‘texture’)”问题
前端·vue.js
Do44 分钟前
性能优化之渲染层优化
javascript·面试·性能优化
工作碎碎念44 分钟前
Lodash 引入以及常用方法
javascript
情非得已小猿猿1 小时前
‌React Hooks主要解决什么
javascript·react.js·ecmascript
前端娱乐圈1 小时前
【前端 vue 或者麦克风,智能语音识别和播放功能】
vue.js·音频·语音识别
徐小夕1 小时前
开源了一款在线电子表格插件,支持一键导入excel文件!
前端·javascript·github
小陆猿1 小时前
如何在Web应用中实现Excel文件的前端展示与编辑:基于SheetJS与Handsontable的实战
前端·vue.js·excel
苏州第一深情2 小时前
【vue+leaflet+elementUI】vue项目中在Leaflet弹框Popup中使用elementui组件(三)
前端·javascript·vue.js