Writing is not easy, without the permission of the author forbid to reprint in any form!
If you think the article is good, welcome to follow, like and share!
Keep sharing technical blog posts, follow wechat public account 👉🏻 front-end LeBron
Effect and Reactive
Effect is the core of THE Vue responsive principle and can be found in Computed, Watch, and Reactive
The main function works with Reactive(Proxy), track, and trigger to collect dependencies and trigger dependency updates
-
Effect
- Side effect dependence function
-
Track
- Depend on the collection
-
Trigger
- Depend on the trigger
Effect
Effect can be understood as a side effect function, which is collected as a dependency and triggered after a responsive data update.
Vue’s responsive apis such as Computed and Watch are implemented with Effect
-
Let’s look at the entry function first
- Entry functions are mainly some logic processing, the core logic is located in createReactiveEffect
function effect<T = any>( fn: () => T, options: ReactiveEffectOptions = EMPTY_OBJ ): ReactiveEffect<T> {// If it is already effect, If (isEffect(fn)) {fn = fn. Raw} // Create effect const effect = createReactiveEffect(fn, options) Execute an 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 {// effect stop function is called if (! Active) {fn return options.scheduler? } // Use undefined: fn(); // use undefined: fn(); Effectstack.includes (effect)) {// Clear effect cleanup(effect) try {/* * Start collecting dependencies * Push into stack * set effect to activeEffect * */ EnableTracking () effectStack.push(effect) activeEffect = effect return fn()} finally {/* * Reset activeEffect * */ effectStack.pop() resetTracking() activeEffect = effectStack[effectStack.length - 1]}} as ReactiveEffect effect.id = UI ++ // add id, effect unique identifier effect.allowRECURse =! AllowRecurse effect._ISEffect = true // Whether effect effectie. active = true // Whether effect. Raw = fn // mount the original object Deps = [] // effect. Options = options // options passed in return effect} // Dependencies are collected every time effect is run, Function cleanup: cleanup of all deps: ReactiveEffect) { const { deps } = effect if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect) } deps.length = 0 } }
Track
Track is often used in the getter function of reactive to collect dependencies
Source details see the comments
Function track(target: object, type: TrackOpTypes, key: unknown) {// If (! ShouldTrack | | activeEffect = = = undefined) {return} / / targetMap dependency management Map, are used to collect depend on / / check if there is any target in the targetMap, Let depsMap = targetMap.get(target) if (! DepsMap) {targetMap.set(target, (depsMap = new Map()))} 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 will trigger onTrack, Is only used to debug the if (__DEV__ && activeEffect. Options. OnTrack) {activeEffect. Options. OnTrack ({effect: activeEffect, target, type, key }) } } }
Trigger
Triggers are often found in setters in reactive and are used to Trigger dependency updates
Source details see the comments
function trigger( target: object, type: TriggerOpTypes, key? : unknown, newValue? : unknown, oldValue? : unknown, oldTarget? : the Map (unknown, unknown > | Set < unknown >) {/ / access to rely on the Map, if have no you don't need to trigger the const depsMap = targetMap. Get (target) if (! DepsMap) {// never been tracked return} // Use Set to save the effect to be triggered, Const Effects = new Set<ReactiveEffect>() const Add = (effectsToAdd: Set<ReactiveEffect> | undefined) => { if (effectsToAdd) { effectsToAdd.forEach(effect => { if (effect ! = = activeEffect | | effect. AllowRecurse) {effects. The add (effect)}})}} / / will be dependent on added to the effects of depsMap / / in order to understand and principle The branches don't have to look 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}} effects const run = (effect: ReactiveEffect) => { if (__DEV__ && effect.options.onTrigger) { effect.options.onTrigger({ effect, target, key, type, newValue, oldValue, OldTarget})} if (effect.options.scheduler) {effect.options.scheduler(effect)} else {effect()} } effects.foreach (run)}
Reactive
Now that you know that Track is used for dependency collection and Trigger is used for dependency triggering, when are they invoked? Reactive source code: Reactive source code: Reactive source code: Reactive source code
Note: The source code structure is more complex (encapsulation), in order to facilitate understanding of the principle, the following is a simplified source code.
-
In summary
- Do dependency collection at the getter
- Trigger dependency updates at 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 common and useful attribute in Vue. The value of this attribute is changed synchronously when the dependency changes and is cached when the dependency does not change.
-
Vue2
- In Vue2, Computed is implemented by nesting watcher to collect response-based data and trigger dependency update indirectly.
-
Effect occurred in Vue3 and Computed was reimplemented
- Effect can be understood as a side effect function that is collected as a dependency and triggered after a responsive data update.
Show me the Code
-
When you look at this computed function, you see that this is just a brief assignment of getters and setters
-
For computed, two writing methods are supported
- function
- Getter and setter
-
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 all in ComputedRefImpl, so let’s move on
- Use the dirty variable to mark whether the data is old
- Assign dirty to true after the responsive data update
- On the next get, when dirty is true, it is recalculated and dirty is assigned 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: // Perform the getter to determine that the dirty is true next time. That is, to recalculate the computed value scheduler: () => {if (! This._dirty) {this._dirty = true} effect 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) If (self._dirty) {self._value = this.effect() self._dirty = false} // dependent collection track(self, Trackoptypes.get, 'value') return self._value} set value(newValue: T) {this._setter(newValue)}}
Watch
Watch is mainly used to listen on a variable and do corresponding processing
Not only does Vue3 refactor Watch, it also adds a WatchEffect API
- Watch
CallBack is used to listen for a variable and to retrieve both the old and new values through the callBack
watch(state, (state, prevState)=>{})
- WatchEffect
Each update is performed, automatically collecting used dependencies
The new and old values cannot be obtained. You can manually stop the listening
OnInvalidate (fn) the incoming callback is executed either when the watchEffect is re-run or when it is stopped
const stop = watchEffect((onInvalidate)=>{ // ... onInvalidate(()=>{ // ... })}) // Stop the listener manually
The differences between Watch and watchEffect
- Watch executes lazily, watchEffect executes every time code is loaded
- Watch can specify listening variables, and watchEffect automatically relies on collection
- Watch can get old and new values, watchEffect cannot
- WatchEffect has onInvalidate function, watch does not
- Watch can only listen to objects such as ref and reactive, and watchEffect can only listen to specific properties
Source Code
Show me the Code
- You can see that the core logic of Both Watch and watchEffet is 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 a truncated version of the source code, 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 // Do getters for different cases if (isRef(source)) getter = () => (source as Ref).value forceTrigger = !! (source as Ref)._shallow} else if (isReactive(source)) {// source => source deep = true} else if (source as Ref)._shallow) (isArray(source)) {// If it is an array, 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 is a function // if there is a cb, 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 {// Exception getter = NOOP // throw exception __DEV__ && warnInvalidSource(source)} // Deep listener 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 oldValue and get newValue by runner // 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 // To read the configuration, process the trigger time of the job // to encapsulate the job execution into the scheduler again let scheduler: ReactiveEffectOptions['scheduler'] if (flush === 'sync') {else if (flush === 'post') {// Scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)} else {// default: 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 (which wraps the execution of callback) const Runner = effect(getter, {lazy: True, onTrack onTrigger, scheduler}) / / collect rely on recordInstanceBoundEffect (runner, the instance) / / reading configuration, Cb if (cb) {if (immediate) {job()} else {oldValue = runner()} else if (flush ===) 'post') {// Whether to execute queuePostRenderEffect after update (runner, For instance && instance.suspense)} else {runner()} return () => {stop(runner) if (instance) { remove(instance.effects! , runner) } } }
Mixin
A Mixin is a common logic wrapper.
The principle is simple: merge.
-
Merges are divided into object merges and lifecycle merges
-
Object, mergeOption
- Overwrite occurs when type object. assign is merged
-
Life cycle, mergeHook
- Merging puts the two lifecycles on a queue and calls them in turn
-
- 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, For (const key in from) {// Override if (strats && hasOwn(strats, key)) { to[key] = strats[key](to[key], from[key], instance && instance.proxy, To [key] = from[key]}} else {// Return to}
- mergeHook
Just put it in a 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 state management library commonly used in Vue. After the release of Vue3, this state management library also issued Vuex4 for Vue3
Fast through the vue X 3.x principle
- Why does every component pass
This.$store access to store data?
- In beforeCreate, the store is injected by means of mixin
-
Why is data in Vuex reactive
- When the store is created, it’s called
new Vue
, creating a Vue instance, and borrowing Vue’s response.
- When the store is created, it’s called
-
How does mapXxxx get data and methods from the Store
- MapXxxx is just syntax sugar, and the underlying implementation is also taken from $Store and returned to computed/Methods.
In general, you can think of Vue3.x as a mixin that each component is injected into, right?
Study on the principle of Vuex4
Cut out the redundant code and get to the basics
createStore
-
Start with createStore
- It can be found that state in Vuex4 is created by reactive API, while in Vuex3, it is created by new Vue instance
- The implementation of Dispatch and COMMIT is basically to encapsulate a layer of execution, do not care too much
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... Store._state = reactive({data: state}) }
install
- Vuex is used in Vue as a plug-in and is installed by calling Install at createApp
export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate? : RootHydrateFunction ): 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.... }}
-
Store of install
- Implementation is obtained through Inject
- Implement this.$store fetch
Let’s move on to the provide implementation
Install the app, injectKey) {/ / implementation through inject for app. Dojo.provide (injectKey | | storeKey, This) / / implement this. $store for app. Config. GlobalProperties $store = this}
App. Dojo.provide implementation
provide(key, Value) {/ / has warned the if (__DEV__ && (key as string | symbol) in the context. Provides) {warn (` App already provides the property With key "${String(key)}". '+' It will be overwritten with the new value. ') Context. provides[key as string] = value return app} 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
- Use Vuex in the Vue3 Composition API
import { useStore } from 'vuex' export default{ setup(){ const store = useStore(); }}
- The realization of the useStore
function useStore (key = null) { return inject(key ! == null ? key : storeKey) }
Vue.inject
- Retrieve the store using the key stored when provide
- If there is a parent instance, the parent instance provides. If there is no parent instance, the root instance provides
function inject( key: InjectionKey<any> | string, defaultValue? : unknown, treatDefaultAsFactory = false ) { const instance = currentInstance || currentRenderingInstance if (instance) { // Parent == null? Parent == null? Parent == null? instance.vnode.appContext && instance.vnode.appContext.provides : Instance. The parent. Provides / / through dojo.provide deposit of key store the if (provides && (key as string | symbol) provides) in {return Provides [key as string] // Omit part of the code...... }}
Vue.provide
- Vue’s provide API is also relatively simple, equivalent to a direct key/value assignment
- The link is established through the prototype chain while the current instance provides and the parent instance provides are the same
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?
- Provides is injected when the component instance is created
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;
}
Provide, Inject, getCurrentInstance and other apis can be introduced from Vue for library development/high-level usage, but not too much detail here.
Diff algorithm optimization
Before understanding Vue3’s Diff algorithm optimization, you can first understand Vue2’s Diff algorithm
This section focuses on clarifying the algorithm and will not do line-by-line source analysis
-
The main optimization points in Vue3 are
- Double end comparison -> longest increment subsequence when updateChildren
- Full Diff -> Static tag + incomplete Diff
- Static ascension
updateChildren
-
Vue2
- Head to head comparison
- Tail-tail comparison
- Head to tail comparison
- Tail-head comparison
-
Vue3
- Head to head comparison
- Tail-tail comparison
- Move/delete/add based on longest increasing subsequence
An 🌰
- oldChild [a,b,c,d,e,f,g]
- newChild [a,b,f,c,d,e,h,g]
-
We start with a head-to-head comparison, breaking out of the loop when we compare different nodes
- [a, b]
-
We then do a tail-tail comparison, breaking out of the loop when we get to a different node
- [g]
-
The remaining [f, c, d, e, h]
- Generate arrays [5, 2, 3, 4, -1] from newIndexToOldIndexMap
- The longest increasing subsequence [2, 3, 4] corresponds to nodes [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 minimizes Dom operations to reduce overhead.
For the longest increasing subsequence algorithm you can look at the longest increasing subsequence
Static markup
In Vue2, vDOM is fully Diff, while in Vue3, static tags are added for incomplete Diff
Vnodes are marked as static as in the following enumeration
- patchFlag
Export const enum PatchFlags{TEXT = 1, // Dynamic TEXT node CLASS = 1 << 1, //2 dynamic CLASS STYLE = 1 << 2, FULL_PROPS = 1, PROPS = 2, PROPS = 2, PROPS = 3 HYDRATE_EVENTS = 1 << 5,//32 STABLE_FRAGMENT = 1 << 6, KEYED_FRAGMENT = 1 << 7, //128 Fragment with key attribute UNKEYEN_FRAGMENT = 1 << 8, NEED_PATCH = 1 << 9, //512 A node will only perform non-props comparison. DYNAMIC_SLOTS = 1 << 10,//1024 dynamic slot HOISTED = -1, // Static node indicates to exit optimization mode during diff process BAIL = -2}
An 🌰
- 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
conclusion
- By marking vNodes, you can classify nodes that require dynamic update and those that do not
- Static nodes only need to be created once, rendering directly reuse, do not participate in the DIFF algorithm process.
Static ascension
- Vue2 recreates each time, whether or not the element participates in the update
- In Vue3, elements that do not participate in updates are created only once and then reused repeatedly each time they are rendered
- Instead of creating the static content each time you render, you can just take the constants that you created in the first place.
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, */ 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 / * * /)]))} / * * * / const after static ascension _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 treated as dynamic binding, so it is tracked every time
- However, since it is the same function, it does not track the changes. It can be cached and reused directly.
// <div> < button@click ="onClick"> <div> <div> <div> <div> // It marks the Props (attribute) of the tag as dynamic. // If we have a property that doesn't change and don't want the property to be marked dynamic, then we need a cacheHandler presence. 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 // 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
It’s pretty obvious what it means, the onClick method is cached.
At the time of use, if the method can be found in the cache, it will be used directly.
If not, inject the method into the cache.
Anyway, I’m just caching the method.
- Nuggets: LeBron on the front end
- Zhihu: LeBron on the front end
- Keep sharing technical blog posts, follow wechat public account 👉🏻 front-end LeBron