探索Vue.js底层源码——v-modal 语法糖

引言

其实对比起来 Vue 与 React 起来,除了 React 鲜明的函数式的组件、JSX,最大的不同就是 Vue 封装了 v-model 这个语法糖实现了数据的双向绑定。而 v-model 这个语法糖的实现其实也是非常地简单,接下来我们就来认识一下 v-model 在底层中的实现。

v-model

v-model 这个指令,在编译的 parse 阶段与 v-bind、v-on 之类的指令一样会被解析到 AST 树的属性相关的数组中,看起来会是这样:
在这里插入图片描述
然后,在 codegen 阶段,会执行 genDirectives() 方法将 v-model 指令编译为可执行的代码。

function genDirectives (el: ASTElement, state: CodegenState): string | void {
  const dirs = el.directives
  if (!dirs) return
  let res = 'directives:['
  let hasRuntime = false
  let i, l, dir, needRuntime
  for (i = 0, l = dirs.length; i < l; i++) {
    dir = dirs[i]
    needRuntime = true
    // 获取对应指令名称对应的方法
    const gen: DirectiveFunction = state.directives[dir.name]
    ...
  }
  ...
}

而 state.directives 是已经定义好的对象 {model, text, html},而我们这次只走 model 这个方法,对象中的 model 定义在 src/platform/web/compiler/directives 中

export default function model (
  el: ASTElement,
  dir: ASTDirective,
  _warn: Function
): ?boolean {
  warn = _warn
  const value = dir.value
  const modifiers = dir.modifiers
  const tag = el.tag
  const type = el.attrsMap.type
  ...
  if (el.component) {
    genComponentModel(el, value, modifiers)
    // component v-model doesn't need extra runtime
    return false
  } else if (tag === 'select') {
    genSelect(el, value, modifiers)
  } else if (tag === 'input' && type === 'checkbox') {
    genCheckboxModel(el, value, modifiers)
  } else if (tag === 'input' && type === 'radio') {
    genRadioModel(el, value, modifiers)
  } else if (tag === 'input' || tag === 'textarea') {
    genDefaultModel(el, value, modifiers)
  } else if (!config.isReservedTag(tag)) {
    genComponentModel(el, value, modifiers)
    // component v-model doesn't need extra runtime
    return false
  } else if (process.env.NODE_ENV !== 'production') {
    warn(
      `<${el.tag} v-model="${value}">: ` +
      `v-model is not supported on this element type. ` +
      'If you are working with contenteditable, it\'s recommended to ' +
      'wrap a library dedicated for that purpose inside a custom component.',
      el.rawAttrsMap['v-model']
    )
  }

  // ensure runtime directive metadata
  return true
}

可以看到 v-model 有很多逻辑分支,不同的场景使用 v-model 所调用的方法也是不同的,这里我们就以简单的 text input 为例,即此时会走 genDefaultModel 这个方法

function genDefaultModel (
  el: ASTElement,
  value: string,
  modifiers: ?ASTModifiers
): ?boolean {
  const type = el.attrsMap.type
  ...
  const { lazy, number, trim } = modifiers || {}
  const needCompositionGuard = !lazy && type !== 'range'
  const event = lazy
    ? 'change'
    : type === 'range'
      ? RANGE_TOKEN
      : 'input'

  let valueExpression = '$event.target.value'
  if (trim) {
    valueExpression = `$event.target.value.trim()`
  }
  if (number) {
    valueExpression = `_n(${valueExpression})`
  }
  // 一般情况下 这一步骤其实就获取到 `${value}=${assignment}`
  let code = genAssignmentCode(value, valueExpression)
  if (needCompositionGuard) {
    // 最终的 code = `if($event.target.composing)return;${value}=${assignment}`
    code = `if($event.target.composing)return;${code}`
  }
  
  addProp(el, 'value', `(${value})`)
  // 此时的 event 为 input
  addHandler(el, event, code, null, true)
  ...
}

上面的代码核心要做的就是获取到 v-model = “param” 的param值 value,然后再构造 param=$event.target.value,然后再给当前的 el 动态地创建 props 和注册事件名为 input 的事件。现在,我想大家大致有个初步认知对于 v-model,我们简单地举个例子。
例如:

{
	template: '<input type="text" v-model="wrods"></input>',
	data() {
		return {
			words: ''
		}
	}
}

那么经过编译,其实上面这一段代码相当于

{
	template: '<input type="text" v-bind="wrods" @input="param=$event.target.value"></input>',
	props: ['wrods']
	data() {
		return {
			words: ''
		}
	}
}
发布了140 篇原创文章 · 获赞 16 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_42049445/article/details/104211401