浅曦Vue源码-23-挂载阶段-$mount(11)

这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战

一、前情回顾 & 背景

本篇小作文在讨论 options.start 中的 closeElement 的内部工具方法:processKeyprocessRefcheckInForprocessSlotContent

  1. processKey:检查元素上的 key 属性并检查使用 key 的限制;

  2. processRef:处理 ref 属性,若 refv-for 包裹,则 ref 指向一个数组;

  3. checkInFor:检测当前元素的父辈元素中是否有 v-for 指令;

  4. processSlotContent:处理插槽内容,获取插槽名、动态插槽以及 2.6.x 以后的 v-slot 指令;

二、 processSlotOutlet

方法作位置:src/compiler/parser/index.js -> function processSlotOutlet

方法参数:elast 节点对象

方法作用:解析自闭和的 slot 标签,获取具名插槽的名字,设置到 el 属性上。

function processSlotOutlet (el) {
  if (el.tag === 'slot') {
    el.slotName = getBindingAttr(el, 'name')

    // 提示信息,不要在 slot 标签上使用 key
    if (process.env.NODE_ENV !== 'production' && el.key) {
      warn(
        ``key` does not work on <slot> because slots are abstract outlets ` +
        `and can possibly expand into multiple elements. ` +
        `Use the key on a wrapping element instead.`,
        getRawBindingAttr(el, 'key')
      )
    }
  }
}
复制代码

三、processComponent

方法位置:src/compiler/parser/index.js -> function processComponent

方法参数:elast 节点对象

方法作用:解析动态组件,设置 el.component = is属性绑定的值,如果 component 标签上存在 inline-template 属性,设置 el.inlineTemplate = true

以下面代码为例:

<component :is="compName" inline-template>
  <div>some content,not slot other than compName's template</div>
</component>
复制代码
function processComponent (el) {
  let binding
  // 解析 is 属性,得到属性值,及组件名称,el.component = compName
  if ((binding = getBindingAttr(el, 'is'))) {
    el.component = binding
  }
  
  // 组件上存在 inline-template 属性,进行标记:el.inlineTemplate = true
  // 所谓内联模板就是子组件标签内的内容不作为插槽分发,而是将其中内容作为该组件模板
  if (getAndRemoveAttr(el, 'inline-template') != null) {
    el.inlineTemplate = true
  }
}
复制代码

四、 transforms

transforms 不是一个方法,而是一个方法数组,这个数组是通过 pluckModuleFunction(options.modules, 'transformNode') 得来的,就像前面的 preTransformNode、postTransform 方法;

再来复习一下下,options.modules 来自 createCompiler(baseOpitons)baseOptions 中的 modulesklass,style,model 三项目,pluckModuleFunctions 就是从这三个中挑出指定的方法组成一个数组;

transformNode 方法只有 klassstyle 导出了,model 没有导出;所以 transforms 数组中有两个方法,分别用于处理 astclass 类名和 style 样式;

其中类名处理是为了获取静态类名和动态类名,所谓动态类名就是使用 Vue 动态类名的语法比如:v-bind:class:class 等,style 也是同理;处理过后为 el 新增 el.staticClassel.classBinding 以及 el.staticStyleel.styleBinding

4.1 transformNode <- modules/style.js

方法位置:src/platforms/web/compiler/modules/style.js -> functions transformNode

方法参数:

  1. elast 节点对象
  2. options:createCompiler(baseOptions) 方法接收到的就是了

方法作用:从 el 上解析出静态的 style属性动态绑定的v-bin:style / :style 属性值,分别赋值给el.staticStyleel.styleBinding`

function transformNode (el: ASTElement, options: CompilerOptions) {
  const warn = options.warn || baseWarn

  // 获取 <div style="color:red"></div> 中 coloer: red 
  const staticStyle = getAndRemoveAttr(el, 'style')
  if (staticStyle) {
    
    if (process.env.NODE_ENV !== 'production') {
      const res = parseText(staticStyle, options.delimiters)
      // 提示,如果从 xx 中解析到界定符,说明是一个动态的 style
      // 比如 <div style={{}} > 给出提示,用 :style="val"
      if (res) {
        warn(
          `style="${staticStyle}": ` +
          'Interpolation inside attributes has been removed. ' +
          'Use v-bind or the colon shorthand instead. For example, ' +
          'instead of <div style="{{ val }}">, use <div :style="val">.',
          el.rawAttrsMap['style']
        )
      }
    }
    // 静态 style 样式赋值给 el.staticStyle
    el.staticStyle = JSON.stringify(parseStyleText(staticStyle))
  }

  // 获取动态样式绑定 style 属性,比如 <div :style="val"></div>
  // 赋值给 el.styleBinding
  const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)
  if (styleBinding) {
    el.styleBinding = styleBinding
  }
}
复制代码

4.2 tranfromNode <- modules/class.js

function transformNode (el: ASTElement, options: CompilerOptions) {
  // 日志
  const warn = options.warn || baseWarn

  // <div class="someStaticClassName"></div>
  // 获取元素上的 class 属性的值 someStaticClassName
  const staticClass = getAndRemoveAttr(el, 'class')

  if (process.env.NODE_ENV !== 'production' && staticClass) {
    const res = parseText(staticClass, options.delimiters)
    // 提示,同 style 提示一样,不能用 <div class={{}}></div> 这种花括号语法
    // <div :class="val"></div> 代替
    if (res) {
      warn(
        `class="${staticClass}": ` +
        'Interpolation inside attributes has been removed. ' +
        'Use v-bind or the colon shorthand instead. For example, ' +
        'instead of <div class="{{ val }}">, use <div :class="val">.',
        el.rawAttrsMap['class']
      )
    }
  }

  // 静态 class 属性赋值给 el.staticClass
  if (staticClass) {
    el.staticClass = JSON.stringify(staticClass.replace(/\s+/g, ' ').trim())
  }

  // 获取动态绑定的 class 属性值,并复制给 el.classBinding
  const classBinding = getBindingAttr(el, 'class', false /* getStatic */)
  if (classBinding) {
    el.classBinding = classBinding
  }
}
复制代码

五、processAttrs

方法位置:src/compiler/parser/index.js -> function processAttrs

方法参数:elast 节点对象

方法作用:遍历 attrsList 上处理 el 上的属性,获取每一项的 name,value 分别是详细步骤:

  1. 判断属性是否是指令,如果是:
    • 1.1 设置 el.hasBinding = true;
    • 1.2 解析指令上的修饰符保存到 modifiers 变量,然后将属性上的修饰符部分替换为空字符串;
    • 1.3 用正则检查属性 name 是否使用 v-bind 或 : 动态绑定了值,将属性 name 的动态绑定 v-bind 或 : 替换为空,只留下被绑定的属性本身;接着解析 value 上的过滤器;
    • 1.4 然后用正则匹配指令上是否有动态参数,结果赋值个 isDynamic,所谓动态参数,例如<a v-bind:[attributeName]="url"> ... </a> 方括号中 attrbuteName 就是动态参数,当有动态参数时获取动态参数时获取方括号中的参数名,即 name.slice(1, -1)
    • 1.5 如果修饰符 modifiers 不为空时,若没有动态参数时处理 prop、camel 修饰符;处理 sync 修饰符,sync 修饰符就是个 update:属性名 事件名的语法糖,事件触发时执行 syncGen 代码,这部分代码负责更新数据(赋值或者$set
    • 1.6 如果是 v-on 指令,处理动态参数名,并获取不带 v-on 或者 @ 的事件名,如果是动态绑定参数则获取不含方括号的部分,例如<div v-on:[someDynamicEventName]></div> someDynamicEventName,然后调用 addHandler 重新绑定事件;
    • 1.7 最后是普通指令,获取普通指令名、参数等、判断是否有动态参数,然后调用 addDirective 增加指令:el.directives.push()
  2. 如果不是指令说明就是普通的字面量的属性,接着调用 addAttr()namevalue 设置到 el.dynamicAttrs 或者 el.attrs,这里就可以很清晰明了的发现 attrs 是静态属性,将 el.plain 设为 false;如果不是组件且 name'muted' 且必须使用 prop 绑定属性时调用 addProp 绑定 muted 属性;
function processAttrs (el) {
  const list = el.attrsList
  let i, l, name, rawName, value, modifiers, syncGen, isDynamic
  for (i = 0, l = list.length; i < l; i++) {
    name = rawName = list[i].name
    value = list[i].value
    if (dirRE.test(name)) {
      // 如果是指令
      // mark element as dynamic
      el.hasBindings = true
      // 修饰符
      modifiers = parseModifiers(name.replace(dirRE, ''))
      // 支持 .foo 简写的语法修饰符
      if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
        (modifiers || (modifiers = {})).prop = true
        name = `.` + name.slice(1).replace(modifierRE, '')
      } else if (modifiers) {
        // 不含有修饰符的名字
        name = name.replace(modifierRE, '')
      }
      if (bindRE.test(name)) { // v-bind
        name = name.replace(bindRE, '')
        value = parseFilters(value)
        isDynamic = dynamicArgRE.test(name) // 是否是动态参数
        if (isDynamic) {
          name = name.slice(1, -1)
        }
        if (
          process.env.NODE_ENV !== 'production' &&
          value.trim().length === 0
        ) {
          warn(
            `The value for a v-bind expression cannot be empty. Found in "v-bind:${name}"`
          )
        }
        if (modifiers) {
          if (modifiers.prop && !isDynamic) {
            name = camelize(name)
            if (name === 'innerHtml') name = 'innerHTML'
          }
          if (modifiers.camel && !isDynamic) {
            name = camelize(name)
          }
          if (modifiers.sync) {
            // 处理 sync 修饰符,这就是 sync 修饰符的原理
            syncGen = genAssignmentCode(value, `$event`)
            if (!isDynamic) {
              addHandler(
                el,
                `update:${camelize(name)}`,
                syncGen,
                null,
                false,
                warn,
                list[i]
              )
              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 // dynamic
              )
            }
          }
        }
        if ((modifiers && modifiers.prop) || (
          !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
        )) {
          addProp(el, name, value, list[i], isDynamic)
        } else {
          addAttr(el, name, value, list[i], isDynamic)
        }
      } else if (onRE.test(name)) { // v-on
        name = name.replace(onRE, '')
        isDynamic = dynamicArgRE.test(name)
        if (isDynamic) {
          name = name.slice(1, -1)
        }
        addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
      } else { // normal directives
        name = name.replace(dirRE, '')
        // parse arg
        const argMatch = name.match(argRE)
        let arg = argMatch && argMatch[1]
        isDynamic = false
        if (arg) {
          name = name.slice(0, -(arg.length + 1))
          if (dynamicArgRE.test(arg)) {
            arg = arg.slice(1, -1)
            isDynamic = true
          }
        }
        addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
        if (process.env.NODE_ENV !== 'production' && name === 'model') {
          checkForAliasModel(el, value)
        }
      }
    } else {
      // literal attribute
      if (process.env.NODE_ENV !== 'production') {
        const res = parseText(value, delimiters)
        if (res) {
          warn(
            `${name}="${value}": ` +
            'Interpolation inside attributes has been removed. ' +
            'Use v-bind or the colon shorthand instead. For example, ' +
            'instead of <div id="{{ val }}">, use <div :id="val">.',
            list[i]
          )
        }
      }
      addAttr(el, name, JSON.stringify(value), list[i])
      // #6887 firefox doesn't update muted state if set via attribute
      // even immediately after element creation
      if (!el.component &&
          name === 'muted' &&
          platformMustUseProp(el.tag, el.attrsMap.type, name)) {
        addProp(el, name, 'true', list[i])
      }
    }
  }
}
复制代码

六、总结

本篇小作文继续讨论了 closeElement 的工具方法,大致如下:

  1. processSlotOutlet:解析自闭和的 slot 标签,获取插槽名字设置到 el 上;

  2. processComponent:解析动态组件,解析 is 属性和 inline-template

  3. transformsclass/style 模块导出的 transoformNode 方法处理静态、动态的类名或样式

  4. processAttrs:处理元素上的 attrs,处理指令的参数、事件绑定、修饰符等;

这一篇就是 closeElment 的终结篇,本篇过后 options.start 的所有逻辑也就全部完成了

Guess you like

Origin juejin.im/post/7062909413270487054