vue实现响应式数据的原理,vue3.0响应式原理

  vue实现响应式数据的原理,vue3.0响应式原理

  本文主要介绍vue3.x源代码分析的数据响应。在讲解的过程中,我们会对比Vue2.x的API特性,在使用上有哪些不同?有需要的朋友可以参考一下。

  

目录

  前言数据响应的一般流程是什么?vue2.x数据响应和3.x响应对比一般流程图实现代码仓库依赖收集结束。

  

前言

  如果你想念秋枫和冬天的雪,那么樱花一定会在春天盛开。最近一直在准备自己的考试。考完试,终于可以继续研究源代码写文章了,哈哈哈。研究过vue的人都知道,数据响应性在vue框架中极其重要。无论是写代码还是面试,数据响应能力都是核心内容。在vue3的官网文档中,作者说如果想让数据更有响应性,可以把数据放在reactive中。官方文件简单提了一下,刚开始不太懂。后来看了源代码才知道,在vue3中,response已经成为一个独立的模块,处理response的模块是反应式的;

  

什么是数据响应式

  一开始用Vue的时候,和之前jq开发的一个很大的区别就是基本不需要手动操作dom,相关的dom会在数据中声明的数据状态发生变化后自动重新渲染。

  换句话说,Vue知道哪个数据状态发生了变化,在哪里使用,需要相应地修改。

  因此,实现数据响应有两个关键问题:

  我如何知道数据已经更改?我如何知道数据更改后需要修改的地方?对于第一个问题,如何知道数据发生了变化?Vue3之前用的是ES5的一个API对象。定义属性VUE 3使用了ES6的代理,它检测被检测数据的变化,添加了getter和setter,这样你就可以知道数据什么时候被读取和修改了。

  第二个问题是,如何知道数据发生变化后需要修改的地方?Vue已经收集了每个数据的相关依赖关系,这里的依赖关系实际上是一个对象,它保存了数据的旧值以及数据发生变化后需要执行的功能。当每个响应的数据发生变化时,它将遍历与通知对应的每个依赖项。收到通知后,依赖项会判断新旧值是否发生了变化。如果是,它将执行一个回调函数来响应数据更改(比如修改dom)。

  

数据响应式的大体流程

  在vue3.0的响应式部分,我们需要找到的核心文件是vue3.0源代码的包中runtime-core下的src我们今天学习的线是沿着render线走的;

  返回{

  渲染,

  水合物,

  createApp: createAppAPI(渲染,水合物)

  }

  找到这个文件下的渲染函数,如下图;该函数用于将传入的vnode呈现到指定的容器中;

  const render:rootdrenderfunction=(vnode,container)={

  if (vnode==null) {

  如果(容器。_vnode) {

  卸载(容器。_vnode,null,null,true)

  }

  }否则{

  补丁(容器。_vnode null,vnode,container)

  }

  flushPostFlushCbs()

  集装箱。_vnode=vnode

  }

  看贴片法。如果你初始化,你将采取else if(shapeflagshapeflags.com部件)。

  const patch: PatchFn=(

  n1,

  n2,

  容器,

  anchor=null,

  parentComponent=null,

  parent悬念=null,

  isSVG=false,

  优化=假

  )={

  //修补不同类型,卸载旧树

  如果(n1!isSameVNodeType(n1,n2)) {

  anchor=getNextHostNode(n1)

  unmount(n1,parentComponent,parent悬念,true)

  n1=空

  }

  if (n2.patchFlag===PatchFlags。保释){

  优化=假

  n2.dynamicChildren=null

  }

  const { type,ref,shapeFlag }=n2

  开关(类型){

  案例文本:

  processText(n1,n2,容器,锚)

  破裂

  案例评论:

  processCommentNode(n1,n2,容器,锚)

  破裂

  案例静态:

  if (n1==null) {

  mountStaticNode(n2,容器,锚,isSVG)

  } else if (__DEV__) {

  patchStaticNode(n1,n2,容器,isSVG)

  }

  破裂

  案例片段:

  processFragment(

  n1,

  n2,

  容器,

  锚,

  parentComponent,

  家长悬念,

  isSVG,

  最佳化的

  )

  破裂

  默认值:

  if (shapeFlag ShapeFlags .元素){

  processElement(

  n1,

  n2,

  容器,

  锚,

  parentComponent,

  家长悬念,

  isSVG,

  最佳化的

  )

  } else if (shapeFlag ShapeFlags .组件){

  //初始化走这个

  进程组件(

  n1,

  n2,

  容器,

  锚,

  parentComponent,

  家长悬念,

  isSVG,

  最佳化的

  )

  } else if (shapeFlag ShapeFlags .瞬间移动){

  ;(类型为传送的类型)。流程(

  n1为传送节点,

  氮气作为传送节点,

  容器,

  锚,

  parentComponent,

  家长悬念,

  isSVG,

  优化,

  内部构件

  )

  } else if(_ _ FEATURE _ suspension _ _ shape flag形状标志.悬念){

  ;(类型为悬架类型)。流程(

  n1,

  n2,

  容器,

  锚,

  parentComponent,

  家长悬念,

  isSVG,

  优化,

  内部构件

  )

  } else if (__DEV__) {

  警告(无效的虚拟节点类型:,type,`(${typeof type})`)

  }

  }

  //设置引用

  if (ref!=null parentComponent) {

  setRef(ref,n1 n1.ref,parentComponent,parent悬念n2)

  }

  }

  接下来查看过程组件方法,接下来走我们熟悉的安装组件

  const processComponent=(

  n1: VNode null,

  n2: VNode,

  容器:RendererElement,

  anchor: RendererNode null,

  父组件:组件内部实例空,

  父挂起:suspendeboundary null,

  isSVG:布尔型,

  优化:布尔型

  )={

  if (n1==null) {

  if (n2.shapeFlag ShapeFlags .COMPONENT _ keep _ ALIVE){

  ;(parentComponent!中强作为KeepAliveContext).激活(

  n2,

  容器,

  锚,

  isSVG,

  最佳化的

  )

  }否则{

  //初始化走挂载流程

  安装组件(

  n2,

  容器,

  锚,

  parentComponent,

  家长悬念,

  isSVG,

  最佳化的

  )

  }

  }否则{

  更新组件(n1,n2,优化)

  }

  }

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

  常量挂载组件:MountComponentFn=(

  初始节点,

  容器,

  锚,

  parentComponent,

  家长悬念,

  isSVG,

  最佳化的

  )={

  //创建组件实例

  const instance:组件内部实例=(初始vnode。component=createcomponent实例(

  初始节点,

  parentComponent,

  父母悬念

  ))

  if(_ _ DEV _ _ instance。类型。_ _ hmrId){

  寄存器HMR(实例)

  }

  if (__DEV__) {

  pushWarningContext(初始vnode)

  开始测量(实例,‘坐骑’)

  }

  //为保持活跃注入渲染器内部

  if (isKeepAlive(initialVNode)) {

  ;(instance.ctx作为KeepAliveContext).渲染器=内部

  }

  //解析设置上下文的属性和插槽

  if (__DEV__) {

  开始测量(实例, init `)

  }

  //安装组件:选项处理

  安装组件(实例)

  if (__DEV__) {

  最终测量值(实例, init `)

  }

  //setup()是异步的。该组件依赖异步逻辑进行解析

  //继续之前

  if(_ _ FEATURE _ suspension _ _ instance。异步dep){

  父母悬念父母悬念。注册Dep(实例,setupRenderEffect)

  //如果这不是水合作用,就给它一个占位符

  //TODO处理自定义回退

  如果(!initialVNode.el) {

  const占位符=(实例。subtree=createVNode(Comment))

  processCommentNode(null,占位符,容器!锚)

  }

  返回

  }

  //建立渲染函数副作用:依赖收集

  setupRenderEffect(

  实例,

  初始节点,

  容器,

  锚,

  家长悬念,

  isSVG,

  最佳化的

  )

  if (__DEV__) {

  popWarningContext()

  最终测量值(实例,“安装”)

  }

  }

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

  导出函数安装组件(

  实例:ComponentInternalInstance,

  isSSR=false

  ) {

  isInSSRComponentSetup=isSSR

  const { props,children,shapeFlag }=instance.vnode

  const is stateful=shape flag shape flags .有状态组件

  initProps(实例,道具,isStateful,isSSR)

  初始化插槽(实例,子代)

  const setupResult=isStateful

  ?setupStatefulComponent(实例isSSR)

  :未定义

  isInSSRComponentSetup=false

  返回设置结果

  }

  进入方法setupStatefulComponent,其中常量组件=实例。类型作为组件选项用于组件配置。其中instance.proxy=新代理(instance.ctx,PublicInstanceProxyHandlers)用于代理,数据,$等都是在这里处理的。

  函数setupStatefulComponent(

  实例:ComponentInternalInstance,

  isSSR:布尔型

  ) {

  //组件配置

  常量组件=实例。类型作为组件选项

  if (__DEV__) {

  if (Component.name) {

  validateComponentName(组件。name,instance.appContext.config)

  }

  if (Component.components) {

  常量名称=对象。按键(组件。组件)

  对于(设I=0;一。姓名。长度;i ) {

  validateComponentName(names[i],instance.appContext.config)

  }

  }

  if (Component.directives) {

  常量名称=对象。按键(组件。指令)

  对于(设I=0;一。姓名。长度;i ) {

  有效方向名称(名称[我])

  }

  }

  }

  //0.创建渲染代理属性访问缓存

  instance.accessCache={}

  //1.创建公共实例/渲染代理

  //同时将它标记为生的,这样就不会被观察到

  instance.proxy=新代理(instance.ctx,PublicInstanceProxyHandlers)

  if (__DEV__) {

  exposePropsOnRenderContext(实例)

  }

  //2.呼叫设置()

  常量{设置}=组件

  如果(设置){

  常量设置上下文=(实例。设置上下文=

  设置。长度1?createSetupContext(实例):空)

  当前实例=实例

  暂停跟踪()

  const setup result=callWithErrorHandling(

  设置,

  实例,

  错误代码。设置_功能,

  [__DEV__?浅层只读(实例。道具):实例。道具,设置文本]

  )

  重置跟踪()

  当前实例=空

  if (isPromise(setupResult)) {

  if (isSSR) {

  //返回承诺,以便服务器呈现程序可以等待它

  返回设置结果。然后((解析结果:未知)={

  handleSetupResult(instance,resolvedResult,isSSR)

  })

  } else if(_ _ FEATURE _ suspension _ _){

  //异步安装程序返回了承诺。

  //在这里保释,等待重新进入。

  instance.asyncDep=setupResult

  } else if (__DEV__) {

  警告(

  `设置()返回了一个承诺,但是您正在使用的某视频剪辑软件版本`

  `还不支持它。

  )

  }

  }否则{

  handleSetupResult(实例,设置结果,isSSR)

  }

  }否则{

  //处理选项等事务

  finishComponentSetup(实例isSSR)

  }

  }

  因为在我们的例子中没有设置,所以我们将执行FinishComponentSetup (instance,ISSSR)来处理与选项api相关的事情。进入函数查看代码逻辑,会看到下面的代码。这部分代码用来处理与option API相关的事情,用来支持vue2.x的版本

  //支持2.x选项

  //支持选项API

  if (__FEATURE_OPTIONS_API__) {

  currentInstance=实例

  应用选项(实例,组件)

  当前实例=空

  }

  输入applyOptions方法;往下翻,你会看到这几行注释,很清楚的解释了vue2.x中各个选项的优先级,包括道具、注入、方法、数据等。

  //选项初始化顺序(与Vue 2一致):

  //- props(已经在此函数之外完成)

  //-注射

  //-方法

  //-数据(推迟,因为它依赖于“此”访问)

  //-计算得出

  //- watch(推迟,因为它依赖于“此”访问)

  继续往下看,你会看到这几行代码。我们这里不用混合形式,所以这一行代码,涉及到数据对应的公式,在resolveData方法里。

  如果(!asMixin) {

  if (deferredData.length) {

  deferred data . foreach(dataFn=resolved ATA(instance,dataFn,publicThis))

  }

  if(数据选项){

  //数据响应类型

  resolveData(实例,数据选项,公共数据)

  }

  输入resolveData,可以看到const data=data fn . call(public this,public this)。这行代码用于获取数据对象。代码行instance.data=reactive(data)用于响应数据。其核心是反应性的,用来做反应性的处理。选项api,设置,最后采取响应式的方法,用这个方法做响应式的处理。

  函数resolveData(

  实例:ComponentInternalInstance,

  dataFn: DataFn,

  public this:ComponentPublicInstance

  ) {

  if (__DEV__!isFunction(dataFn)) {

  警告(

  `数据选项必须是函数。`

  不再支持使用普通对象

  )

  }

  //获取数据对象

  const data=datafn . call(publicThis,public this)

  if (__DEV__ isPromise(data)) {

  警告(

  ` data()返回了一个承诺-注意data()不能是异步的;如果你

  `要在组件呈现之前执行数据提取,请使用` 2

  `异步设置()暂记.

  )

  }

  如果(!isObject(data)) {

  __DEV__ warn(`data()应返回一个对象。`)

  } else if(instance . data===EMPTY _ OBJ){

  //响应数据

  instance.data=reactive(data)

  }否则{

  //现有数据:这是mixin或extends。

  扩展(实例.数据,数据)

  }

  }

  进入反应式,观察代码逻辑;CreateReactiveObject用于处理数据。目标是最后要转换的东西。

  返回createReactiveObject(

  目标,

  假的,

  可变处理器,

  可变集合处理程序

  )

  mutableHandlers中有一些get、set、deleteProperty等方法。MutableCollectionHandlers创建依赖集合等操作。

  

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

  这里,我们先来回顾一下vue2.x是如何处理responsive的。DefineReactive用于截获每个键,以便可以检测到数据更改。这套处理方法是有问题的。当数据逐层嵌套时,会逐层递归执行,从而消耗大量内存。从这个角度来说,这一套处理方式并不友好。在Vue3中,defineReactive也用于拦截每个键。与此不同的是,vue3.x中的defineReactive使用了一层代理,相当于增加了另一层。Vue2.x需要递归对象化所有键,比较慢。阵列响应需要额外的实现。而且添加或删除属性是无法监控的,所以需要专门的api。现在,一个新的代理直接解决了所有问题。同时,以前的方法不知道映射、集合、类等数据结构。

  

大致流程图

  那么我们来梳理一下反应过程中的顺序。

  

实现依赖收集

  在实现响应的过程中,依赖收集与之密切相关,在setupRenderEffect函数中使用了effect函数来收集依赖。输入setupRenderEffect函数的内部。上面的代码里有这个函数,这里就不赘述了。我们继续往下看。当您进入该函数时,您将看到下面的代码。效果可以建立依赖关系:传入效果的回调函数和响应数据之间;效果相当于vue2中的dep,然后vue3中没有守望者。

  instance . update=effect(function component effect(){

  如果(!instance.isMounted) {

  let vnode hook:vnode hook null undefined

  const { el,props }=initialVNode

  const { bm,m,parent }=实例

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

  const subTree=(instance . subTree=rendercomponentrout(instance))

  至此,vue3.0的响应式部分将告一段落。

  

代码仓库

  手写的vue3.0短版实现数据响应,已经上传到个人仓库,有兴趣的可以看看。喜欢的可以关注一下,哈哈哈。关注我,你在编程的道路上会多一个朋友。https://gitee.com/zhang-shichuang/xiangyingshi/tree/master/

  

结尾

  Vue的数据响应能力经常在面试中被问到。原理就是看源代码。看源代码的时候,难免会有枯燥的时候,但是坚持就是胜利。后面会分享vue的编译过程和react相关的源代码知识。

  关于vue3.x源代码分析的数据响应这篇文章到此为止。更多关于vue3.x数据响应的信息,请搜索我们之前的文章或者继续浏览下面的相关文章。希望大家以后能多多支持我们!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: