Vue 源码
你想要的 vue 源码分析 · 31
数据驱动 DOM,DOM 的变化也会影响数据 input 中 v-model 的原理 input 中的 v-model 如何使用 源码 组件 中 v-model 的原理 组件中的 v-model 如何使用 源码 v-model 的本质 对于 input,v-model="account" 是一个语法糖,通过设置一个 :value="account",并设置一…
数据驱动 DOM,DOM 的变化也会影响数据 input 中 v-model 的原理 input 中的 v-model 如何使用 源码 组件 中 v-model 的原理 组件中的 v-model 如何使用 源码 v-model 的本质 对于 input,v-model="account" 是一个语法糖,通过设置一个 :value="account",并设置一…
数据驱动 DOM,DOM 的变化也会影响数据
let vue = new Vue({
el: "#app",
template: `<div><input v-model="account" placeholder="placeholder">{{ account }}</div>`,
data() {
return {
account: ""
};
}
});
// 遍历el.directives,获取每一个指令对应的方法,同时根据指令生成一些data数据
// src/compiler/codegen/index.js
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;
// 遍历el.directives
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i];
needRuntime = true;
// 获取每一个指令对应的方法
const gen: DirectiveFunction = state.directives[dir.name];
if (gen) {
// 根据ast元素节点的不同去执行model函数
needRuntime = !!gen(el, dir, state.warn);
}
// 根据指令生成一些data数据
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.arg}"` : ""}${dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ""}},`;
}
}
if (hasRuntime) {
return res.slice(0, -1) + "]";
}
}
// 遍历el.directives的时候需要的参数
// src/platforms/web/compiler/options.js
export const baseOptions: CompilerOptions = {
expectHTML: true,
modules,
directives,
isPreTag,
isUnaryTag,
mustUseProp,
canBeLeftOpenTag,
isReservedTag,
getTagNamespace,
staticKeys: genStaticKeys(modules)
};
// v-model对应的directive,根据ast元素节点的不同去执行model函数
// src/platforms/web/compiler/directives/model.js
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") {
// input用v-model是执行到这
genDefaultModel(el, value, modifiers);
} else if (!config.isReservedTag(tag)) {
// 根据v-model的修饰符做不同的处理,修饰符:lazy,number,trim
// 处理完成,生成代码
// 组件中用的v-model是执行到这块
genComponentModel(el, value, modifiers);
// component v-model doesn't need extra runtime
return false;
} else if (process.env.NODE_ENV !== "production") {
// ...
}
// ensure runtime directive metadata
return true;
}
// 根据v-model的修饰符做不同的处理,修饰符:lazy,number,trim
// 处理完成,生成代码
// src/platforms/web/compiler/directives/model.js
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})`;
}
// 处理完成,生成代码
// code是account=$event.target.value
let code = genAssignmentCode(value, valueExpression);
// 又因为v-model的修饰符没有lazy
// code是if($event.target.composing)return;account=$event.target.value
if (needCompositionGuard) {
code = `if($event.target.composing)return;${code}`;
}
// 修改ast元素,给el添加一个prop,prop的值是account,又添加了一个自定义事件,自定义事件是input
// 最终为:<input v-bind:value="account" v-on:input="account=$event.target.value">
// 对于input来说,有一个prop是account,当触发@input事件,就重写account,这个account的值就是input的value
addProp(el, "value", `(${value})`);
addHandler(el, event, code, null, true);
if (trim || number) {
addHandler(el, "blur", "$forceUpdate()");
}
}
// 对v-model的value进行解析,当前例子中,v-model=account res.key为null,所以${value}=${assignment}
// src/compiler/directives/model.js
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})`;
}
}
// 最终生成的render伪代码是
with (this) {
return _c("div", [
_c("input", {
directives: [
{
name: "model",
rawName: "v-model",
value: account,
expression: "account"
}
],
attrs: { placeholder: "placeholder" },
domProps: { value: account },
on: {
input: function($event) {
if ($event.target.composing) return;
account = $event.target.value;
}
}
}),
_s(account)
]);
}
let Child = {
template: `<div><input :value="value" @input="updateValue" placeholder="placeholder"></div>`,
props: ["value"],
methods: {
updateValue(e) {
this.$emit("input", e.target.value);
}
}
};
let vue = new Vue({
el: "#app",
template: `<div>'<child v-model="account"></child>{{ account }}</div>`,
data() {
return {
account: ""
};
},
components: {
Child
}
});
// 生成伪代码
// src/compiler/directives/model.js
export function genComponentModel(el: ASTElement, value: string, modifiers: ?ASTModifiers): ?boolean {
const { number, trim } = modifiers || {};
const baseValueExpression = "$$v";
let valueExpression = baseValueExpression;
if (trim) {
valueExpression = `(typeof ${baseValueExpression} === 'string'` + `? ${baseValueExpression}.trim()` + `: ${baseValueExpression})`;
}
if (number) {
valueExpression = `_n(${valueExpression})`;
}
const assignment = genAssignmentCode(value, valueExpression);
el.model = {
value: `(${value})`,
expression: `"${value}"`,
callback: `function (${baseValueExpression}) {${assignment}}`
};
}
// 最终生成的render伪代码是
with (this) {
return _c(
"div",
[
_c("child", {
model: {
value: account,
callback: function($$v) {
account = $$v;
},
expression: "account"
}
}),
_s(account))
],
1
);
}
// 创建子组件vnode阶段,会执行createComponent函数,给data.props添加参数data.model.value,给data.on添加data.model.callback
// src/core/vdom/create-component.js
export function createComponent(Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string): VNode | Array<VNode> | void {
// ...
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data);
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag);
// ...
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on;
// ...
const vnode = new VNode(`vue-component-${Ctor.cid}${name ? `-${name}` : ""}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory);
return vnode;
}
// 最终为
data.props = {
value: account
};
data.on = {
input: function($$v) {
account = $$v;
}
};
// 可见,子组件传入的value,绑定在父组件的account中,
// 父组件同时又监听自定义事件input变化,子组件input变化会派发事件,父组件account就会重新赋值,子到父
// 如果父组件account变化,传入子组件的value也会变化,value变化,触发子组件的input事件,所以又子组件派发事件,父组件的account又变化,父到子
// 父组件通过prop把数据传入子组件,子组件修改数据,通过$emit通知父组件
let vue = new Vue({
el: "#app",
template: `<div><child :value="account" @input="account=arguments[0]"></child> {{ account }} </div>`,
data() {
return {
account: ""
};
},
components: {
Child
}
});
- 对于 input,v-model="account" 是一个语法糖,通过设置一个 :value="account",并设置一个 input 事件,在触发 input 的时候,(
:value="account" @input="account=\$event.target.value"),重写 account="$event.target.value",所以 account 变,dom 变,dom 变,account 变。- 对于组件来说,v-model="account" 也是一个语法糖,子组件接收一个 :value="value",并设置一个 input 事件,在触发 input 事件的时候,$emit一个事件,传入$event.target.value,(
:value="value" @input="this.$emit("input", e.target.value)"),父组件在监听$emit 的事件的时候,会把传入的值赋给一个 account,account 在父组件定义,account 又作为 v-bind 传入子组件,(:value="account" @input="account=arguments[0]"),所以子变->父变 父变->子变
暂无评论
确认操作
请再次确认编辑图片
待上传图片 (剩余 0 张)
项目详情
确认评论邮箱
邀请制评论