如何使用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()方法就会重新渲染视图.

相信看了本文案例你已经掌握了方法,更多精彩请关注【创想鸟】其它相关文章!

推荐阅读:

如何正确解决Vue 项目中遇到跨域问题

怎样使用React高阶组件

以上就是如何使用vue数据控制视图的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2756339.html

(0)
上一篇 2025年3月8日 07:30:49
下一篇 2025年3月8日 07:30:56

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • vue利用axios来完成数据的交互

    这篇文章主要介绍了vue利用axios来完成数据的交互,本文通过实例代码给大家讲解数据交互方法及安装方法,需要的朋友可以参考下 axios基于 Promise 的 HTTP 请求客户端,可同时在浏览器和 node.js 中使用 现在Vue官…

    2025年3月8日
    200
  • 使用JSON格式提交数据到服务端的实例代码

    这篇文章主要介绍了使用json格式提交数据到服务端的实例代码,代码简单易懂,非常不错,具有参考借鉴价值,需要的朋友参考下 准备Hero.java public class Hero {  private String name;  priv…

    编程技术 2025年3月8日
    200
  • 如何进行Vue数据双向绑定实现

    这次给大家带来如何进行Vue数据双向绑定实现,进行Vue数据双向绑定实现的注意事项有哪些,下面就是实战案例,一起来看一下。 一、示例 var vm = new Vue({ data: { obj: { a: 1 } }, created: …

    2025年3月8日
    200
  • vue数据控制视图源码解析

    本篇内容给大家详细分析了关于vue数据控制视图的源码以及重点做了注释,有兴趣的朋友参考学习下。 分析vue是如何实现数据改变更新视图的. 前记 三个月前看了vue源码来分析如何做到响应式数据的, 文章名字叫vue源码之响应式数据, 最后分析…

    编程技术 2025年3月8日
    200
  • 怎样实现vue 标签属性数据绑定和拼接

    这次给大家带来怎样实现vue标签属性数据绑定和拼接,实现vue标签属性数据绑定和拼接的注意事项有哪些,下面就是实战案例,一起来看一下。 在vue官网把文档扫了一遍后,就开始写网站项目了,没有设计,就百度里找了一个h5的助赢软件的网站把他co…

    编程技术 2025年3月8日
    200
  • 怎样使用vue keep-alive请求数据

    这次给大家带来怎样使用vue keep-alive请求数据,使用vue keep-alive请求数据的注意事项有哪些,下面就是实战案例,一起来看一下。 index页面:首页品牌入口 list页面:商品列表页面 product页面:商品详情页…

    编程技术 2025年3月8日
    200
  • vue中实现先请求数据再渲染dom分享

    下面我就为大家分享一篇vue中实现先请求数据再渲染dom分享,具有很好的参考价值,希望对大家有所帮助。 在项目中遇到了一个问题,下面是vue template中的代码: 我之前的写法是 这样做的结果是下面取dom的操作,取到的dom都是un…

    2025年3月8日
    200
  • 怎样使用vue组件进行数据传递

    这次给大家带来怎样使用vue组件进行数据传递,使用vue组件进行数据传递的注意事项有哪些,下面就是实战案例,一起来看一下。 Vue 的组件作用域都是孤立的,不允许在子组件的模板内直接引用父组件的数据。必须使用特定的方法才能实现组件之间的数据…

    编程技术 2025年3月8日
    200
  • 解决vue 路由变化页面数据不刷新的问题

    下面我就为大家分享一篇解决vue 路由变化页面数据不刷新的问题,具有很好的参考价值,希望对大家有所帮助。 每天记录一点点,把我遇到的问题记录下来, 希望可以帮助到更多和我遇到同样问题的人。 问题描述:通过调接口,动态显示帮助页面的问题列表,…

    编程技术 2025年3月8日
    200
  • vue移动UI框架滑动加载数据的方法

    这篇文章主要介绍了vue移动ui框架滑动加载的方法,现在分享给大家,也给大家做个参考。 前言 在我们移动端还有一个很常用的组件,那就是滑动加载更多组件。平常我们看到的很多插件实现相当复杂就觉得这个组件很难,其实不是的!!这个组件其实可以很简…

    2025年3月8日
    200

发表回复

登录后才能评论