<!-- binding the expression --> <button @mousemove="counter++">{{counter}}</button> <!-- binding object listens to multiple events --> <button v-on="{ click: btnClick, mousemove: mousemove }">{{counter}}</button> <button @="{click: btnClick, mousemove: mousemove}">{{counter}}</button> <!-- Incoming parameters --> <!-- An event parameter will be passed in by default --> <button @click="btnClick">btn1</button> <!-- $event to get event parameter --> <button @click="btnClick($event, 'smallstars')">btn1</button>
<!-- Use of modifiers --> <!-- .stop: event.stopPropagation() .prevent: event.preventDefault() .capture: Event listener uses capture mode .once: Trigger only once .left: Only click the left mouse button to take effect .right: Only click the right mouse button to take effect .middle: Only click the middle mouse button to take effect --> <button @click.stop="btnClick">btn1</button>
v-if v-else-if v-else
v-if is lazy. When the condition is false, the component will not be rendered or will be destroyed
<!-- First convert the template to virtual DOM, and then mount it to the real DOM --> <div> <p> <i>1</i> <b>2</b> </p> <span>3</span> <strong>4</strong> </div>
graph TB
div --> p
div --> span
div --> strong
p --> i
p --> b
Diff
Unkeyed
Different letters represent different nodes (The number is added here to facilitate drawing). The nodes are compared in turn, and if they are different, the nodes are updated. Finally if there are more old nodes than new nodes, the remaining nodes are removed, other wish all of them are added.
// 4.Create responsive Object const createReactiveObject = (target) => { // 5.Judging the target type if (!isObject(target)) return target;
// 14.If the object has been proxied, return the proxied object const proxy = toProxy.get(target); if (proxy) return proxy; if (toRaw.has(target)) return target;
const baseHandle = { get: (target, key, receiver) => { console.log("get"); // 8.Use Reflect to get the value // return target[key] const res = Reflect.get(target, key, receiver);
// 21.Collection dependencies // correspond the keys of current object width reactiveEffects track(target, key);
// 9.consider the case of multi-level object return isObject(res) ? reactive(res) : res; }, /** * target: original object ({name: 'smallstars'}) * key: keyword (name) * value: new value * receiver: current proxied object (observer) */ set: (target, key, value, receiver) => { console.log("set"); // This way cannot determine whether the operation is successful // target[key] = value; // console.log(target, key, value, receiver);
// 10.Use Reflect to determine the status of operation const res = Reflect.set(target, key, value, receiver);
// 25.Determine whether it is a new attribute const hadKey = target.hasOwnProperty(key); // Avoid meaningless rendering views (eg: array.length) if (!hadKey) { // data changed, need to rendering views // console.log("rendering"); trigger(target, "add", key); } else { // property changed, don't need to rendering views // console.log("no rendering"); trigger(target, "set", key); }
// 11.Return the res return res; }, // 12.Delete the property by key deleteProperty: (target, key) => { console.log("del"); returnReflect.deleteProperty(target, key); }, }; // 6.Create the observer of target // Use the baseHandle(ProxyHandler) function to truncate the operation let observed = newProxy(target, baseHandle);
// 15.Mark the original object has been proxied toProxy.set(target, observed); toRaw.set(observed, target);
// 7.Return the observed return observed; };
// 3.Turn data into responsive const reactive = (target) => { return createReactiveObject(target); };
// if the target[key] changed, executed the effect /* targetsMap: { [target]: { [key]: deps; } } deps: [effects] */ // 22.use targetsMap to collect dependencies of per targets[key] const targetsMap = newWeakMap(); const track = (target, key) => { // 23.Determine whether the stack is empty let effect = activeEffectStack[activeEffectStack.length - 1];
// 24.Determine whether the targetsMap is empty, initialize it // console.log(target, key); if (effect) { let depsMap = targetsMap.get(target); if (!depsMap) targetsMap.set(target, (depsMap = newMap())); let deps = depsMap.get(key); if (!deps) depsMap.set(key, (deps = newSet()));
if (!deps.has(effect)) deps.add(effect); } };
// 26.Called the effect to update views const trigger = (target, type, key) => { const depsMap = targetsMap.get(target); if (depsMap) { const deps = depsMap.get(key); if (deps) { deps.forEach((effect) => { effect(); }); } } };
/** * 19.Use stack cache fn(if this effect has other effect, there will be multiple values in the stack) * 1).push effect into stack * 2).execute fn * 3).pop the effect */ const run = (effect, fn) => { console.log("run"); // prevent errors when fn is executed try { activeEffectStack.push(effect); // 20. execute fn fn(); } finally { activeEffectStack.pop(); } };
// let proxy = reactive({ name: "smallstars" }); // // This effect is executed once when the dependencies are collected, and then called when the subsequent data is updated. // effect(() => { // console.log(1); // }); // proxy.name = "blackangel";
// test // const info = { name: { firstName: "small", lastName: "stars" } }; // let proxy = reactive(info);
// // We need to prevent duplication of proxy // // use WeakMap to store it // reactive(info); // reactive(info); // // reactive(info);
// const arr = [1, 2, 3]; // let proxy = reactive(arr); // // it will trigger operation twice. 1.push 2.length // proxy.push(4);
// 1.Defined the data (ref/reactive/computed) const info = reactive({ counter1: 1, counter2: 1 });
// 16.Side effects of data changes effect(() => { console.log("counter1:", info.counter1); // console.log("counter2:", info.counter2); });
// 2.Change the data value setInterval(() => { info.counter1++; }, 3000);
const { patchFlag, shapeFlag } = n2; // fast path if (patchFlag > 0) { if (patchFlag & PatchFlags.KEYED_FRAGMENT) { // this could be either fully-keyed or mixed (some keyed some not) // presence of patchFlag means children are guaranteed to be arrays // keyed patchKeyedChildren( c1 as VNode[], c2 as VNodeArrayChildren, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); return; } elseif (patchFlag & PatchFlags.UNKEYED_FRAGMENT) { // unkeyed patchUnkeyedChildren( c1 as VNode[], c2 as VNodeArrayChildren, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); return; } }
// children has 3 possibilities: text, array or no children. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // text children fast path if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { unmountChildren(c1 as VNode[], parentComponent, parentSuspense); } if (c2 !== c1) { hostSetElementText(container, c2 asstring); } } else { if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { // prev children was array if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // two arrays, cannot assume anything, do full diff patchKeyedChildren( c1 as VNode[], c2 as VNodeArrayChildren, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); } else { // no new children, just unmount old unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true); } } else { // prev children was text OR null // new children is array OR null if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(container, ""); } // mount new if array if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren( c2 as VNodeArrayChildren, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); } } } };
// unkey const patchUnkeyedChildren = ( c1: VNode[], c2: VNodeArrayChildren, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null, optimized: boolean ) => { c1 = c1 || EMPTY_ARR; c2 = c2 || EMPTY_ARR; // get length of old nodes const oldLength = c1.length; // get length of new nodes const newLength = c2.length; // get the minimum length const commonLength = Math.min(oldLength, newLength); let i;
for (i = 0; i < commonLength; i++) { const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as VNode) : normalizeVNode(c2[i]));
// Compare nodes one by one patch( c1[i], nextChild, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); }
// if the number of new nodes is less if (oldLength > newLength) { // remove old unmountChildren( c1, parentComponent, parentSuspense, true, false, commonLength ); } else { // mount new mountChildren( c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, commonLength ); } };
Differences between Vue2 and Vue3
Performance
Diff algorithm optimization
VNodes in Vue2 are a full comparison
The simple summary is that the pointers on both sides move to the middle for comparison, until one of the traversal of oldCh or newCh is completed. In the Vue2, virtual DOM is a full comparison process. When the object is too large, the update of VNodes will be slower.
Vue3 added a static flag (PatchFlag). During the comparison, only nodes with static flags are compared, and the specific comparison content can be learned through the flags.
-------------The end of this article, thanks for reading-------------