vue操作数据改变更新视图
这次给大家带来vue操作数据改变更新视图,vue操作数据改变更新视图的注意事项有哪些,下面就是实战案例,一起来看一下。
前记
三个月前看了vue源码来分析如何做到响应式数据的, 文章名字叫vue源码之响应式数据, 最后分析到, 数据变化后会调用Watcher的update()方法. 那么时隔三月让我们继续看看update()做了什么. (这三个月用react-native做了个项目, 也无心总结了, 因为好像太简单了).
本文叙事方式为树藤摸瓜, 顺着看源码的逻辑走一遍, 查看的vue的版本为2.5.2. 我fork了一份源码用来记录注释.
目的
明确调查方向才能直至目标, 先说一下目标行为: 数据变化以后执行了什么方法来更新视图的. 那么准备开始以这个方向为目标从vue源码的入口开始找答案.
从之前的结论开始
先来复习一下之前的结论:
vue构造的时候会在data(和一些别的字段)上建立Observer对象, getter和setter被做了拦截, getter触发依赖收集, setter触发notify.
另一个对象是Watcher, 注册watch的时候会调用一次watch的对象, 这样触发了watch对象的getter, 把依赖收集到当前Watcher的deps里, 当任何dep的setter被触发就会notify当前Watcher来调用Watcher的update()方法.
那么这里就从注册渲染相关的Watcher开始.
找到了文件在src/core/instance/lifecycle.js中.
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
mountComponent
渲染相关的Watcher是在mountComponent()这个方法中调用的, 那么我们搜一下这个方法是在哪里调用的. 只有2处, 分别是src/platforms/web/runtime/index.js和src/platforms/weex/runtime/index.js, 以web为例:
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
原来如此, 是$mount()方法调用了mountComponent(), (或者在vue构造时指定el字段也会自动调用$mount()方法), 因为web和weex(什么是weex?之前别的文章介绍过)渲染的标的物不同, 所以在发布的时候应该引入了不同的文件最后发不成不同的dist(这个问题留给之后来研究vue的整个流程).
下面是mountComponent方法:
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el // 放一份el到自己的属性里 if (!vm.$options.render) { // render应该经过处理了, 因为我们经常都是用template或者vue文件 // 判断是否存在render函数, 如果没有就把render函数写成空VNode来避免红错, 并报出黄错 vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { // 不看这里的代码了, 直接看else里的, 行为是一样的 updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined // 注册一个Watcher new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
这段代码其实只做了3件事:
调用beforeMount钩子
建立Watcher
调用mounted钩子
(哈哈哈)那么其实核心就是建立Watcher了.
看一下Watcher的参数: vm是this, updateComponent是一个函数, noop是空, null是空, true代表是RenderWatcher.
在Watcher里看了isRenderWatcher:
if (isRenderWatcher) { vm._watcher = this }
是的, 只是复制了一份用来在watcher第一次patch的时候判断一些东西(从注释里看的, 我现在还不知道是干嘛的).
那么只有一个问题没解决就是updateComponent是个什么东西.
updateComponent
在Watcher的构造函数的第二个参数传了function, 那么这个函数就成了watcher的getter. 聪明的你应该已经猜到, 在这个updateComponent里一定调用了视图中所有的数据的getter, 才能在watcher中建立依赖从而让视图响应数据的变化.
updateComponent = () => { vm._update(vm._render(), hydrating) }
那么就去找vm._update()和vm._render().
在src/core/instance/render.js找到了._render()方法.
Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options // todo: render和_parentVnode的由来 // reset _rendered flag on slots for duplicate slot check if (process.env.NODE_ENV !== 'production') { for (const key in vm.$slots) { // $flow-disable-line vm.$slots[key]._rendered = false } } if (_parentVnode) { vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject } // set parent vnode. this allows render functions to have access // to the data on the placeholder node. vm.$vnode = _parentVnode // render self let vnode try { vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { // catch其实不需要看了, 都是做异常处理, _vnode是在vm._update的时候保存的, 也就是上次的状态或是null(init的时候给的) handleError(e, vm, `render`) // return error render result, // or previous vnode to prevent render error causing blank component /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { if (vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e) } catch (e) { handleError(e, vm, `renderError`) vnode = vm._vnode } } else { vnode = vm._vnode } } else { vnode = vm._vnode } } // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ) } vnode = createEmptyVNode() } // set parent vnode.parent = _parentVnode return vnode } }
这个方法做了:
根据当前vm的render方法来生成VNode. (render方法可能是根据template或vue文件编译而来, 所以推论直接写render方法效率最高)
如果render方法有问题, 那么首先调用renderError方法, 再不行就读取上次的vnode或是null.
如果有父节点就放到自己的.parent属性里.
最后返回VNode
所以核心是这句:
vnode = render.call(vm._renderProxy, vm.$createElement)
其中的render(), vm._renderProxy, vm.$createElement都不知道是什么.
先看vm._renderProxy: 是initMixin()的时候设置的, 在生产环境返回vm, 开发环境返回代理, 那么我们认为他是一个可以debug的vm(就是vm), 细节之后再看.
vm.$createElement的代码在vdom文件夹下, 看了下是一个方法, 返回值一个VNode.
render有点复杂, 能不能以后研究, 总之就是把template或者vue单文件和mount目标parse成render函数.
小总结: vm._render()的返回值是VNode, 根据当前vm的render函数
接下来看vm._update()
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this if (vm._isMounted) { callHook(vm, 'beforeUpdate') } // 记录update之前的状态 const prevEl = vm.$el const prevVnode = vm._vnode const prevActiveInstance = activeInstance activeInstance = vm vm._vnode = vnode // Vue.prototype.patch is injected in entry points // based on the rendering backend used. if (!prevVnode) { // 初次加载, 只有_update方法更新vm._vnode, 初始化是null // initial render vm.$el = vm.patch( // patch创建新dom vm.$el, vnode, hydrating, false /* removeOnly */, vm.$options._parentElm, vm.$options._refElm ) // no need for the ref nodes after initial patch // this prevents keeping a detached DOM tree in memory (#5851) vm.$options._parentElm = vm.$options._refElm = null } else { // updates vm.$el = vm.patch(prevVnode, vnode) // patch更新dom } activeInstance = prevActiveInstance // update vue reference if (prevEl) { prevEl.vue = null } if (vm.$el) { vm.$el.vue = vm } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el } // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. }
我们关心的部分其实就是patch()的部分, patch()做了对dom的操作, 在_update()里判断了是否是初次调用, 如果是的话创建新dom, 不是的话传入新旧node进行比较再操作.
结论
vue的视图渲染是一种特殊的Watcher, watch的内容是一个函数, 函数运行的过程调用了render函数, render又是由template或者el的dom编译成的(template中含有一些被observe的数据). 所以template中被observe的数据有变化触发Watcher的update()方法就会重新渲染视图.
遗留
render函数是在哪里被编译的
vue源码发布时引入不同平台最后打成dist的流程是什么
patch和VNode的分析
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上是vue操作数据改变更新视图的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

暴雪战网更新一直卡在45%怎么解决?近期有很多人在更新软件的时候,都是卡在45%的进度条,重启多次还是会卡住,那么这种情况应该要如何解决,我们可以通过重新安装客户端、切换地区、删除文件的方式来处理,本期软件教程就来分享操作步骤,希望能够给更多的人带来帮助。 暴雪战网更新一直卡在45%怎么解决 一、客户端 1、首先需要确认你的客户是官网下载的官方版本。 2、如果不是的话,用户可以进入亚服网址来进行下载。 3、进入以后点击右上角的下载就可以了。 注意:安装的时候一定不要选择简体中文。

第七史诗已经确定将于2月22日中午11点进行不停机更新,本次更新为我们带来超级多新的活动和内容,包括莱娅和甜蜜奇迹限定召唤几率UP、神秘卡池更新、特别支线故事奇迹女仆王国第二周开启等,一起来看看本次的更新吧。手游更新时间表第七史诗2月22日更新:奇迹女仆王国第二周开启※「莱娅」&「甜蜜奇迹」限定召唤几率UP!■限定召唤几率UP时间:-2024/02/22(周四)11:00~2024/03/07(周四)10:59■角色属性&职业:自然属性、战士■角色简介:四人乐队【奇迹女仆王国】的副主唱兼贝

Angular.js是一种可自由访问的JavaScript平台,用于创建动态应用程序。它允许您通过扩展HTML的语法作为模板语言,以快速、清晰地表示应用程序的各个方面。Angular.js提供了一系列工具,可帮助您编写、更新和测试代码。此外,它还提供了许多功能,如路由和表单管理。本指南将讨论在Ubuntu24上安装Angular的方法。首先,您需要安装Node.js。Node.js是一个基于ChromeV8引擎的JavaScript运行环境,可让您在服务器端运行JavaScript代码。要在Ub

一分钟搞定:如何更新pip版本,需要具体代码示例随着Python的快速发展,pip成为了Python包管理的标准工具。然而,随着时间的推移,pip版本也在不断更新,为了能够使用最新的功能和修复可能的安全漏洞,更新pip版本是非常重要的。本文将介绍如何在一分钟内快速更新pip,并提供具体的代码示例。首先,我们需要打开命令行窗口。在Windows系统中,可以使用

在美团外卖商家版的运营过程中,起送价的设置是一个至关重要的环节。合理的起送价不仅能够帮助商家控制成本,还能在一定程度上提升订单金额,从而增加整体收益。然而,很多商家对于如何修改起送价并不十分了解。那么在下文中本站小编就将为各位商家们带来详细的起送价设置攻略,想要了解的话就快来下文中一探究竟吧!在美团外卖商家中心,通过登录后进入店铺设置,然后选择门店管理,在门店管理页面顶部的切换导航中,选中配送信息,接着点击新增配送区域即可完成操作。在您添加地点后,系统将自动显示相应的配送费用。完成订单后,您将获

提灯与地下城已经确定将于2月29日更新,更新之后会上线提灯与地下城重制版本,而且重制版本还会与哪吒传奇联动,重制版本还带来全新职业,玩家可以直接转职哦,地下城内容也将拓展,开放全新副本区域等。手游更新时间表提灯与地下城2月29日更新:重制版╳「哪吒传奇」联动版本重点内容全新职业,邀您转职什么?提灯者竟然可以转职了?如此炫酷的装备真是让人眼馋,听说,转职之后,提灯者还能学习很多超帅的技能,五郎直接惊呼:泰裤辣!哪吒传奇,联动来袭!踩着那风火轮,乾坤圈手中拿♫~智勇双全的小英雄:哪吒和小龙女,即将来

小伙伴电脑出现这样的故障,打开“此电脑”和C盘文件会提示“Explorer.EXEWindows无法访问指定设备、路径或文件。你可能没有适当的权限访问访问该项目。”包括文件夹、文件、此电脑、回收站等,双击都会弹出这样的窗口,右键打开又是正常的。这是系统更新导致,如果你也遇到这样的情况,下面小编教大家如何解决。一,打开注册表编辑器Win+R,输入regedit,或右键开始菜单运行输入regedit;二,定位注册表“计算机\HKEY_CLASSES_ROOT\PackagedCom\ClassInd

微星显卡是市面上主流的显卡品牌,我们知道显卡都需要安装驱动才能发挥性能,并保证兼容性。那么微星显卡驱动要怎么更新到最新版本呢?一般微星显卡驱动可以官网下载驱动安装,下面就来了解一下吧。 显卡驱动更新方法: 1.首先我们进入“微星官网”。 2.进入后点击右上角“搜索”按钮并输入自己的显卡型号。 3.然后找到对应的显卡点开详情页。 4.随后进入上方“技术支持”选项。 5.最后在“驱动&下载”
