If you’ve written vUE, you’re familiar with the v-bind directive. I’ll take a look at the principles behind V-Bind at the source level.

Will explore from the following aspects:

  • V-bind key source code analysis
    • Where are v-binded attributes uniformly stored: attrsMap and attrsList
    • The binding property getter function getBindingAttr and property operator function getAndRemoveAttr
  • How does V-bind handle different binding properties
    • V-bind :key source code analysis
    • V-bind :title source code analysis
    • V-bind :class source code analysis
    • V-bind :style source code analysis
    • V-bind: text-content-prop source code analysis
    • V-bind modifiers. Camel. Sync source code analysis

V-bind key source code analysis

Where are v-binded attributes uniformly stored: attrsMap and attrsList

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

Assuming that the p tag V-bind binds the title attribute, let’s examine how the title attribute is handled in vUE.

Vue takes the HTML tag, processes the title attribute, and does the following:

  • Parsing the HTML yields a collection of attributes, attrs, which is returned in the start callback
  • Create ASTElement in the start callback,createASTElement(... ,attrs, ...)
  • ASTElement generates attrsList and attrsMap after creation

The v-bind: SRC source code is used to analyze the generic value of the v-bind:title attribute.

Parsing the HTML yields a collection of attributes, attrs, which is returned in the start callback
  function handleStartTag (match) {... const l = match.attrs.lengthconst 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) {
      // Upload to the start function here
      options.start(tagName, attrs, unary, match.start, match.end)
    }
  }
Copy the code

Create ASTElement in the start callback,createASTElement(... ,attrs, ...)

/ / parsing HTML enthusiast
parseHTML(template, {
    ...
    start(tag, attrs, unary, start, end) {
        let element: ASTElement = createASTElement(tag, attrs, currentParent) // Note attrs here}})Copy the code

ASTElement generates attrsList and attrsMap after creation

// Create the AST element
export function createASTElement (
  tag: string,
  attrs: Array<ASTAttr>, //Parent: attribute object array ASTElement | void//The parent element is also an ASTElement) :ASTElement { // Returns ASTElement as well
  return {
    type: 1,
    tag,
    attrsList: attrs,
    attrsMap: makeAttrsMap(attrs),
    rawAttrsMap: {},
    parent,
    children: []}}Copy the code

Attrs data type definition

// Declare an ASTAttr attribute abstract syntax tree object data type
declare type ASTAttr = {
  name: string; / / the property name
  value: any; / / property valuesdynamic? : boolean;// Whether the attribute is dynamicstart? : number; end? : number };Copy the code

The binding property getter function getBindingAttr and property operator function getAndRemoveAttr

GetBindingAttr and its subfunction getAndRemoveAttr are useful for handling v-bind in specific scenarios, in the section “How V-Bind handles different binding properties.” Here it is listed for the following v-bind:key source code analysis; V-bind: SRC source code analysis; V-bind :class source code analysis; V-bind :style source code analysis; V-bind :dataset. Prop source analysis source analysis reference.

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 JSON.stringify(staticValue)
    }
  }
}
Copy the code
// 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, name: string, 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].name === name) {
        list.splice(i, 1) // Remove an attribute from attrsList, not from attrsMap
        break}}}if (removeFromMap) {
    delete el.attrsMap[name]
  }
  return val
}
Copy the code

How do I get the value of V-bind

Take the following code as an example from the source code analysis vUE is how to obtain the value of V-bind.

Will write down several scenarios to analyze:

  • Common key attributes
  • Bind a normal HTML attribute: title
  • Bind class and style
  • Bind an 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"
}
Copy the code
<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>
Copy the code

V-bind :key source code analysis

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

Const dynamicValue = getAndRemoveAttr(el, ‘v-bind:’+’key’); processKey = getBindingAttr (el, ‘v-bind:’+’key’); , getAndRemoveAttr(el, ‘v-bind:key’) to attrsMap to determine if ‘v-bind:key’ exists, assign this attribute to val and remove it from attrsMap, but not from attrsMap. The value of ‘V-bind :key’ (val) will be used as the dynamicValue, and the result will be returned. Finally, the result will be set to the key property of the element in processKey. The segments are then stored in segments, which are available in the source code.

V-bind :title source code analysis

Title is a “non-VUE specific” HTML attribute.

function processAttrs(el){
     constlist = 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, name: string, value: any, range? : Range, dynamic? : boolean) {
  const attrs = dynamic
    ? (el.dynamicAttrs || (el.dynamicAttrs = []))
    : (el.attrs || (el.attrs = []))
  attrs.push(rangeSetItem({ name, value, dynamic }, range))
  el.plain = false
}
Copy the code

Through reading the source code we see: For native attributes like title, vue first resolves name and value, then makes a series of modifiers checks (more on that below), and finally updates ASTElement’s attrs, AttrsList and attrsMap are also updated synchronously.

V-bind :class source code analysis

CSS class is a very important layer in the presentation of front-end development. So Vue also does a lot of special handling for class attributes.

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
  }
}
Copy the code

In the transfromNode function, we get the static class from getAndRemoveAttr, which is class=”foo”; At getBindingAttr you get the bound class, v-bind:class=”vBind. Class “v-bind:class=” borderRadius: IsBorderRadius}”, assigns ASTElement’s classBinding to our bound property for later use.

V-bind :style source code analysis

Style is an HTML attribute that takes precedence over important and is more intuitive than class. Vue also takes special care of this property.

function transformNode (el: ASTElement, options: CompilerOptions) {
  const 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
  }
}
Copy the code

In the transfromNode function, getAndRemoveAttr gets the static style, that is, style=”{fontSize: ’12px’}”; V-bind :style=”vBind. Style “v-bind:class={minHeight: 100 + ‘px’, maxHeight}” where maxHeight is a variable, assign ASTElement’s styleBinding to our binding property for future use.

V-bind: text-content-prop source code analysis

TextContent is a native property of the DOM object, so it can be identified with prop. If we want to set a DOM prop directly through vUE, we can change it on the DOM node.

Let’s look at the source code.

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, name, value, list[i], isDynamic)
        }
   }
}
export function addProp (el: ASTElement, name: string, value: string, range? : Range, dynamic? : boolean) {
  (el.props || (el.props = [])).push(rangeSetItem({ name, value, dynamic }, range))
  el.plain = false} props? :Array<ASTAttr>;
Copy the code

V-bind: text-content-prop is humped as a textContent (DOM property is humped). Vue also makes compatibility with innerHtml errors by heart, and then adds the textContent property to the Props of the ASTElement, which is essentially an ASTAttr, via the Prop identifier.

It’s a good question to ask: Why? What are the similarities and differences with HTML attributes?

  • No HTML attribute can directly modify the text content of the DOM, so you need to identify it separately
  • It is faster than manually updating the text node of the DOM through JS, eliminating the step of querying the DOM and replacing the text content
  • You can see which attribute we v-bind on the tag, which is pretty straightforward
  • V-bind :titleV - bind: title. Attr, v - bind: text - content. propVue acquiesces in HTML attributes without modifiers

V-bind modifiers. Camel. Sync source code analysis

It’s for camel, it’s for camel. Sync, however, is not so simple and expands into a V-on listener that updates the binding values of the parent component.

I was surprised to see the.sync modifier at first, but a closer look at the component.sync in action will show how powerful it is.

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

In Vue, the props passed by the parent component to the child component cannot be modified by the parent component directly via this.props. Foo = newFoo. Unless we do this.$emit(“updateFoo”, newFoo) in the component and then listen for updateFoo events using V-on in the parent component. For better readability, change the name of $emit to update:foo and then V-on :update:foo.

Is there a more concise way to write it?? That’s our.sync operator here. This can be abbreviated as:

<Parent v-bind:foo.sync="parent.foo"></Parent>
Copy the code

$emit(“update:foo”, newFoo); To trigger, note that the event name must be in the update: XXX format, because in the vue source code, using the. Sync modifier property will automatically generate a V-on :update: XXX listener.

Let’s look at the source code below:

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 is the hyphenated function, where camelize is the humped function
    if(hyphenate(name) ! == camelize(name)) { addHandler(el,`update:${hyphenate(name)}`,syncGen,null.false,warn,list[i])
    }
  } else {
    // handler w/ dynamic event name
    addHandler(el,`"update:"+(${name}) `,syncGen,null.false,warn,list[i],true)}}Copy the code

For the v-bind:foo.sync attribute, vue determines whether the attribute is dynamic. If it is not a dynamic attribute, add a humped listener first and then a hyphenated listener, such as V-bind :foo-bar.sync, v-on:update:fooBar, then V-on :update:foo-bar. V-on listeners are added via addHandler. AddHandler (el,update:${name},…) Listen for events with dynamic attributes.

Sync:.sync is a syntax sugar that simplifies v-bind and V-on to v-bind. Sync and this.$emit(‘update: XXX ‘). It provides a quick way for a subcomponent to update its parent’s data.

References:

  • Cn.vuejs.org/v2/api/#v-b…
  • Github.com/vuejs/vue/t…
  • Cn.vuejs.org/v2/guide/co…