Other Common instructions
The implementation of v-model/ V-bind and other instructions. After the end of this article, I will no longer explain the instructions. After this article, I will sort out the process and design of petite-Vue. Let’s close this series, and without further ado, let’s get to today’s topic.
v-model
The V-Model directive provides the ability to bind data in both directions, which is a great convenience in many scenarios.
- Text and Textarea elements use value property and input events;
- Checkbox and radio use checked Property and change events;
- The SELECT field takes value as prop and change as an event.
The effect is similar to the following code snippet:
<input type="text"> <script type="text/javascript"> const input = document.querySelector('input'); const data = { _text: '', get text() { return this._text; }, set text(newText) { this._text = newText; input.value = (this._text).toUpperCase(); // To show synchronization, convert to uppercase}}; input.addEventListener('input', (e) => { data.text = e.target.value; }); </script>Copy the code
After a small example, v-Model should have a preliminary impression, in fact, the most important role is the event and state association synchronization, the next several parts to analyze it.
1. Delay execution
In the walk function, when the v-model instruction is parsed, it is not executed immediately. Instead, it is delayed until all other instructions of the target element have been executed. We can defer V-model since it relies on :value Bindings to be processed first.
let deferredModel for (const { name, value } of [...el.attributes]) { if (dirRE.test(name) && name ! == 'v-cloak') { if (name === 'v-model') { // defer v-model since it relies on :value bindings to be processed // first deferredModel = value } else { processDirective(el, name, value, ctx) } } } if (deferredModel) { processDirective(el, 'v-model', deferredModel, ctx) }Copy the code
The reason for postponing the V-Model is pretty clear, and at first it was a bit confusing and didn’t feel like it needed to be delayed, but consider the following situation:
<inut v-model="text" :type="type">
Copy the code
Different types will result in different processing logic of V-Model. Therefore, v-Model instruction can be executed only after type binding is completed. Therefore, such design is reasonable.
2. Event binding
As you can see earlier, different events need to be bound to different form elements, so you need to determine the type first, so you can find this determination in the source code:
const type = el.type; if (el.tageName === 'SELECT') { listen(el, 'change', () => { ... }); effect(() => { ... }); } else if (type === 'checkbox') { listen(el, 'change', () => { ... }); effect(() => { ... }); } else if (type === 'radio') { listen(el, 'change', () => { ... }); effect(() => { ... }); } else { // input/textarea listen(el, 'compositionstart', onCompositionStart); listen(el, 'compositionend', onCompositionEnd); listen(el, modifiers? .lazy ? 'change' : 'input', () => { ... }; effect(() => { ... }); }Copy the code
So, except for the input/textarea handling of synthetic events, the other processing logic is similar. First bind the change/input event, and then register the side effect, so that when the event callback is triggered, change the state value in the context, and the side effect is triggered, so that the UI updates synchronously. The specific implementation of each case is not detailed here.
3.compositionEvent
For languages that use input methods (Chinese, Japanese, Korean, etc.), you will find that the V-Model is not updated during the input method organization process. If you also want to respond to these updates, use the input event listener and value binding instead of using the V-Model. CompositionEvent events are not supported here, so you need to implement them manually. In the process of triggering the composite event, the user actually prevents the target object input/change event callback from happening. After compositionEnd is triggered, the simulation triggers the input event to ensure the consistency of the interface.
listen(el, 'compositionstart', onCompositionStart); listen(el, 'compositionend', onCompositionEnd); listen(el, modifiers? .lazy ? 'change' : 'input', () => {if (el.com) posture) {// If it was a synthetic event, return was directly posture, so update return was not executed when compositionEvent occurred; } // update }); const onCompositionStart = (e) => { e.target.composing = true; Const onCompositionEnd = (e) => {const target = e.target; If (target.com posture) {// Synthetic event target.com posture = false; trigger(target, 'input'); }} const trigger = (el, type) => {const e = document.createEvent('HTMLEvents'); e.initEvent(type, true, true); el.dispatchEvent(e); }Copy the code
v-bind
The v-bind command is used to bind attributes to tags. Attributes taken over by V-bind can be divided into HTML tag native attributes and VUE custom attributes. The class and style attributes of the HTML native attributes are slightly more difficult to handle. The realization principle is analyzed one by one below.
1.class
First, let’s review the class notation supported by V-Bind:
const data = (window.data = reactive({ classes: ['foo', { red: true }], })); . <div class="static" :class="classes">Copy the code
The class state of the binding can be a string, an array, or an object. It also needs to be merged with the tag’s own class property.
function getClass(reactiveClasses, rawClass = '') {
// ...
}
getClass(['foo', { red: true }], 'static'); // static foo red
Copy the code
The reactiveClasses type can be a string, array, or object. The rawClass reactiveClasses type can be a string, array, or object. Return directly, array and object need to iterate, now implement:
function isObject(o) {
return Object.prototype.toString.call(o) === "[object Object]";
}
function getClass(reactiveClasses, rawClass = '') {
const clsQueue = [rawClass];
if (typeof reactiveClasses === 'string') {
clsQueue.push(reactiveClasses);
} else if (Array.isArray(reactiveClasses)) {
reactiveClasses.forEach((classItem) => {
clsQueue.push(getClass(classItem));
});
} else if (isObject(reactiveClasses)) {
Object.entries(reactiveClasses).forEach(([k, v]) => {
if (v) {
clsQueue.push(k);
}
});
}
return clsQueue.filter(Boolean).join(' ');
}
Copy the code
The normalizeClass function is used to implement the class binding. The code is relatively simple:
function normalizeClass(value) {
let res = '';
if (isString(value)) {
res = value;
}
else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
const normalized = normalizeClass(value[i]);
if (normalized) {
res += normalized + ' ';
}
}
}
else if (isObject(value)) {
for (const name in value) {
if (value[name]) {
res += name + ' ';
}
}
}
return res.trim();
}
Copy the code
With their own implementation of the version of the contrast, in fact, do things are about the same, the implementation is relatively simple, not to say;
2.style
The binding style is similar to class, but with a few caveat: first, custom attributes, and then! For example, fontSize needs to be changed to font size, but if you set it to el.style. XXX directly, there is no need to convert it, because js access itself is in line with the naming rules of small hump. Let’s start with an example:
const data = (window.data = reactive({ style: { color: 'blue' }, arrayStyle: ['color: red', { fontSize: '16px' }] })); . <div style=" font-size: 16px; color: blue;" <div >< div :style="arrayStyle"></div> font-size: 16px;" ></div> ......Copy the code
The processing of source code can be divided into three steps:
- NormalizeStyle returns a style object
For example [‘color: red’, {fontSize: ’16px! Important ‘}], after conversion: {color: ‘red’, fontSize: ’16px! Important ‘};
- Iterating over styleObj calls setStytle to set the DOM style
- If the property value is
--
To start with, you need to customize the property by calling the style.setProperty method. - If the property value contains
! important
, you also need to set the style.setProperty method. For the instructions to change the method, you can see the MDN documentation, but also note that the property name needs to be converted to a hyphen; - Otherwise, assign the style object directly
- Delete style attributes that are not needed
The last thing you need to do is compare the style object with the previous one, and then delete the attributes that are not present this time.
After sorting out the ideas and matters needing attention, to see the source code is twice the result with half the effort, here is not posted code, specific can be viewed. V-bind, v-html, v-text, v-model, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind, v-bind The design and flow of Petite-Vue will be analyzed from a global perspective.