v-model数据绑定分析
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",data: {
msg: ""
},template: `
<div>
<div>Message is: {{ msg }}</div>
<input v-model="msg">
</div>
`
})
</script>
</html>
-
input
和textarea
元素使用value property
和input
事件。
-
checkbox
和radio
元素使用checked property
和change
事件。
-
select
元素将value
作为prop
并将change
作为事件。
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",template: `
<div>
<div>Message is: {{ msg }}</div>
<input :value="msg" @input="msg = $event.target.value">
</div>
`
})
</script>
</html>
-
.trim
: 输入首尾空格过滤。
-
.lazy
: 取代input
事件而监听change
事件。
-
.number
: 输入字符串转为有效的数字,如果这个值无法被parseFloat()
解析,则会返回原始的值。
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",data: {
msg: 0
},template: `
<div>
<div>Message is: {{ msg }}</div>
<div>Type is: {{ typeof(msg) }}</div>
<input v-model.number="msg" type="number">
</div>
`
})
</script>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
Vue.component("u-input",{
model: {
prop: "message",event: "input"
},props: {
message: {
type: String
},},template: `
<div>
<input :value="message" @input="$emit('input',$event.target.value)">
</div>
`
})
var vm = new Vue({
el: "#app",template: `
<div>
<div>Message is: {{ msg }}</div>
<u-input v-model="msg"></u-input>
</div>
`
})
</script>
</html>
// dev/src/compiler/index.js line 11
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(),options) // 生成AST
if (options.optimize !== false) {
optimize(ast,options) // 优化AST
}
const code = generate(ast,options) // 生成代码 即render字符串
return {
ast,render: code.render,staticRenderFns: code.staticRenderFns
}
})
// dev/src/compiler/codegen/index.js line 43
export function generate (
ast: ASTElement | void,options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast,state) : '_c("div")'
return {
render: `with(this){return ${code}}`,// render字符串
staticRenderFns: state.staticRenderFns
}
}
// dev/src/compiler/codegen/index.js line 55
export function genElement (el: ASTElement,state: CodegenState): string {
// ...
data = genData(el,state)
// ...
}
// dev/src/compiler/codegen/index.js line 219
export function genData (el: ASTElement,state: CodegenState): string {
// ...
const dirs = genDirectives(el,state)
// ...
}
// dev/src/compiler/codegen/index.js line 309
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] // 对于v-model来说 const gen = state.directives["model"];
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el,state.warn)
}
if (needRuntime) {
hasRuntime = true
res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
}${
dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''
}${
dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
}},`
}
}
if (hasRuntime) {
return res.slice(0,-1) + ']'
}
}
// dev/src/platforms/web/compiler/directives/model.js line 14
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 (process.env.NODE_ENV !== 'production') {
// inputs with type="file" are read only and setting the input's
// value will throw an error.
if (tag === 'input' && type === 'file') {
warn(
`<${el.tag} v-model="${value}" type="file">:\n` +
`File inputs are read only. Use a v-on:change listener instead.`,el.rawAttrsMap['v-model']
)
}
}
// 分支处理
if (el.component) {
genComponentModel(el,value,modifiers)
// component v-model doesn't need extra runtime
return false
} else if (tag === 'select') {
genSelect(el,modifiers)
} else if (tag === 'input' && type === 'checkbox') {
genCheckboxModel(el,modifiers)
} else if (tag === 'input' && type === 'radio') {
genRadioModel(el,modifiers)
} else if (tag === 'input' || tag === 'textarea') {
genDefaultModel(el,modifiers)
} else if (!config.isReservedTag(tag)) {
genComponentModel(el,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
}
// dev/src/platforms/web/compiler/directives/model.js line 127
function genDefaultModel (
el: ASTElement,value: string,modifiers: ?ASTModifiers
): ?boolean {
const type = el.attrsMap.type
// warn if v-bind:value conflicts with v-model
// except for inputs with v-bind:type
// value与v-model冲突则发出警告
if (process.env.NODE_ENV !== 'production') {
const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']
const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']
if (value && !typeBinding) {
const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'
warn(
`${binding}="${value}" conflicts with v-model on the same element ` +
'because the latter already expands to a value binding internally',el.rawAttrsMap[binding]
)
}
}
// 修饰符处理
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})`
}
let code = genAssignmentCode(value,valueExpression)
if (needCompositionGuard) {
code = `if($event.target.composing)return;${code}`
}
addProp(el,'value',`(${value})`)
addHandler(el,event,code,null,true)
if (trim || number) {
addHandler(el,'blur','$forceUpdate()')
}
}
// dev/src/compiler/directives/model.js line 36
export function genAssignmentCode (
value: string,assignment: string
): string {
const res = parseModel(value)
if (res.key === null) {
return `${value}=${assignment}`
} else {
return `$set(${res.exp},${res.key},${assignment})`
}
}
https://github.com/WindrunnerMax/EveryDay
https://cn.vuejs.org/v2/api/#v-model
https://www.jianshu.com/p/19bb4912c62a
https://www.jianshu.com/p/0d089f770ab2
https://cn.vuejs.org/v2/guide/forms.html
https://juejin.im/post/6844903784963899400
https://juejin.im/post/6844903999414485005
https://segmentfault.com/a/1190000021516035
https://segmentfault.com/a/1190000015848976
https://github.com/haizlin/fe-interview/issues/560
https://ustbhuangyi.github.io/vue-analysis/v2/extend/v-model.html