引言
其实对比起来 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: ''
}
}
}