首頁 > 軟體

vue中v-if和v-show使用區別原始碼分析

2022-09-06 18:05:47

高頻面試題:vue中的v-showv-if的區別?

一、v-if

例子:

new Vue({
  el: "#app",
  data() {
    return {
      isShow: false,
    };
  },
  methods: {
    changeStatus() {
      this.isShow = !this.isShow;
    }
  },
  template: `<div><button @click="changeStatus">切換</button><div v-if="isShow">顯示</div></div>`
});

1、render

`with(this){
    return _c('div',[_c('button',{on:{"click":changeStatus}},[_v("切換")]),(isShow)?_c('div',[_v("顯示")]):_e()])
}`

可以看出,這裡通過isShow為三目運運算元的判斷條件,起始條件下其值為false

2、vNode

獲取到的vNodev-if條件為false的情況下,獲取到的是空的註釋節點用來佔位,包含屬性isComment: truetext: ""

3、patch

當前例子中,v-iffalsepatch的過程中執行到:

else if (isTrue(vnode.isComment)) {
  vnode.elm = nodeOps.createComment(vnode.text);
  insert(parentElm, vnode.elm, refElm);
}

通過nodeOps中的方法建立註釋空節點,並插入到父元素中,最終執行結果為:

小結

v-if的情況下,如果起始為false,只會生成空的註釋節點用來佔位,在需要考慮白屏場景下,使用v-if比較合適。

二、v-show

例子:

new Vue({
  el: "#app",
  data() {
    return {
      isShow: false,
    };
  },
  methods: {
    changeStatus() {
      this.isShow = !this.isShow;
    }
  },
  template: `<div><button @click="changeStatus">切換</button><div v-show="isShow">顯示</div></div>`
});

1、render

`with(this){
    return _c('div',[_c('button',{on:{"click":changeStatus}},[_v("切換")]),_c('div',{directives:[{name:"show",rawName:"v-show",value:(isShow),expression:"isShow"}]},[_v("顯示")])])
}`

可以看出,這裡與v-if不同的是,裡面有directives屬性。

2、vNode

v-if不同的是,這裡包含用於描述vNode屬性的data

data: {
    directives: {
        expression: "isShow",
        name: "show",
        rawName: "v-show",
        value: false,
    }
}

3、patch

在當前例子中v-show控制的節點會執行到createElm方法中的以下邏輯:

  {
    createChildren(vnode, children, insertedVnodeQueue);
    if (isDef(data)) {
      invokeCreateHooks(vnode, insertedVnodeQueue);
    }
    insert(parentElm, vnode.elm, refElm);
  }

當執行完createChildren(vnode, children, insertedVnodeQueue)vnodeelm中包含outerHTML: "<div>顯示</div>"

data存在,會執行到invokeCreateHooks

function invokeCreateHooks (vnode, insertedVnodeQueue) {
    for (let i = 0; i < cbs.create.length; ++i) {
      cbs.create[i](emptyNode, vnode)
    }
    i = vnode.data.hook // Reuse variable
    if (isDef(i)) {
      if (isDef(i.create)) i.create(emptyNode, vnode)
      if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
    }
}

這裡對data中的directives進行處理的方法是cbs.create中的updateDirectives

function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  if (oldVnode.data.directives || vnode.data.directives) {
    _update(oldVnode, vnode)
  }
}
function _update (oldVnode, vnode) {
  const isCreate = oldVnode === emptyNode
  const isDestroy = vnode === emptyNode
  const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)
  const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
  const dirsWithInsert = []
  const dirsWithPostpatch = []
  let key, oldDir, dir
  for (key in newDirs) {
    oldDir = oldDirs[key]
    dir = newDirs[key]
    if (!oldDir) {
      // new directive, bind
      callHook(dir, 'bind', vnode, oldVnode)
      if (dir.def && dir.def.inserted) {
        dirsWithInsert.push(dir)
      }
    } else {
      // existing directive, update
      dir.oldValue = oldDir.value
      dir.oldArg = oldDir.arg
      callHook(dir, 'update', vnode, oldVnode)
      if (dir.def && dir.def.componentUpdated) {
        dirsWithPostpatch.push(dir)
      }
    }
  }
  // ...
}

這裡主要做了兩件事,通過normalizeDirectives獲取到關於v-show的操作,通過callHook$1(dir, 'bind', vnode, oldVnode)的方式進行屬性的繫結

(1)normalizeDirectives

function normalizeDirectives$1 (
  dirs,
  vm
) {
  var res = Object.create(null);
  if (!dirs) {
    // $flow-disable-line
    return res
  }
  var i, dir;
  for (i = 0; i < dirs.length; i++) {
    dir = dirs[i];
    if (!dir.modifiers) {
      // $flow-disable-line
      dir.modifiers = emptyModifiers;
    }
    res[getRawDirName(dir)] = dir;
    dir.def = resolveAsset(vm.$options, 'directives', dir.name, true);
  }
  // $flow-disable-line
  return res
}
/**
 * Resolve an asset.
 * This function is used because child instances need access
 * to assets defined in its ancestor chain.
 */
function resolveAsset (
  options,
  type,
  id,
  warnMissing
) {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  var assets = options[type];
  // check local registration variations first
  if (hasOwn(assets, id)) { return assets[id] }
  var camelizedId = camelize(id);
  if (hasOwn(assets, camelizedId)) { return assets[camelizedId] }
  var PascalCaseId = capitalize(camelizedId);
  if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] }
  // fallback to prototype chain
  var res = assets[id] || assets[camelizedId] || assets[PascalCaseId];
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    );
  }
  return res
}

這裡通過dir.def = resolveAsset(vm.$options, 'directives', dir.name, true)的方式去解析directives中存在的操作方法,resolveAsset方法中typedirectives,即從Vueoptions中獲得directives的值為一個原型上存在modelshow方法的物件。

那麼這裡有個疑問,這個directives是什麼時候掛載上去的呢?
答案:在原始碼檔案platform/web/runtime/index.js有程式碼extend(Vue.options.directives, platformDirectives),將modelshow進行原型掛載。

通過 var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]我們獲得了show方法:

export default {
  bind (el: any, { value }: VNodeDirective, vnode: VNodeWithData) {
    vnode = locateNode(vnode)
    const transition = vnode.data && vnode.data.transition
    const originalDisplay = el.__vOriginalDisplay =
      el.style.display === 'none' ? '' : el.style.display
    if (value && transition) {
      vnode.data.show = true
      enter(vnode, () => {
        el.style.display = originalDisplay
      })
    } else {
      el.style.display = value ? originalDisplay : 'none'
    }
  },
  // 這裡還有unbind和update方法
}

這裡定義了節點樣式屬性display繫結bind、解綁unbind和更新update的方法。

(2)callHook

當獲取到可執行的showbind方法後再看callHook(dir, 'bind', vnode, oldVnode)

function callHook (dir, hook, vnode, oldVnode, isDestroy) {
  const fn = dir.def && dir.def[hook]
  if (fn) {
    try {
      fn(vnode.elm, dir, vnode, oldVnode, isDestroy)
    } catch (e) {
      handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`)
    }
  }
}

這裡的fn就是show中的bind方法,最終執行到邏輯el.style.display = value ? originalDisplay : 'none',在當前例子中v-show控制的節點elm就有了屬性outerHTML: "<div style="display: none;">顯示</div>"

總結

v-show點選切換成true時將會通過diff演演算法進行本地複用策略的優化,執行到v-show節點控制的節點渲染時節點key相同,採取原地複用的原則只對其屬性display進行修改比從佔位空註釋節點變為真實節點更優,如果在transition這種頻繁切換的場景中,進行v-show控制展示隱藏更合理。

v-ifv-show的使用需要根據場景,一般來說,v-if 有更高的切換開銷,更多的使用在需要考慮白屏時間或者切換次數很少的場景;

而 v-show 有更高的初始渲染開銷但切換開銷較小,因此,如果在transition控制的動畫或者需要非常頻繁地切換場景,則使用 v-show 較好。

以上就是vue中v-if和v-show使用區別原始碼分析的詳細內容,更多關於vue v-if v-show區別的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com