Концепция двусторонней привязки данных знакома не всем. Представление влияет на данные, а данные также влияют на представление. Между ними существует двусторонняя зависимость. В первой, средней и второй главах построения адаптивной системы я подробно объяснил принцип того, как данные влияют на представление. И как завершить ассоциацию представлений, влияющих на данные? В этом смысл этого раздела: Директивы
v-model.
из-заv-modelВ соответствии со слотами и событиями, представленными ранее, все они относятся к инструкциям, предоставляемым vue, поэтому мыv-modelАнализ и предыдущий аналогичный способ. Анализ будет составлен вокруг шаблона,renderГенерация функции выполняется в порядке монтирования последнего реального узла. В итоге мы все равно придем к выводу,v-model по сути является синтаксическим сахаром независимо от сценария использования..
11.1 Привязка формы
11.1.1 Основное использование
v-modelОно неотделимо от формы.Причина, по которой представление может влиять на данные, заключается в том, что представление должно быть по сути интерактивным, поэтому форма является необходимым условием для этого взаимодействия. Использование формы начинается с<input > <textarea> <select>Как ядро, более тонкое разделение и комбинацияv-modelиспользуется следующим образом:
// 普通输入框
<input type="text" v-model="value1">
// 多行文本框
<textarea v-model="value2" cols="30" rows="10"></textarea>
// 单选框
<div class="group">
<input type="radio" value="one" v-model="value3"> one
<input type="radio" value="two" v-model="value3"> two
</div>
// 原生单选框的写法 注:原生单选框的写法需要通过name绑定一组单选,两个radio的name属性相同,才能表现为互斥
<div class="group">
<input type="radio" name="number" value="one">one
<input type="radio" name="number" value="two">two
</div>
// 多选框 (原始值: value4: [])
<div class="group">
<input type="checkbox" value="jack" v-model="value4">jack
<input type="checkbox" value="lili" v-model="value4">lili
</div>
// 下拉选项
<select name="" id="" v-model="value5">
<option value="apple">apple</option>
<option value="banana">banana</option>
<option value="bear">bear</option>
</select>
В следующем анализе мы возьмем обычное поле ввода в качестве примера.
<div id="app">
<input type="text" v-model="value1">
</div>
new Vue({
el: '#app',
data() {
return {
value1: ''
}
}
})
Прежде чем вводить текст, просмотрите процесс преобразования шаблона в реальный узел.
-
- Шаблон разбирается на
ASTДерево;
- Шаблон разбирается на
-
-
ASTисполняемый файл генерации дереваrenderфункция;
-
-
-
renderфункция преобразуется вVnodeобъект;
-
-
- согласно с
VnodeОбъекты генерируют реальныеDomузел.
- согласно с
Далее, давайте посмотрим на парсинг шаблона какASTДерево процесс.
11.1.2 Разбор деревьев AST
Этап компиляции шаблона вызоветvar ast = parse(template.trim(), options)генерироватьASTДерево,parseОстальные детали функции здесь не анализируются, предыдущие статьи были более или менее освещены, мы по-прежнему сосредоточимся на анализе атрибутов шаблона, т.е.processAttrsфункция.
использовалvueЛюбой, кто пишет шаблоны, знает, чтоvueАтрибут шаблона состоит из двух частей, одна из которых является директивой, а другая — обычнымhtmlСвойства ярлыка. z Это также две основные ветви обработки атрибутов. В подразделе поле инструкции,v-on,v-bindВыполните специальную обработку, другие обычные ветки будут выполненыaddDirectiveОбработать.
// 处理模板属性
function processAttrs(el) {
var list = el.attrsList;
var i, l, name, rawName, value, modifiers, syncGen, isDynamic;
for (i = 0, l = list.length; i < l; i++) {
name = rawName = list[i].name; // v-on:click
value = list[i].value; // doThis
if (dirRE.test(name)) { // 1.针对指令的属性处理
···
if (bindRE.test(name)) { // v-bind分支
···
} else if(onRE.test(name)) { // v-on分支
···
} else { // 除了v-bind,v-on之外的普通指令
···
// 普通指令会在AST树上添加directives属性
addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]);
if (name === 'model') {
checkForAliasModel(el, value);
}
}
} else {
// 2. 普通html标签属性
}
}
}
существуетУглубленный анализ исходного кода Vue — демистификация механизма событий VueВ этом разделе мы знакомимASTСоздание инструкций от фазы к событиюv-onобработка предназначена дляASTдерево добавитьeventsАтрибуты. Точно так же обычные команды будутASTдобавить на деревоdirectivesсвойства см.addDirectiveфункция.
// 添加directives属性
function addDirective (el,name,rawName,value,arg,isDynamicArg,modifiers,range) {
(el.directives || (el.directives = [])).push(rangeSetItem({
name: name,
rawName: rawName,
value: value,
arg: arg,
isDynamicArg: isDynamicArg,
modifiers: modifiers
}, range));
el.plain = false;
}
наконецASTДерево больше, чем свойство объекта, которыйmodifiersПредставляет модификаторы, добавленные в шаблон, например:.lazy, .number, .trim.
// AST
{
directives: {
{
rawName: 'v-model',
value: 'value',
name: 'v-model',
modifiers: undefined
}
}
}
11.1.3 Генерация функции визуализации
renderЭтап генерации функции, то есть предыдущий анализ несколько разgenerateлогика, котораяgenDataОн обработает множество атрибутов шаблона и, наконец, вернет шаблон склеенной строки, и начнется обработка инструкции.genDirectivesобработать.
function genData(el, state) {
var data = '{';
// 指令的处理
var dirs = genDirectives(el, state);
··· // 其他属性,指令的处理
// 针对组件的v-model处理,放到后面分析
if (el.model) {
data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
}
return data
}
genDirectivesЛогика не сложная, он доберется доASTсохранился в деревеdirectivesобъект, и пройти по объекту инструкции синтаксического анализа, и, наконец, закончить с'directives:['Обернутая строка возвращается.
// directives render字符串的生成
function genDirectives (el, state) {
// 拿到指令对象
var dirs = el.directives;
if (!dirs) { return }
// 字符串拼接
var res = 'directives:[';
var hasRuntime = false;
var i, l, dir, needRuntime;
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i];
needRuntime = true;
// 对指令ast树的重新处理
var gen = state.directives[dir.name];
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir, 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) + ']'
}
}
Вот ключевой фрагмент кодаvar gen = state.directives[dir.name], Чтобы понять его тонкости, давайте вернемся к процессу компиляции в исходном коде Vue.В предыдущих статьях мы полностью представилиtemplateШаблон процесса компиляции, эта часть дизайна очень сложная и умная, в которой широко используется идея частичных функций, а именно разделение разных платформ в разных процессах компиляции, но также для одной и той же платформы каждый раз предоставляются одни и те же параметры конфигурации. обработка слияния и хорошая конфигурация кэша. Для чего у браузера есть три важных параметра команды.
var directive$1 = {
model: model,
text: text,
html, html
}
var baseOptions = {
···
// 指令选项
directives: directives$1,
};
// 编译时传入选项配置
createCompiler(baseOptions)
и этоstate.directives['model']то есть соответствующийmodelфункции, поэтому давайте сосредоточимся наmodelЛогика функции.
function model (el,dir,_warn) {
warn$1 = _warn;
// 绑定的值
var value = dir.value;
var modifiers = dir.modifiers;
var tag = el.tag;
var type = el.attrsMap.type;
{
// 这里遇到type是file的html,如果还使用双向绑定会报出警告。
// 因为File inputs是只读的
if (tag === 'input' && type === 'file') {
warn$1(
"<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" +
"File inputs are read only. Use a v-on:change listener instead.",
el.rawAttrsMap['v-model']
);
}
}
//组件上v-model的处理
if (el.component) {
genComponentModel(el, value, modifiers);
// component v-model doesn't need extra runtime
return false
} else if (tag === 'select') {
// select表单
genSelect(el, value, modifiers);
} else if (tag === 'input' && type === 'checkbox') {
// checkbox表单
genCheckboxModel(el, value, modifiers);
} else if (tag === 'input' && type === 'radio') {
// radio表单
genRadioModel(el, value, modifiers);
} else if (tag === 'input' || tag === 'textarea') {
// 普通input,如 text, 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 {
// 如果不是表单使用v-model,同样会报出警告,双向绑定只针对表单控件。
warn$1(
"<" + (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
}
Очевидно,modelдля элементов управления формыASTДерево для дальнейшей обработки, в приведенном выше базовом использовании мы знаемСуществуют разные типы форм, и механизм ответа обработки событий, соответствующий каждому типу, также отличается.. Следовательно, нам нужно создавать разные формы для разных элементов управления формой.renderфункции, поэтому необходимо производить различныеASTАтрибуты.modelСуществуют разные ветки обработки для разных типов элементов управления формы. Мы сосредоточимся на анализе обычныхinputРабота с этикетками,genDefaultModelВетви, другие типы ветвей, могут быть смоделированы после процесса анализа, описанного ниже.
function genDefaultModel (el,value,modifiers) {
var type = el.attrsMap.type;
// v-model和v-bind值相同值,有冲突会报错
{
var value$1 = el.attrsMap['v-bind:value'] || el.attrsMap[':value'];
var typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type'];
if (value$1 && !typeBinding) {
var binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value';
warn$1(
binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " +
'because the latter already expands to a value binding internally',
el.rawAttrsMap[binding]
);
}
}
// modifiers存贮的是v-model的修饰符。
var ref = modifiers || {};
// lazy,trim,number是可供v-model使用的修饰符
var lazy = ref.lazy;
var number = ref.number;
var trim = ref.trim;
var needCompositionGuard = !lazy && type !== 'range';
// lazy修饰符将触发同步的事件从input改为change
var event = lazy ? 'change' : type === 'range' ? RANGE_TOKEN : 'input';
var valueExpression = '$event.target.value';
// 过滤用户输入的首尾空白符
if (trim) {
valueExpression = "$event.target.value.trim()";
}
// 将用户输入转为数值类型
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
// genAssignmentCode函数是为了处理v-model的格式,允许使用以下的形式: v-model="a.b" v-model="a[b]"
var code = genAssignmentCode(value, valueExpression);
if (needCompositionGuard) {
// 保证了不会在输入法组合文字过程中得到更新
code = "if($event.target.composing)return;" + code;
}
// 添加value属性
addProp(el, 'value', ("(" + value + ")"));
// 绑定事件
addHandler(el, event, code, null, true);
if (trim || number) {
addHandler(el, 'blur', '$forceUpdate()');
}
}
function genAssignmentCode (value,assignment) {
// 处理v-model的格式,v-model="a.b" v-model="a[b]"
var res = parseModel(value);
if (res.key === null) {
// 普通情形
return (value + "=" + assignment)
} else {
// 对象形式
return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
}
}
genDefaultModelЛогика состоит из двух частей,Одна часть предназначена для создания различных строк обработки событий для модификаторов, а другая — для создания различных строк обработки событий для модификаторов.v-modelпроизведеноASTдерево добавления свойств и свойств, связанных с событиями. Две самые важные строки кода:
// 添加value属性
addProp(el, 'value', ("(" + value + ")"));
// 绑定事件属性
addHandler(el, event, code, null, true);
addHandlerВ представлении проанализированы до события, он предоставитASTдерево для добавления свойств, связанных с событиями, то же самоеaddPropтакже дляASTдерево добавитьpropsАтрибуты. наконецASTВ дерево добавлены два новых свойства:
назадgenData,пройти черезgenDirectivesПосле обработки оригиналASTВ дерево добавлены два новых свойства, поэтому их также необходимо обработать на этапе генерации строки.propsа такжеeventsветвь.
function genData$2 (el, state) {
var data = '{';
// 已经分析过的genDirectives
var dirs = genDirectives(el, state);
// 处理props
if (el.props) {
data += "domProps:" + (genProps(el.props)) + ",";
}
// 处理事件
if (el.events) {
data += (genHandlers(el.events, false)) + ",";
}
}
наконецrenderРезультат функции:
"_c('input',{directives:[{name:"model",rawName:"v-model",value:(message),expression:"message"}],attrs:{"type":"text"},domProps:{"value":(message)},on:{"input":function($event){if($event.target.composing)return;message=$event.target.value}}})"
<input type="text" v-model="value">Если вы думаете о приведенном выше анализе процесса, вы можете непосредственно увидеть следующие выводы, шаблоны сравнения и сгенерированныеrenderфункции, мы можем получить:
-
-
inputВсе атрибуты тега, включая содержимое, связанное с инструкциями, начинаются сdataФорма атрибута передается как весь параметр_c(即:createElement)функция.
-
-
-
input typeвведитеdataсвойства, сattrsПара ключ-значение существует.
-
-
-
v-modelбудет соответствующийdirectivesАтрибуты описывают информацию о директиве.
-
-
- зачем говорить
v-modelявляется синтаксическим сахаром изrenderВидно, что конечный результат функции состоит из двух частей.inputэтикетка, нужноvalue1кpropsФорма существованияdomProps), другой хранится в виде событийinputсобытия и оставаться вonв свойствах.
- зачем говорить
-
- Важный ключ, события используют
$event.target.composingатрибут, чтобы гарантировать, что данные не будут обновляться в процессе объединения текста в методе ввода, о чем мы еще упомянем позже.
- Важный ключ, события используют
11.1.4 исправление реальных узлов
существуетpatchБыло предыдущее поколениеvnodeпроцесс, в этом процессе нет ничего особенного, все включая директивы, атрибуты будут начинаться сdataФорма свойства передается конструкторуVnodeсредний, финальныйVnodeимеютdirectives,domProps,onАтрибуты:
имеютVnodeбудет выполнено сразу послеpatchVnode,patchVnodeПроцесс представляет собой процесс создания реального узла, где ключcreateElmметод, мы также анализировали этот метод в разных случаях, и информация, связанная с командой, полученная из предыдущего исходного кода, также будет сохранена вvnodeизdataатрибут, поэтому обработка атрибутов тоже пойдетinvokeCreateHooksлогика.
function createElm() {
···
// 针对指令的处理
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
}
invokeCreateHooksБудет вызывать определенную функцию ловушки, правильноvnodeАтрибуты, инструкции, события и т. Д. Определены выше, обрабатываются реальным домом. Шаги включают следующее (не все включено):
-
-
updateDOMPropsбуду использоватьvnode dataВверхdomPropsвозобновитьinputпомеченvalueстоимость;
-
-
-
updateAttrsбуду использоватьvnode dataВверхattrsАтрибут обновляет значение атрибута узла;
-
-
-
updateDomListenersиспользоватьvnode dataВверхonсвойство для добавления прослушивателей событий.
-
следовательноv-modelРезультатом окончательного ответа синтаксического сахара является отслеживание самого контроля формы.inputСобытия (другие типы имеют другие типы событий прослушивания), чтобы влиять на свои собственныеvalueстоимость. если нетv-modelсинтаксический сахар, мы можем написать:<input type="text" :value="message" @input="(e) => { this.message = e.target.value }" >
11.1.5 За синтаксическим сахаром
Однакоv-modelТолько ли с целью слияния грамматик, создания нового синтаксического сахара?**Очевидно, что нет, для языков, требующих метода ввода (таких как китайский, японский, корейский и т. д.), вы найдетеv-modelНе будет обновляться во время ввода текста композиции метода. **Этоv-modelважная особенность ж. Он добавит новый прослушиватель событий на уровне обработки событий.compositionstart,compositionend, они будут прослушивать изменения в начале и в конце языкового ввода, соответственно, до тех пор, пока$event.target.composing, он может быть разработан для обновления данных только в конце текста комбинации методов ввода, что полезно для улучшения взаимодействия с пользователем. Эту часть я хочу помочь понять с помощью внерамочных форм.
Реализация просмотра данных ответа вне кадра (эффект аналогичен v-model):
// html
<input type="text" id="inputValue">
<span id="showValue"></span>
// js
<script>
let input = document.getElementById('inputValue');
let show = document.getElementById('showValue');
input.value = 123;
show.innerText = input.value
function onCompositionStart(e) {
e.target.composing = true;
}
function onCompositionEnd(e) {
if (!e.target.composing) {
return
}
e.target.composing = false;
show.innerText = e.target.value
}
function onInputChange(e) {
// e.target.composing表示是否还在输入中
if(e.target.composing)return;
show.innerText = e.target.value
}
input.addEventListener('input', onInputChange)
input.addEventListener('compositionstart', onCompositionStart)// 组合输入开始
input.addEventListener('compositionend', onCompositionEnd) // 组合输入结束
</script>
11.2 Использование компонентовv-model
Наконец, давайте кратко поговорим об использовании его в родительском компоненте.v-model, вы можете сначала посмотреть заключение,используемые компонентыv-modelПо сути, синтаксический сахар для связи дочерних и родительских компонентов.. Давайте рассмотрим простой пример использования.
var child = {
template: '<div><input type="text" :value="value" @input="emitEvent">{{value}}</div>',
methods: {
emitEvent(e) {
this.$emit('input', e.target.value)
}
},
props: ['value']
}
new Vue({
data() {
return {
message: 'test'
}
},
components: {
child
},
template: '<div id="app"><child v-model="message"></child></div>',
el: '#app'
})
Использовать на родительском компонентеv-model, дочерний компонент будет использовать имя по умолчаниюvalueизpropи назвалinputсобытия, конечно, какselectФормы существуют как другие события по умолчанию. Процесс анализа исходного кода аналогичен, здесь перечислены только несколько особых мест.
ASTОтличие фазы генерации от обычных элементов управления формой заключается в том, что при встречеchild, так как это не обычныйhtmlметка, будет выполнятьсяgetComponentModelпроцесс, иgetComponentModelРезультат вASTдобавить на деревоmodelхарактеристики.
function model() {
if (!config.isReservedTag(tag)) {
genComponentModel(el, value, modifiers);
}
}
function genComponentModel (el,value,modifiers) {
var ref = modifiers || {};
var number = ref.number;
var trim = ref.trim;
var baseValueExpression = '?v';
var valueExpression = baseValueExpression;
if (trim) {
valueExpression =
"(typeof " + baseValueExpression + " === 'string'" +
"? " + baseValueExpression + ".trim()" +
": " + baseValueExpression + ")";
}
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
var assignment = genAssignmentCode(value, valueExpression);
// 在ast树上添加model属性,其中有value,expression,callback属性
el.model = {
value: ("(" + value + ")"),
expression: JSON.stringify(value),
callback: ("function (" + baseValueExpression + ") {" + assignment + "}")
};
}
наконецASTРезультат дерева:
{
model: {
callback: "function ($$v) {message=$$v}"
expression: ""message""
value: "(message)"
}
}
через паруASTПосле обработки дерева вернитесь кgenData$2процесс, так как естьmodelсвойство, строка, объединенная родительским компонентом, будет дополнительно обработана.
function genData$2 (el, state) {
var data = '{';
var dirs = genDirectives(el, state);
···
// v-model组件的render函数处理
if (el.model) {
data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
}
···
return data
}
Таким образом, родительский компонент заканчиваетсяrenderФункция ведет себя так:"_c('child',{model:{value:(message),callback:function (?v) {message=?v},expression:"message"}})"
Фаза создания подкомпонента выполняется как обычноcreateComponent, что дляmodelЛогика требует специального пояснения.
function createComponent() {
// transform component v-model data into props & events
if (isDef(data.model)) {
// 处理父组件的v-model指令对象
transformModel(Ctor.options, data);
}
}
function transformModel (options, data) {
// prop默认取的是value,除非配置上有model的选项
var prop = (options.model && options.model.prop) || 'value';
// event默认取的是input,除非配置上有model的选项
var event = (options.model && options.model.event) || 'input'
// vnode上新增props的属性,值为value
;(data.attrs || (data.attrs = {}))[prop] = data.model.value;
// vnode上新增on属性,标记事件
var on = data.on || (data.on = {});
var existing = on[event];
var callback = data.model.callback;
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1
: existing !== callback
) {
on[event] = [callback].concat(existing);
}
} else {
on[event] = callback;
}
}
отtransformModelПо логике видно, что дочерний компонентvnodeбыло быdata.propsДобавить кdata.model.value, и датьdata.onДобавить кdata.model.callback. поэтому родительский компонентv-modelСинтаксический сахар может быть существенно изменен для'<child :value="message" @input="function(e){message = e}"></child>'
Очевидно, что этот способ написания является способом написания сообщения о событии, и этот процесс возвращается к процессу анализа инструкций по событию. Таким образом, мы можем ясно понять, что компонент используетv-modelПо сути, это синтаксический сахар для связи дочерних и родительских компонентов.
- Углубленный анализ исходного кода Vue — слияние опций (включено)
- Углубленный анализ исходного кода Vue — слияние вариантов (ниже)
- Углубленный анализ исходного кода Vue — прокси данных, связывание дочерних и родительских компонентов
- Углубленный анализ исходного кода Vue — монтирование экземпляра, процесс компиляции
- Углубленный анализ исходного кода Vue — полный процесс рендеринга
- Глубинный анализ исходного кода VUE - фундамент компонентов
- Углубленный анализ исходного кода Vue — расширенный компонент
- Углубленный анализ исходного кода Vue — построение адаптивной системы (включено)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (посередине)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (ниже)
- Углубленный анализ исходного кода Vue — приходите и реализуйте алгоритм сравнения вместе со мной!
- Углубленный анализ исходного кода Vue — демистификация механизма событий Vue
- Углубленный анализ исходного кода Vue - слоты Vue, все, что вы хотите знать, здесь!
- Углубленный анализ исходного кода Vue — понимаете ли вы синтаксический сахар v-model?
- Углубленный анализ исходного кода Vue — концепция динамических компонентов Vue, не запутаетесь ли вы?
- Тщательно изучите магию поддержки активности в Vue (часть 1)
- Тщательно изучите магию поддержки активности в Vue (часть 2)