您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

如何使用vue数据控制视图

2024/3/31 7:06:24发布13次查看
这次给大家带来如何使用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数据控制视图的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product