1. 程式人生 > 程式設計 >帶你理解vue中的v-bind

帶你理解vue中的v-bind

目錄
  • 一、v-bind關鍵原始碼分析
  • 1、v-bind化的屬性統一儲存在哪裡:attrsMap與attrsList
    • 2、解析HTML,解析出屬性集合attrs,在start回撥中返回
    • 3、在start回撥中建立ASTElement,createASTElement(...,attrs,...)
    • 4、建立後ASTElement會生成attrsList和attrsMap
    • 5、attrs的資料型別定義
    • 6、繫結屬性獲取函式
  • 二、如何獲取v-bind的值
    • 1、v-bind:key原始碼分析
    • 2、v-bind:title原始碼分析
    • 3、v-bind:class原始碼分析
    • 4、、v-bind:style原始碼分析
    • 5、v-bind:text-content.prop原始碼分析
    • 6、v-bind的修飾符.camel .sync原始碼分析

一、v-bind關鍵原始碼分析

1、v-bind化的屬性統一儲存在哪裡:attrsMap與attrsList

<p v-bind:title="vBindTitle"></p>

假設為p標籤v-bind化了title屬性,我們來分析title屬性在中是如何被處理的。

vue在拿到這個html標籤之後,處理title屬性,會做以下幾步:

  • 解析HTML,解析出屬性集合attrs,在start回撥中返回
  • 在start回撥中建立ASTElement,createASTElement(...,...)
  • 建立後ASTElement會生成attrsListattrsMap

至於建立之後是如何處理v-bind:title這種普通的屬性值的,可以在下文的v-bind:src原始碼分析中一探究竟。

2、解析HTML,解析出屬性集合attrs,在start回撥中返回

 
  function handleStartTag (match) {
    ...
    const l = match.attrs.length
    const attrs = new Array(l)
    for (let i = 0; i < l; i++) {
      const args = match.attrs[i]
      .
.. attrs[i] = { name: args[1],value: decodeAttr(value,shouldDecodeNewlines) } } ... if (options.start) { // 在這裡上傳到start函式 options.start(tagName,unary,match.start,match.end) } }

3、在start回撥中建立ASTElement,createASTElement(...,...)

// 解析HMTL
parseHTML(template,{
    ...
    start(tag,start,end) {
        let element: ASTElement = createASTElement(tag,currentParent) // 注意此處的attrs
    }
})

4、建立後ASTElement會生成attrsList和attrsMap

// 建立AST元素
export function createASTElement (
  tag: string,attrs: Array<ASTAttr>,// 屬性物件陣列
  parent: ASTElement | void // 父元素也是ASTElement
): ASTElement { // 返回的也是ASTElement
  return {
    type: 1,tag,attrsList: attrs,attrsMap: makeAttrsMap(attrs),rawAttrsMap: {},parent,children: []
  }
}

5、attrs的資料型別定義

// 宣告一個ASTAttr 屬性抽象語法樹物件 資料型別
declare type ASTAttr = {
  name: string; // 屬性名
  value: any; // 屬性值
  dynamic?: boolean; // 是否是動態屬性
  start?: number;
  end?: number
};

6、繫結屬性獲取函式

繫結屬性獲取函式getBindingAttr 和 屬性操作函式 getAndRemoveAttr

getBindingAttr及其子函式getAndRemoveAttr在處理特定場景下的v-bind十分有用,也就是”v-bind如何處理不同的繫結屬性“章節很有用。 這裡將其列舉出來供下文v-bind:key原始碼分析;v-bind:src原始碼分析;v-bind:class原始碼分析;v-bind:style原始碼分析;v-bind:dataset.prop原始碼分析原始碼分析參照。

export function getBindingAttr (
  el: ASTElement,name: string,getStatic?: boolean
): ?string {
  const dynamicValue =
    getAndRemoveAttr(el,':' + name) ||
    getAndRemoveAttr(el,'v-bind:' + name)
  if (dynamicValue != null) {
    return parseFilters(dynamicValue)
  } else if (getStatic !== false) {
    const staticValue = getAndRemoveAttr(el,name)
    if (staticValue != null) {
      return ON.stringify(staticValue)
    }
  }
}

// note: this only removes the attr from the Array (attrsList) so that it
// doesn't get processed by processAttrs.
// By default it does NOT remove it from the map (attrsMap) because the map is
// needed during codegen.
export function getAndRemoveAttr (
  el: ASTElement,removeFromMap?: boolean
): ?string {
  let val
  if ((val = el.attrsMap[name]) != null) {
    const list = el.attrsList
    for (let i = 0,l = list.length; i < l; i++) {
      if (list[i].namellZjBmDF === name) {
        list.splice(i,1) // 從attrsList刪除一個屬性,不會從attrsMap刪除
        break
      }
    }
  }
  if (removeFromMap) {
    delete el.attrsMap[name]
  }
  return val
}

二、如何獲取v-bind的值

以下面程式碼為例從原始碼分析vue是如何獲取v-bind的值。

會從記下幾個場景去分析:

  • 常見的key屬性
  • 繫結一個普通html attribute:title
  • 繫結classstyle
  • 繫結一個html DOM property:textContent
vBind:{
    key: +new Date(),title: "This is a HTML attribute v-bind",class: "{ borderRadius: isBorderRadius }"
    style: "{ minHeight: 100 + 'px',maxHeight}"
    text-content: "hello vue v-bind"
}

<div
   v-bind:key="vBind.key"
   v-bind:title="vBind.title"
   v-bind:class="vBind.class"
   v-bind:style="vBind.style"
   v-bind:text-content.prop="vBind.textContent"
 />
</div>

1、v-bind:key原始碼分析

function processKey (el) {
  const exp = getBindingAttr(el,'key')
   if(exp){
      ...
      el.key = exp;
   }
}


processKey函式中用到了getBindingAttr函式,由於我們用的是v-bind,沒有用:,所以const dynamicValue = getAndRemoveAttr(el,'v-bind:'+'key');,getAndRemoveAttr(el,'v-bind:key')函式到attrsMap中判斷是否存在 'v-bind:key',取這個屬性的值賦為val並從從attrsList刪除,但是不會從attrsMap刪除,最後將 'v-bind:key'的值,也就是val作為dynamicValue,之後再返回解析過濾後的結果,最後將結果setprocessKey中將元素的key property。然後儲存在segments中,至於segments是什麼,在上面的原始碼中可以看到。

2、v-bind:title原始碼分析

title是一種“非vue特殊的”也就是普通的HTML attribute

function processAttrs(el){
     const list = el.attrsList;
     ...
     if (bindRE.test(name)) { // v-bind
        name = name.replace(bindRE,'')
        value = parseFilters(value)
        ...
        addAttr(el,name,value,list[i],...)
      }
}
export const bindRE = /^:|^\.|^v-bind:/
export function addAttr (el: ASTElement,value: any,range?: Range,dynamic?: boolean) {
  const attrs = dynamic
    ? (el.dynamicAttrs || (el.dynamicAttrs = []))
    : (el.attrs || (el.attrs = []))
  attrs.push(rangeSetItem({ name,dynamic },range))
  el.plain = false
}

通過閱讀原始碼我們看出:對於原生的屬性,比如title這樣的屬性,vue會首先解析出namevalue,然後再進行一系列的是否有modifiers的判斷(modifier的部分在下文中會詳細講解),最終向更新ASTElementattrs,從而attrsListattrsMap也同步更新。

3、v-bind:class原始碼分析

class在前端開發的展現層面,是非常重要的一層。 因此vue在對於class屬性也做了很多特殊的處理。

function transformNode (el: ASTElement,options: CompilerOptions) {
  const warn = options.warn || baseWarn
  const staticClass = getAndRemoveAttr(el,'class')
  if (staticClass) {
    el.staticClass = JSON.stringify(staticClass)
  }
  const classBinding = getBindingAttr(el,'class',false /* getStatic */)
  if (classBinding) {
    el.classBinding = classBinding
  }
}

transfromNode函式中,會通過getAndRemoveAttr得到靜態class,也就是class="foo";在getBindingAttr得到繫結的class,也就是v-bind:class="vBind.class"即v-bind:class="{ borderRadius: isBorderRadius }",將ASTElement的classBinding賦值為我們繫結的屬性供後續使用。

4、、v-bind:style原始碼分析

style是直接操作樣式的優先順序僅次於important,比class更加直觀的操作樣式的一個HTML attribute。 vue對這個屬性也做了特殊的處理。

function transformNode (el: ASTElement,options: CompilerOptions) {
  collZjBmDFnst warn = options.warn || baseWarn
  const staticStyle = getAndRemoveAttr(el,'style')
  if (staticStyle) {
    el.staticStyle = JSON.stringify(parseStyleText(staticStyle))
  }
  const styleBinding = getBindingAttr(el,'style',false /* getStatic */)
  if (styleBinding) {
    el.styleBinding = styleBinding
  }
}

transfromNode函式中,會通過getAndRemoveAttr得到靜態style,也就是style="{fontSize: '12px'}";在getBindingAttr得到繫結的style,也就是v-bind:style="vBind.style"即v-bind:class={ minHeight: 100 + 'px',maxHeight}",其中maxHeight是一個變數,將ASTElementstyleBinding賦值為我們繫結的屬性供後續使用。

5、v-bind:text-content.prop原始碼分析

textContent是DOM物件的原生屬性,所以可以通過prop進行標識。 如果我們想對某個DOM prop直接通過vue進行set,可以在DOM節點上做修改。

下面我們來看原始碼。

function processAttrs (el) {
  const list = el.attrsList
  ...
  if (bindRE.test(name)) { // v-bind
      if (modifiers) {
          if (modifiers.prop && !isDynamic) {
            name = camelize(name)
            if (name === 'innerHtml') name = 'innerHTML'
          }
       }
       if (modifiers && modifiers.prop) {
          addProp(el,isDynamic)
        }
   }
}
export function addProp (el: ASTElement,value: string,dynamic?: boolean) {
  (el.props || (el.props = [])).push(rangeSetItem({ name,range))
  el.plain = false
}
props?: Array<ASTAttr>;

通過上面的原始碼我們可以看出,v-bind:text-content.prop中的text-content首先被駝峰化為textContent(這是因為DOM property都是駝峰的格式),vue還對innerHtml錯誤寫法做了相容也是有心,之後再通過prop識別符號,將textContent屬性增加到ASTElement的props中,而這裡的props本質上也是一個ASTAttr。

有一個很值得思考的問題:為什麼要這麼做?與HTML attribute有何異同?

  • 沒有HTML attribute可以直接修改DOM的文字內容,所以需要單獨去標識
  • 比通過js去手動更新DOM的文字節點更加快捷,省去了查詢dom然後替換文字內容的步驟
  • 在標籤上即可看到我們對哪個屬性進行了v-bind,非常直觀
  • 其實v-bind:title可以理解為v-bind:title.attr,v-bind:text-content.prop只不過vue默許不加修飾符的就是HTML attribute罷了

6、v-bind的修飾符.camel .sync原始碼分析

.camel僅僅是駝峰化,很簡單。 但是.sync就不是這麼簡單了,它會擴充套件成一個更新父元件繫結值的v-on偵聽器。

其實剛開始看到這個.sync修飾符我是一臉懵逼的,但是仔細閱讀一下元件的.sync再結合實際工作,就會發現它的強大了。

<Parent
  v-bind:foo="parent.foo"
  v-on:updateFoo="parent.foo = $event"
></Parent>

在vue中,父元件向子元件傳遞的props是無法被子元件直接通過this.props.foo = newFoo去修改的。 除非我們在元件this.$emit("updateFoo",newFoo),然後在父元件使用v-on做事件監聽updateFoo事件。若是想要可讀性更好,可以在$emit的name上改為update:foo,然後v-on:update:foo

有沒有一種更加簡潔的寫法呢??? 那就是我們這裡的.sync操作符。 可以簡寫為:

<Parent v-bind:foo.sync="parent.http://www.cppcns.comfoo"></Parent>

然後在子元件通過this.$emit("update:foo",newFoo);去觸發,注意這裡的事件名必須是update:xxx的格式,因為在vue的原始碼中,使用.sync修飾符的屬性,會自定生成一個v-on:update:xxx的監聽。

下面我們來看原始碼:

if (modifiers.camel && !isDynamic) {
  name = camelize(name)
}
if (modifiers.sync) {
  syncGen = genAssignmentCode(value,`$event`)
  if (!isDynamic) {
    addHandler(el,`update:${camelize(name)}`,syncGen,null,false,warn,list[i]) 
   // Hyphenate是連字元化函式,其中camelize是駝峰化函式
    if (hyphenate(name) !== camelize(name)) {
      addHandler(el,`update:${hyphenate(name)}`,list[i])
    }
  } else {
    // handler w/ dynamic event name
    addHandler(el,`"update:"+(${name})`,true)
  }
}

通過閱讀原始碼我們可以看到: 對於v-bind:foo.sync的屬性,vue會判斷屬性是否為動態屬性。 若不是動態屬性,首先為其增加駝峰化後的監聽,然後再為其增加一個連字元的監聽,例如v-bind:foo-bar.sync,首先v-on:update:fooBar,然後v-on:update:foo-bar。v-on監聽是通過addHandler加上的。 若是動態屬性,就不駝峰化也不連字元化了,通過addHandler(el,update:${name},...),老老實實監聽那個動態屬性的事件。

一句話概括.sync: .sync是一個語法糖,簡化v-bind和v-on為v-bind.syncthis.$emit('update:xxx')。為我們提供了一種子元件快捷更新父元件資料的方式。

到此這篇關於帶你理解vue中的v-bind的文章就介紹到這了,更多相關vue中的v-bind內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!