<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當前位置: 首頁 - 科技 - 知識百科 - 正文

        vue操作數據改變更新視圖

        來源:懂視網 責編:小采 時間:2020-11-27 19:50:45
        文檔

        vue操作數據改變更新視圖

        vue操作數據改變更新視圖:這次給大家帶來vue操作數據改變更新視圖,vue操作數據改變更新視圖的注意事項有哪些,下面就是實戰案例,一起來看一下。前記三個月前看了vue源碼來分析如何做到響應式數據的, 文章名字叫vue源碼之響應式數據, 最后分析到, 數據變化后會調用Watcher的u
        推薦度:
        導讀vue操作數據改變更新視圖:這次給大家帶來vue操作數據改變更新視圖,vue操作數據改變更新視圖的注意事項有哪些,下面就是實戰案例,一起來看一下。前記三個月前看了vue源碼來分析如何做到響應式數據的, 文章名字叫vue源碼之響應式數據, 最后分析到, 數據變化后會調用Watcher的u

        這次給大家帶來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件事:

      1. 調用beforeMount鉤子

      2. 建立Watcher

      3. 調用mounted鉤子

      4. (哈哈哈)那么其實核心就是建立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
         }
        }

        這個方法做了:

      5. 根據當前vm的render方法來生成VNode. (render方法可能是根據template或vue文件編譯而來, 所以推論直接寫render方法效率最高)

      6. 如果render方法有問題, 那么首先調用renderError方法, 再不行就讀取上次的vnode或是null.

      7. 如果有父節點就放到自己的.parent屬性里.

      8. 最后返回VNode

      9. 所以核心是這句:

        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的分析

        相信看了本文案例你已經掌握了方法,更多精彩請關注Gxl網其它相關文章!

        推薦閱讀:

        jQuery實現輸入文字超過規定行數時自動添加省略號

        ElTableColumn添加搜索歸納功能

        聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        vue操作數據改變更新視圖

        vue操作數據改變更新視圖:這次給大家帶來vue操作數據改變更新視圖,vue操作數據改變更新視圖的注意事項有哪些,下面就是實戰案例,一起來看一下。前記三個月前看了vue源碼來分析如何做到響應式數據的, 文章名字叫vue源碼之響應式數據, 最后分析到, 數據變化后會調用Watcher的u
        推薦度:
        標簽: 操作 修改 使用
        • 熱門焦點

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 99久9在线|免费| 亚洲高清中文字幕综合网| 国产精品亚洲不卡一区二区三区| 亚洲精品女同中文字幕| ass亚洲**毛茸茸pics| 国产午夜亚洲精品不卡| 国产精品区免费视频| 亚洲精品无码av人在线观看| 亚洲av中文无码乱人伦在线观看| 黄页网址大全免费观看12网站| 亚洲一区二区三区深夜天堂| 久久久久久久岛国免费播放| 免费AA片少妇人AA片直播| 亚洲一区二区三区免费观看| 猫咪社区免费资源在线观看| 亚洲国产精品日韩av不卡在线| 免费看少妇高潮成人片| 波多野结衣久久高清免费 | 亚欧国产一级在线免费| 国产一卡二卡四卡免费| 色在线亚洲视频www| 久久久久久夜精品精品免费啦| 成人免费a级毛片无码网站入口| 欧美日韩国产免费一区二区三区| 免费在线观看黄色毛片| 91av免费在线视频| 亚洲视频免费在线观看| 无码区日韩特区永久免费系列| 亚洲精品中文字幕乱码三区| 久久精品毛片免费观看| www.亚洲成在线| 亚洲 无码 在线 专区| 国产精品免费观看调教网| 亚洲毛片基地4455ww| 亚洲AV永久无码精品一区二区国产| 国产亚洲一区区二区在线| 亚洲av无码专区在线电影| 成年女人午夜毛片免费看| 无码毛片一区二区三区视频免费播放 | 一级毛片在线完整免费观看| 四虎成人免费大片在线|