我的编程空间,编程开发者的网络收藏夹
学习永远不晚

vue3.x数据响应式的流程是怎样的

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

vue3.x数据响应式的流程是怎样的

本篇内容介绍了“vue3.x数据响应式的流程是怎样的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

什么是数据响应式

从一开始使用 Vue 时,对于之前的 jq 开发而言,一个很大的区别就是基本不用手动操作 dom,data 中声明的数据状态改变后会自动重新渲染相关的 dom。
换句话说就是 Vue 自己知道哪个数据状态发生了变化及哪里有用到这个数据需要随之修改。

因此实现数据响应式有两个重点问题:

  • 如何知道数据发生了变化?

  • 如何知道数据变化后哪里需要修改?

对于第一个问题,如何知道数据发生了变化,Vue3 之前使用了 ES5 的一个 API Object.defineProperty Vue3 中使用了 ES6 的 Proxy,都是对需要侦测的数据进行 变化侦测 ,添加 getter 和 setter ,这样就可以知道数据何时被读取和修改。

第二个问题,如何知道数据变化后哪里需要修改,Vue 对于每个数据都收集了与之相关的 依赖 ,这里的依赖其实就是一个对象,保存有该数据的旧值及数据变化后需要执行的函数。每个响应式的数据变化时会遍历通知其对应的每个依赖,依赖收到通知后会判断一下新旧值有没有发生变化,如果变化则执行回调函数响应数据变化(比如修改 dom)。

数据响应式的大体流程

在vue3.0的响应式的部分,我们需要找的核心文件是vue3.0源码的packages里面的runtime-core下面的class="lazy" data-src里面的;我们今天研究的这条线,就是沿着render这条线走下去的;

 return {    render,    hydrate,    createApp: createAppAPI(render, hydrate)  }

在该文件下找到render函数,如下所示;该函数的作用是渲染传入vnode,到指定容器中;

 const render: RootRenderFunction = (vnode, container) => {    if (vnode == null) {      if (container._vnode) {        unmount(container._vnode, null, null, true)      }    } else {      patch(container._vnode || null, vnode, container)    }    flushPostFlushCbs()    container._vnode = vnode  }

查看patch方法,初始化的话会走else if (shapeFlag & ShapeFlags.COMPONENT)

 const patch: PatchFn = (    n1,    n2,    container,    anchor = null,    parentComponent = null,    parentSuspense = null,    isSVG = false,    optimized = false  ) => {    // patching & not same type, unmount old tree    if (n1 && !isSameVNodeType(n1, n2)) {      anchor = getNextHostNode(n1)      unmount(n1, parentComponent, parentSuspense, true)      n1 = null    }    if (n2.patchFlag === PatchFlags.BAIL) {      optimized = false      n2.dynamicChildren = null    }    const { type, ref, shapeFlag } = n2    switch (type) {      case Text:        processText(n1, n2, container, anchor)        break      case Comment:        processCommentNode(n1, n2, container, anchor)        break      case Static:        if (n1 == null) {          mountStaticNode(n2, container, anchor, isSVG)        } else if (__DEV__) {          patchStaticNode(n1, n2, container, isSVG)        }        break      case Fragment:        processFragment(          n1,          n2,          container,          anchor,          parentComponent,          parentSuspense,          isSVG,          optimized        )        break      default:        if (shapeFlag & ShapeFlags.ELEMENT) {          processElement(            n1,            n2,            container,            anchor,            parentComponent,            parentSuspense,            isSVG,            optimized          )        } else if (shapeFlag & ShapeFlags.COMPONENT) {          // 初始化走这个          processComponent(            n1,            n2,            container,            anchor,            parentComponent,            parentSuspense,            isSVG,            optimized          )        } else if (shapeFlag & ShapeFlags.TELEPORT) {          ;(type as typeof TeleportImpl).process(            n1 as TeleportVNode,            n2 as TeleportVNode,            container,            anchor,            parentComponent,            parentSuspense,            isSVG,            optimized,            internals          )        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {          ;(type as typeof SuspenseImpl).process(            n1,            n2,            container,            anchor,            parentComponent,            parentSuspense,            isSVG,            optimized,            internals          )        } else if (__DEV__) {          warn('Invalid VNode type:', type, `(${typeof type})`)        }    }    // set ref    if (ref != null && parentComponent) {      setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)    }  }

接下来查看processComponent方法,接下来走我们熟悉的mountComponent

 const processComponent = (    n1: VNode | null,    n2: VNode,    container: RendererElement,    anchor: RendererNode | null,    parentComponent: ComponentInternalInstance | null,    parentSuspense: SuspenseBoundary | null,    isSVG: boolean,    optimized: boolean  ) => {    if (n1 == null) {      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {        ;(parentComponent!.ctx as KeepAliveContext).activate(          n2,          container,          anchor,          isSVG,          optimized        )      } else {        // 初始化走挂载流程        mountComponent(          n2,          container,          anchor,          parentComponent,          parentSuspense,          isSVG,          optimized        )      }    } else {      updateComponent(n1, n2, optimized)    }  }

进入mountComponent方法,其中比较重要的instance为创建组件实例,setupComponent为安装组件准备的;做选项处理用的;setupRenderEffec用于建立渲染函数副作用,在依赖收集的时候使用。

const mountComponent: MountComponentFn = (    initialVNode,    container,    anchor,    parentComponent,    parentSuspense,    isSVG,    optimized  ) => {    // 创建组件实例    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(      initialVNode,      parentComponent,      parentSuspense    ))    if (__DEV__ && instance.type.__hmrId) {      registerHMR(instance)    }    if (__DEV__) {      pushWarningContext(initialVNode)      startMeasure(instance, `mount`)    }    // inject renderer internals for keepAlive    if (isKeepAlive(initialVNode)) {      ;(instance.ctx as KeepAliveContext).renderer = internals    }    // resolve props and slots for setup context    if (__DEV__) {      startMeasure(instance, `init`)    }    // 安装组件:选项处理    setupComponent(instance)    if (__DEV__) {      endMeasure(instance, `init`)    }    // setup() is async. This component relies on async logic to be resolved    // before proceeding    if (__FEATURE_SUSPENSE__ && instance.asyncDep) {      parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)      // Give it a placeholder if this is not hydration      // TODO handle self-defined fallback      if (!initialVNode.el) {        const placeholder = (instance.subTree = createVNode(Comment))        processCommentNode(null, placeholder, container!, anchor)      }      return    }    // 建立渲染函数副作用:依赖收集    setupRenderEffect(      instance,      initialVNode,      container,      anchor,      parentSuspense,      isSVG,      optimized    )    if (__DEV__) {      popWarningContext()      endMeasure(instance, `mount`)    }  }

进入到setupComponent函数里面,观看setupComponent函数的内部逻辑,在这里面有属性插槽的初始化; 在这里面可以看到setupStatefulComponent方法,它就是用来处理响应式的。

export function setupComponent(  instance: ComponentInternalInstance,  isSSR = false) {  isInSclass="lazy" data-srcomponentSetup = isSSR  const { props, children, shapeFlag } = instance.vnode  const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT  initProps(instance, props, isStateful, isSSR)  initSlots(instance, children)  const setupResult = isStateful    ? setupStatefulComponent(instance, isSSR)    : undefined  isInSclass="lazy" data-srcomponentSetup = false  return setupResult}

进入方法setupStatefulComponent,其中const Component = instance.type as ComponentOptions用于组件配置。其中instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)用于代理,data,$等都是在这里处理的。

function setupStatefulComponent(  instance: ComponentInternalInstance,  isSSR: boolean) {  // 组件配置  const Component = instance.type as ComponentOptions  if (__DEV__) {    if (Component.name) {      validateComponentName(Component.name, instance.appContext.config)    }    if (Component.components) {      const names = Object.keys(Component.components)      for (let i = 0; i < names.length; i++) {        validateComponentName(names[i], instance.appContext.config)      }    }    if (Component.directives) {      const names = Object.keys(Component.directives)      for (let i = 0; i < names.length; i++) {        validateDirectiveName(names[i])      }    }  }  // 0. create render proxy property access cache  instance.accessCache = {}  // 1. create public instance / render proxy  // also mark it raw so it's never observed  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)  if (__DEV__) {    exposePropsOnRenderContext(instance)  }  // 2. call setup()  const { setup } = Component  if (setup) {    const setupContext = (instance.setupContext =      setup.length > 1 ? createSetupContext(instance) : null)    currentInstance = instance    pauseTracking()    const setupResult = callWithErrorHandling(      setup,      instance,      ErrorCodes.SETUP_FUNCTION,      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]    )    resetTracking()    currentInstance = null    if (isPromise(setupResult)) {      if (isSSR) {        // return the promise so server-renderer can wait on it        return setupResult.then((resolvedResult: unknown) => {          handleSetupResult(instance, resolvedResult, isSSR)        })      } else if (__FEATURE_SUSPENSE__) {        // async setup returned Promise.        // bail here and wait for re-entry.        instance.asyncDep = setupResult      } else if (__DEV__) {        warn(          `setup() returned a Promise, but the version of Vue you are using ` +            `does not support it yet.`        )      }    } else {      handleSetupResult(instance, setupResult, isSSR)    }  } else {    // 处理选项等事务    finishComponentSetup(instance, isSSR)  }}

由于咱们的案例里面没有setup,所以会执行 finishComponentSetup(instance, isSSR)来处理选项式api相关的东西。进入该函数里面查看代码逻辑,会看到如下的代码,该部分的代码用于处理选项式API相关的东西,用于支持vue2.x的版本。

  // support for 2.x options  // 支持选项API  if (__FEATURE_OPTIONS_API__) {    currentInstance = instance    applyOptions(instance, Component)    currentInstance = null  }

进入applyOptions方法里面;往下翻,会看到这几行注释,这几行注释清晰地解释了vue2.x里面各个选项的优先级,其中包括props、inject、methods、data等。

  // options initialization order (to be consistent with Vue 2):  // - props (already done outside of this function)  // - inject  // - methods  // - data (deferred since it relies on `this` access)  // - computed  // - watch (deferred since it relies on `this` access)

继续往下看,会看到这几行代码,我们这里面用的不是混入的形式,所以这行这一系列的代码,,其中涉及到数据相应式的代码都在resolveData方法里面。

  if (!asMixin) {    if (deferredData.length) {      deferredData.forEach(dataFn => resolveData(instance, dataFn, publicThis))    }    if (dataOptions) {      // 数据响应式      resolveData(instance, dataOptions, publicThis)    }

进入resolveData里面,可以看到const data = dataFn.call(publicThis, publicThis),这一行代码用于获取数据对象。instance.data = reactive(data)这一行代码用于对data做响应式处理。其中核心的就是reactive,该方法用于做响应式的处理。选项式api也好,setup也罢,最终走的都是reactive方法,用该方法来做响应式处理。

function resolveData(  instance: ComponentInternalInstance,  dataFn: DataFn,  publicThis: ComponentPublicInstance) {  if (__DEV__ && !isFunction(dataFn)) {    warn(      `The data option must be a function. ` +        `Plain object usage is no longer supported.`    )  }  // 获取数据对象  const data = dataFn.call(publicThis, publicThis)  if (__DEV__ && isPromise(data)) {    warn(      `data() returned a Promise - note data() cannot be async; If you ` +        `intend to perform data fetching before component renders, use ` +        `async setup() + <Suspense>.`    )  }  if (!isObject(data)) {    __DEV__ && warn(`data() should return an object.`)  } else if (instance.data === EMPTY_OBJ) {    // 对data 做响应式处理    instance.data = reactive(data)  } else {    // existing data: this is a mixin or extends.    extend(instance.data, data)  }}

进入到reactive里面,观察其中的代码逻辑;这里面的createReactiveObject用于对数据进行处理。其中target是最终要转化的东西。

  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers  )

其中mutableHandlers里面有一些get、set、deleteProperty等方法。mutableCollectionHandlers会创建依赖收集之类的操作。

vue2.x数据响应式和3.x响应式对比

到这里,我们先回顾一下vue2.x是如何处理响应式的。是用defineReactive来拦截每个key,从而可以检测数据变化,这一套处理方式是有问题的,当数据是一层嵌套一层的时候,就会进行层层递归,从而消耗大量的内存。由此来看,这一套处理方式算不上友好。Vue3里面也是用用defineReactive来拦截每个key,与此不同的是,在vue3.x里面的defineReactive里面用proxy做了一层代理,相当于加了一层关卡。Vue2.x里面需要进行递归对象所有key,速度慢。数组响应式需要额外实现。而且新增或删除属性无法监听,需要使用特殊api。而现在,直接一个new proxy直接把所有的问题都给解决了。与此同时,之前的那一套方法不知Map,Set、Class等数据结构。

大致流程图

然后我们梳理一下到响应式的过程中顺序

vue3.x数据响应式的流程是怎样的

实现依赖收集

在实现响应式的过程中,依赖收集是和其紧密相连的东西,其中setupRenderEffect函数中使用effect函数做依赖收集。进入setupRenderEffect函数内部,在上面的代码中有这个函数,这里不一一赘述,我们继续往下看。进入到该函数内部,会看到如下代码。effect可以建立一个依赖关系:传入effect的回调函数和响应式数据之间;effect就相当于的vue2里面的dep,然后vue3里面没有watcher了。

  instance.update = effect(function componentEffect() {      if (!instance.isMounted) {        let vnodeHook: VNodeHook | null | undefined        const { el, props } = initialVNode        const { bm, m, parent } = instance

继续往下看,会看到如下代码,subTree是当前组件vnode,其中renderComponentRoot方法用于实现渲染组件的根。

        const subTree = (instance.subTree = renderComponentRoot(instance))

“vue3.x数据响应式的流程是怎样的”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

vue3.x数据响应式的流程是怎样的

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

vue3.x数据响应式的流程是怎样的

本篇内容介绍了“vue3.x数据响应式的流程是怎样的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!什么是数据响应式从一开始使用 Vue 时,
2023-06-28

Cassandra的数据写入流程是怎样的

Cassandra的数据写入流程如下:客户端应用程序向Cassandra节点发送写请求。请求首先发送到负责处理数据分片的节点,称为coordinator节点。Coordinator节点负责确定数据将被写入的位置,并将写入请求路由到相应的
Cassandra的数据写入流程是怎样的
2024-04-09

ApacheBeam中的数据处理流程是怎样的

Apache Beam 是一个分布式数据处理框架,它可以处理批处理和流处理任务。数据处理流程通常包括以下步骤:创建一个 Pipeline 对象:Pipeline 是数据处理流程的核心概念,它表示一个数据处理任务的整体流程。定义数据源:通过调
ApacheBeam中的数据处理流程是怎样的
2024-03-06

springboot应用访问zookeeper的流程是怎样的

这期内容当中小编将会给大家带来有关springboot应用访问zookeeper的流程是怎样的,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。下面讲解了如果通过springboot快速开发web服务,并读取
2023-06-26

大数据Atlas的部署和维护流程是怎样的

大数据Atlas的部署和维护流程如下:部署Atlas:首先需要安装和配置Hadoop集群,然后下载并安装Atlas的软件包,在Atlas的配置文件中配置相关参数,如Hadoop集群的连接信息等。接着启动Atlas服务,并通过浏览器访问Atl
大数据Atlas的部署和维护流程是怎样的
2024-03-08

vue处理响应式数据的方法是什么

这篇“vue处理响应式数据的方法是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“vue处理响应式数据的方法是什么”文章吧
2023-07-05

Wormhole大数据流式处理平台的设计思想是怎样的

本篇文章为大家展示了Wormhole大数据流式处理平台的设计思想是怎样的,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。导读:互联网的迅猛发展使得数据不再昂贵,而如何从数据中更快速获取价值变得日益重要
2023-06-19

Android沉浸式状态栏的实现流程是怎样的

本篇内容主要讲解“Android沉浸式状态栏的实现流程是怎样的”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android沉浸式状态栏的实现流程是怎样的”吧!Android—沉浸式状态栏我们的征
2023-06-25

Cacti系统的数据存储方式是怎样的

Cacti系统的数据存储方式主要是基于RRDTool(Round-Robin Database Tool)数据库。RRDTool是一种用于存储时间序列数据的工具,它使用round-robin文件来存储数据。Round-robin文件是一种特
Cacti系统的数据存储方式是怎样的
2024-03-11

Python数据分析过程是怎样的

这篇文章主要介绍“Python数据分析过程是怎样的”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Python数据分析过程是怎样的”文章能帮助大家解决问题。一、需求介绍该需求主要是分析某一种数据的历史
2023-06-26

JavaScript数据类型对函数式编程的影响是什么

这篇文章主要介绍了JavaScript数据类型对函数式编程的影响是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇JavaScript数据类型对函数式编程的影响是什么文章都会有所收获,下面我们一起来看看吧。J
2023-07-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录