Writing is not easy. Reprinting in any form is prohibited without the permission of the author!
If you think the article is good, you are welcome to pay attention, praise and share!
Continue to share technical blog, pay attention to WeChat official account. 👉🏻 Front end LeBron
Effect and Reactive
As the core of Vue's responsive principle, effect appears in Computed, Watch and Reactive
It mainly works with Reactive(Proxy), track, trigger and other functions to collect dependencies and trigger dependency updates
- Effect
- Side effect dependent function
- Track
- Dependency collection
- Trigger
- Dependency trigger
Effect
Effect can be understood as a side effect function, which is collected as a dependency and triggered after responsive data update.
Vue's responsive API s, such as Computed and Watch, are implemented with effect
- Let's look at the entry function first
- The entry function is mainly used for some logic processing, and the core logic is located in createReactiveEffect
function effect<T = any>( fn: () => T, options: ReactiveEffectOptions = EMPTY_OBJ ): ReactiveEffect<T> { // If it is already an effect, reset it if (isEffect(fn)) { fn = fn.raw } // Create effect const effect = createReactiveEffect(fn, options) // If it is not lazy execution, execute it once first if (!options.lazy) { effect() } return effect }
- createReactiveEffect
const effectStack: ReactiveEffect[] = [] function createReactiveEffect<T = any>( fn: () => T, options: ReactiveEffectOptions ): ReactiveEffect<T> { const effect = function reactiveEffect(): unknown { // If it is not activated, the effect stop function is called if (!effect.active) { // If there is no dispatcher, return directly; otherwise, execute fn return options.scheduler ? undefined : fn() } // Judge whether there is an effect in the EffectStack. If there is, it will not be processed if (!effectStack.includes(effect)) { // Clear effect cleanup(effect) try { /* * Start collecting dependencies again * Press in stack * Set effect to activeEffect * */ enableTracking() effectStack.push(effect) activeEffect = effect return fn() } finally { /* * When finished, the effect will pop up * Reset dependency * Reset activeEffect * */ effectStack.pop() resetTracking() activeEffect = effectStack[effectStack.length - 1] } } } as ReactiveEffect effect.id = uid++ // Self incrementing id, unique id of effect effect.allowRecurse = !!options.allowRecurse effect._isEffect = true // Is it effect effect.active = true // Activate effect.raw = fn // Mount original object effect.deps = [] // dep array of current effect effect.options = options // Incoming options return effect } // Every time the effect runs, the dependencies will be collected again. deps is the dependency array of the effect, which needs to be emptied function cleanup(effect: ReactiveEffect) { const { deps } = effect if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect) } deps.length = 0 } }
Track
The Track function often appears in the getter function of reactive and is used for dependency collection
See notes for detailed source code
function track(target: object, type: TrackOpTypes, key: unknown) { // An empty activeEffect indicates that there is no dependency if (!shouldTrack || activeEffect === undefined) { return } // targetMap dependency management Map is used to collect dependencies // Check whether there is a target in the targetMap. If not, create a new one let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } // Dep is used to collect dependent functions. When the monitored key value changes, the dependent function update in dep is triggered let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } if (!dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep) // The development environment triggers onTrack for debugging only if (__DEV__ && activeEffect.options.onTrack) { activeEffect.options.onTrack({ effect: activeEffect, target, type, key }) } } }
Trigger
Trigger often appears in the setter function in reactive to trigger dependency updates
See notes for detailed source code
function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) { // Get the dependency Map. If it doesn't exist, it doesn't need to be triggered const depsMap = targetMap.get(target) if (!depsMap) { // never been tracked return } // Use Set to save the effect to be triggered to avoid repetition const effects = new Set<ReactiveEffect>() // Define dependency add function const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => { if (effectsToAdd) { effectsToAdd.forEach(effect => { if (effect !== activeEffect || effect.allowRecurse) { effects.add(effect) } }) } } // Add dependencies from depsMap to effects // Just for understanding and principle, you don't have to look at each branch if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target depsMap.forEach(add) } else if (key === 'length' && isArray(target)) { depsMap.forEach((dep, key) => { if (key === 'length' || key >= (newValue as number)) { add(dep) } }) } else { // schedule runs for SET | ADD | DELETE if (key !== void 0) { add(depsMap.get(key)) } // also run for iteration key on ADD | DELETE | Map.SET switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { add(depsMap.get(ITERATE_KEY)) if (isMap(target)) { add(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes add(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { add(depsMap.get(ITERATE_KEY)) if (isMap(target)) { add(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { add(depsMap.get(ITERATE_KEY)) } break } } // Encapsulating the effects execution function const run = (effect: ReactiveEffect) => { if (__DEV__ && effect.options.onTrigger) { effect.options.onTrigger({ effect, target, key, type, newValue, oldValue, oldTarget }) } // Called if a scheduler exists if (effect.options.scheduler) { effect.options.scheduler(effect) } else { effect() } } // Trigger all dependent functions in effects effects.forEach(run) }
Reactive
After knowing that Track is used for dependency collection and Trigger is used for dependency Trigger, when will they be called? Let's take a look at the source code of Reactive. See the notes for a detailed explanation of the source code.
Note: the structure of the source code is complex (encapsulation). In order to understand the principle, the following is the simplified source code.
- In conclusion
- Dependency collection at getter
- Trigger dependency update on setter
function reactive(target:object){ return new Proxy(target,{ get(target: Target, key: string | symbol, receiver: object){ const res = Reflect.get(target, key, receiver) track(target, TrackOpTypes.GET, key) return res } set(target: object, key: string | symbol, value: unknown, receiver: object){ let oldValue = (target as any)[key] const result = Reflect.set(target, key, value, receiver) // trigger(target, TriggerOpTypes.ADD, key, value) trigger(target, TriggerOpTypes.SET, key, value, oldValue) return result } }) }
Computed
Computed is a commonly used and easy-to-use attribute in Vue. The value of this attribute changes synchronously after the dependency changes, and the cached value is used when the dependency does not change.
- Vue2
- In Vue2, the implementation of Computed realizes the dependency collection of responsive data through nested watcher s, and indirectly triggers dependency updates in a chain.
- effect appears in Vue3 and the Computed attribute is re implemented
- Effect can be understood as a side effect function, which is collected as a dependency and triggered after responsive data update.
Show me the Code
- After reading the computed function, you will find that here is only a brief assignment of getter s and setter s
- computed supports two writing methods
- function
- getter,setter
- computed supports two writing methods
function computed<T>( getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T> ) { let getter: ComputedGetter<T> let setter: ComputedSetter<T> if (isFunction(getterOrOptions)) { getter = getterOrOptions setter = __DEV__ ? () => { console.warn('Write operation failed: computed value is readonly') } : NOOP } else { getter = getterOrOptions.get setter = getterOrOptions.set } return new ComputedRefImpl( getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set ) as any }
- The core logic is in ComputedRefImpl. Let's move on
- Mark whether the data is old through the dirty variable
- Assign dirty to true after responsive data update
- In the next get, recalculate when dirty is true and assign dirty to false
class ComputedRefImpl<T> { private _value!: T private _dirty = true public readonly effect: ReactiveEffect<T> public readonly __v_isRef = true; public readonly [ReactiveFlags.IS_READONLY]: boolean constructor( getter: ComputedGetter<T>, private readonly _setter: ComputedSetter<T>, isReadonly: boolean ) { this.effect = effect(getter, { lazy: true, // After the responsive data is updated, assign dirty to true // The next time you execute getter, judge whether dirty is true, that is, recalculate the computed value scheduler: () => { if (!this._dirty) { this._dirty = true // Distribute all side-effect functions that reference the current calculated attribute trigger(toRaw(this), TriggerOpTypes.SET, 'value') } } }) this[ReactiveFlags.IS_READONLY] = isReadonly } get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) // dirty is true when the responsive data is updated // After recalculating the data, assign dirty to false if (self._dirty) { self._value = this.effect() self._dirty = false } // Dependency collection track(self, TrackOpTypes.GET, 'value') // Returns the calculated value return self._value } set value(newValue: T) { this._setter(newValue) } }
Watch
Watch is mainly used to monitor a variable and handle it accordingly
Vue3 not only reconstructs the watch, but also adds a WatchEffect API
- Watch
It is used to listen to a variable. At the same time, you can get the new value and the old value through callBack
watch(state, (state, prevState)=>{})
- WatchEffect
Each update is executed, and the dependencies used are automatically collected
Unable to get the new and old values. You can stop listening manually
onInvalidate(fn) the callback passed in will be executed when watchEffect runs again or watchEffect stops
const stop = watchEffect((onInvalidate)=>{ // ... onInvalidate(()=>{ // ... }) }) // Manually stop listening stop()
Differences between watch and watchEffect
- watch is executed lazily, and watchEffect is executed every time the code is loaded
- watch can specify the listening variable, and watchEffect automatically depends on the collection
- watch can get old and new values, but watchEffect cannot
- watchEffect has the function of onInvalidate, but watch does not
- watch can only listen to objects such as ref and reactive, and watchEffect can only listen to specific attributes
Source Code
Show me the Code
- Here you can see that the core logic of watch and watcheffect are encapsulated in doWatch
// watch export function watch<T = any, Immediate extends Readonly<boolean> = false>( source: T | WatchSource<T>, cb: any, options?: WatchOptions<Immediate> ): WatchStopHandle { if (__DEV__ && !isFunction(cb)) { warn( `\`watch(fn, options?)\` signature has been moved to a separate API. ` + `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` + `supports \`watch(source, cb, options?) signature.` ) } return doWatch(source as any, cb, options) } export function watchEffect( effect: WatchEffect, options?: WatchOptionsBase ): WatchStopHandle { return doWatch(effect, null, options) }
- doWatch
The following is the deleted version of the source code, you can understand the core principles
See note for details
function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ, instance = currentInstance ): WatchStopHandle { let getter: () => any let forceTrigger = false let isMultiSource = false // Make getter assignment for different situations if (isRef(source)) { // ref passed value acquisition getter = () => (source as Ref).value forceTrigger = !!(source as Ref)._shallow } else if (isReactive(source)) { // reactive direct acquisition getter = () => source deep = true } else if (isArray(source)) { // If it is an array, do traversal isMultiSource = true forceTrigger = source.some(isReactive) getter = () => source.map(s => { if (isRef(s)) { return s.value } else if (isReactive(s)) { return traverse(s) } else if (isFunction(s)) { return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER, [ instance && (instance.proxy as any) ]) } else { __DEV__ && warnInvalidSource(s) } }) } else if (isFunction(source)) { // If it's a function // If there is cb, it is watch. If there is no cb, it is watchEffect if (cb) { // getter with cb getter = () => callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER, [ instance && (instance.proxy as any) ]) } else { // no cb -> simple effect getter = () => { if (instance && instance.isUnmounted) { return } if (cleanup) { cleanup() } return callWithAsyncErrorHandling( source, instance, ErrorCodes.WATCH_CALLBACK, [onInvalidate] ) } } } else { // Abnormal condition getter = NOOP // Throw exception __DEV__ && warnInvalidSource(source) } // Deep listening logic processing if (cb && deep) { const baseGetter = getter getter = () => traverse(baseGetter()) } let cleanup: () => void let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => { cleanup = runner.options.onStop = () => { callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP) } } // Record the oldValue and get the newValue through the runner // The encapsulation of callback is job let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE const job: SchedulerJob = () => { if (!runner.active) { return } if (cb) { // watch(source, cb) const newValue = runner() if ( deep || forceTrigger || (isMultiSource ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])[i]) ) : hasChanged(newValue, oldValue)) || (__COMPAT__ && isArray(newValue) && isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)) ) { // cleanup before running cb again if (cleanup) { cleanup() } callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ newValue, // pass undefined as the old value when it's changed for the first time oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue, onInvalidate ]) oldValue = newValue } } else { // watchEffect runner() } } // important: mark the job as a watcher callback so that scheduler knows // it is allowed to self-trigger (#1727) job.allowRecurse = !!cb // Handle the trigger timing of the job by reading the configuration // And encapsulate the execution of the job into the scheduler again let scheduler: ReactiveEffectOptions['scheduler'] if (flush === 'sync') { // Synchronous execution scheduler = job } else if (flush === 'post') { // Execute after update scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { // default: 'pre' // Execute before update scheduler = () => { if (!instance || instance.isMounted) { queuePreFlushCb(job) } else { // with 'pre' option, the first call must happen before // the component is mounted so it is called synchronously. job() } } } // Use effect side effects to handle dependency collection, and call scheduler after updating, which encapsulates the execution of callback. const runner = effect(getter, { lazy: true, onTrack, onTrigger, scheduler }) // Collection dependency recordInstanceBoundEffect(runner, instance) // Read the configuration and initialize the watch // Is there a cb if (cb) { // Execute now if (immediate) { job() } else { oldValue = runner() } } else if (flush === 'post') { // Execute after update queuePostRenderEffect(runner, instance && instance.suspense) } else { runner() } // Return to manual stop function return () => { stop(runner) if (instance) { remove(instance.effects!, runner) } } }
Mixin
Mixin means mixed. It is a sharp tool for public logic encapsulation.
The principle is relatively simple, that is, merging.
- Merging is divided into object merging and lifecycle merging
- Object, mergeOption
- Type object The merge of assign will overwrite
- Lifecycle, mergeHook
- Merging puts the two lifecycles into a queue and calls them in turn
- Object, mergeOption
- mergeOptions
function mergeOptions( to: any, from: any, instance?: ComponentInternalInstance | null, strats = instance && instance.appContext.config.optionMergeStrategies ) { if (__COMPAT__ && isFunction(from)) { from = from.options } const { mixins, extends: extendsOptions } = from extendsOptions && mergeOptions(to, extendsOptions, instance, strats) mixins && mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, instance, strats) ) // Traversal of objects in mixin for (const key in from) { // If it exists, overwrite it if (strats && hasOwn(strats, key)) { to[key] = strats[key](to[key], from[key], instance && instance.proxy, key) } else { // If it does not exist, it is assigned directly to[key] = from[key] } } return to }
- mergeHook
Simply put it into the Set and call it in turn
function mergeHook( to: Function[] | Function | undefined, from: Function | Function[] ) { return Array.from(new Set([...toArray(to), ...toArray(from)])) }
Vuex4
Vuex is a commonly used state management library in Vue. After Vue3 is published, the state management library also sends Vuex4 adapted to Vue3
Fast pass vuex3 X principle
-
Why can every component pass
this.$store access to store data?
- During beforeCreate, the store is injected through mixin
-
Why is the data in Vuex responsive
- When creating a store, new Vue is called and a Vue instance is created, which is equivalent to borrowing Vue's response.
-
How does mapXxxx get the data and methods in the store
- mapXxxx is just a syntax sugar, and the underlying implementation also obtains it from $store and returns it to calculated / methods.
In general, vue3 X is understood as a mixin injected into each component?
Research on Vuex4 principle
Remove redundant code and see the essence
createStore
- Starting with createStore
- It can be found that the state in Vuex4 is the responsive data created through the reactive API, and Vuex3 is the new Vue instance
- The implementation of dispatch and commit basically encapsulates a layer of execution without too much concern
export function createStore (options) { return new Store(options) } class Store{ constructor (options = {}){ // Omit some code this._modules = new ModuleCollection(options) const state = this._modules.root.state resetStoreState(this, state) // bind commit and dispatch to self const store = this const { dispatch, commit } = this this.dispatch = function boundDispatch (type, payload) { return dispatch.call(store, type, payload) } this.commit = function boundCommit (type, payload, options) { return commit.call(store, type, payload, options) } // Omit some code } } function resetStoreState (store, state, hot) { // Omit some code store._state = reactive({ data: state }) // Omit some code }
install
- Vuex is used in Vue as a plug-in. When creating app, install is called
export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate?: RootHydrateFunction ): CreateAppFunction<HostElement> { return function createApp(rootComponent, rootProps = null) { // Omit part of the code const app: App = (context.app = { _uid: uid++, _component: rootComponent as ConcreteComponent, _props: rootProps, _container: null, _context: context, version, // Omit part of the code use(plugin: Plugin, ...options: any[]) { if (installedPlugins.has(plugin)) { __DEV__ && warn(`Plugin has already been applied to target app.`) } else if (plugin && isFunction(plugin.install)) { installedPlugins.add(plugin) plugin.install(app, ...options) } else if (isFunction(plugin)) { installedPlugins.add(plugin) plugin(app, ...options) } else if (__DEV__) { warn( `A plugin must either be a function or an object with an "install" ` + `function.` ) } return app }, // Omit part of the code } }
- install of Store class
- Get through inject
- Implement this$ Store get
Next, let's look at the implementation of provide
install (app, injectKey) { // Get through inject app.provide(injectKey || storeKey, this) // Implement this$ Store get app.config.globalProperties.$store = this }
app.provide implementation
provide(key, value) { // Warning if already exists if (__DEV__ && (key as string | symbol) in context.provides) { warn( `App already provides property with key "${String(key)}". ` + `It will be overwritten with the new value.` ) } // Put the store into the provide of the context context.provides[key as string] = value return app } // Context context is a context object const context = createAppContext() export function createAppContext(): AppContext { return { app: null as any, config: { isNativeTag: NO, performance: false, globalProperties: {}, optionMergeStrategies: {}, errorHandler: undefined, warnHandler: undefined, compilerOptions: {} }, mixins: [], components: {}, directives: {}, provides: Object.create(null) } }
Vue.useStore
- Using Vuex in Vue3 Composition API
import { useStore } from 'vuex' export default{ setup(){ const store = useStore(); } }
- Implementation of useStore
function useStore (key = null) { return inject(key !== null ? key : storeKey) }
Vue.inject
- Take out the store through the key stored during provide
- If there is a parent instance, the provider of the parent instance is taken; if there is no parent instance, the provider of the root instance is taken
function inject( key: InjectionKey<any> | string, defaultValue?: unknown, treatDefaultAsFactory = false ) { const instance = currentInstance || currentRenderingInstance if (instance) { // If there is a parent instance, the provider of the parent instance is taken. If there is no parent instance, the provider of the root instance is taken const provides = instance.parent == null ? instance.vnode.appContext && instance.vnode.appContext.provides : instance.parent.provides // Take out the store through the key stored during provide if (provides && (key as string | symbol) in provides) { return provides[key as string] // Omit part of the code } }
Vue.provide
- Vue's provide API is also relatively simple, which is equivalent to assigning values directly through key/value
- When the current instance provides the same as the parent instance, the connection is established through the prototype chain
function provide<T>(key: InjectionKey<T> | string | number, value: T) { if (!currentInstance) { if (__DEV__) { warn(`provide() can only be used inside setup().`) } } else { let provides = currentInstance.provides const parentProvides = currentInstance.parent && currentInstance.parent.provides if (parentProvides === provides) { provides = currentInstance.provides = Object.create(parentProvides) } // TS doesn't allow symbol as index type provides[key as string] = value } }
injection
- Why does every component instance have a Store object?
- Providers are injected when creating component instances
function createComponentInstance(vnode, parent, suspense) { const type = vnode.type; const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext; const instance = { parent, appContext, // ... provides: parent ? parent.provides : Object.create(appContext.provides), // ... } // ... return instance; }
API s such as provide, inject and getCurrentInstance can be introduced from vue for library development / high-level usage, which will not be repeated here.
Diff algorithm optimization
Before understanding the Diff algorithm optimization of Vue3, you can first understand it Diff algorithm of Vue2
This part focuses on clarifying the algorithm and will not conduct line by line source code analysis
- The main optimization points in Vue3 are
- In updateChildren, double ended comparison - > longest increment subsequence
- Full Diff - > static tag + partial Diff
- Static lift
updateChildren
- Vue2
- Head to head comparison
- Tail tail comparison
- Head tail comparison
- Tail head comparison
- Vue3
- Head to head comparison
- Tail tail comparison
- Move / delete / add based on the longest increment subsequence
For example 🌰
- oldChild [a,b,c,d,e,f,g]
- newChild [a,b,f,c,d,e,h,g]
- First, perform head to head comparison, and jump out of the loop when comparing different nodes
- Get [a,b]
- Then conduct tail to tail comparison, and jump out of the loop when different nodes are compared
- Get [g]
- Remaining [f,c,d,e,h]
- Generate array [5, 2, 3, 4, - 1] through newIndexToOldIndexMap
- The corresponding node of the longest increasing subsequence [2, 3, 4] is [c, d, e]
- The remaining nodes are moved / added / deleted based on [c, d, e]
The longest increasing subsequence reduces the movement of dom elements and achieves the least dom operations to reduce the overhead.
The longest increasing subsequence algorithm can be seen Longest increasing subsequence
Static tag
In Vue2, vdom is fully diffed, and in Vue3, a static tag is added for partial Diff
vnode is marked statically as in the following enumeration
- patchFlag
export const enum PatchFlags{ TEXT = 1 , //Dynamic text node CLASS = 1 << 1, //2 dynamic class STYLE = 1 << 2, //4 dynamic style PROPS = 1 << 3, //8 dynamic attributes, but not class names and styles FULL_PROPS = 1 << 4, //16 has a dynamic key attribute. When the key is changed, a complete diff comparison is required HYDRATE_EVENTS = 1 << 5,//32 nodes with listening events STABLE_FRAGMENT = 1 << 6, //64 a fragment that does not change the order of child nodes KEYED_FRAGMENT = 1 << 7, //128 fragment s with key attribute or some child nodes have keys UNKEYEN_FRAGMENT = 1 << 8, //256 child nodes do not have fragment s of key s NEED_PATCH = 1 << 9, //512 only non props comparison will be performed for one node DYNAMIC_SLOTS = 1 << 10,//1024 dynamic slot HOISTED = -1, //Static node //Indicates that you want to exit optimization mode during diff BAIL = -2 }
For example 🌰
- The template looks like this
<div> <p>Hello World</p> <p>{{msg}}</p> </div>
- Generate vdom source code
The msg variable is marked
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", null, "Hello World"), _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */) ])) } // Check the console for the AST
summary
- Mark vnode s to classify nodes that need dynamic update and nodes that do not need dynamic update
- Static nodes only need to be created once, rendering is directly reused, and does not participate in the diff algorithm process.
Static lift
-
In Vue2, whether the element participates in the update or not, it will be recreated every time
-
In Vue3, elements that do not participate in the update will only be created once, and then they will be reused every time they are rendered
-
In the future, each time you render, you will not create these static contents repeatedly, but directly take them from the constants created at the beginning.
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" /* * Before static lifting */ export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", null, "Xmo"), _createVNode("p", null, "Xmo"), _createVNode("p", null, "Xmo"), _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */) ])) } /* * After static lifting */ const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "Xmo", -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "Xmo", -1 /* HOISTED */) const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "Xmo", -1 /* HOISTED */) export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _hoisted_1, _hoisted_2, _hoisted_3, _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */) ])) } // Check the console for the AST
cacheHandlers event listener cache
-
By default, onClick is regarded as a dynamic binding, so it will track its changes every time
-
However, because it is the same function, it does not track changes. It can be directly cached and reused.
// Template <div> <button @click="onClick">btn</button> </div> // Before using cache // Here, we haven't started the event listening cache. The familiar static tag 8 /* PROPS * / appears, // It marks the Props (properties) of the label as dynamic properties. // If we don't want this property to be marked as dynamic, we need the appearance of cacheHandler. import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue" export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _ctx.onClick }, "btn", 8 /* PROPS */, ["onClick"]) ])) } // Check the console for the AST // After using cache import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue" export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args))) }, "btn") ])) } // Check the console for the AST
Its meaning is obvious. The onClick method is stored in the cache.
When using, if this method can be found in the cache, it will be used directly.
If not, inject this method into the cache.
In short, the method is cached.
-
Nuggets: Front end LeBron
-
Know: Front end LeBron
-
Continue to share technical blog, pay attention to WeChat official account. 👉🏻 Front end LeBron