寫做不易,未經做者容許禁止以任何形式轉載!
若是以爲文章不錯,歡迎關注、點贊和分享!
持續分享技術博文,關注微信公衆號 👉🏻 前端LeBronhtml
effect做爲Vue響應式原理中的核心,在Computed、Watch、Reactive中都有出現前端
主要和Reactive(Proxy)、track、trigger等函數配合實現收集依賴,觸發依賴更新vue
effect能夠被理解爲一個反作用函數,被當作依賴收集,在響應式數據更新後被觸發。node
Vue的響應式API例如Computed、Watch都有用到effect來實現react
function effect<T = any>( fn: () => T, options: ReactiveEffectOptions = EMPTY_OBJ ): ReactiveEffect<T> {
// 若是已是effect,則重置
if (isEffect(fn)) {
fn = fn.raw
}
// 建立effect
const effect = createReactiveEffect(fn, options)
// 若是不是惰性執行,先執行一次
if (!options.lazy) {
effect()
}
return effect
}
複製代碼
const effectStack: ReactiveEffect[] = []
function createReactiveEffect<T = any>( fn: () => T, options: ReactiveEffectOptions ): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
// 沒有激活,說明調用了effect stop函數
if (!effect.active) {
// 無調度者則直接返回,不然執行fn
return options.scheduler ? undefined : fn()
}
// 判斷EffectStack中有沒有effect,有則不處理
if (!effectStack.includes(effect)) {
// 清除effect
cleanup(effect)
try {
/* * 開始從新收集依賴 * 壓入stack * 將effect設置爲activeEffect * */
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
/* * 完成後將effect彈出 * 重置依賴 * 重置activeEffect * */
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++ // 自增id,effect惟一標識
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true // 是不是effect
effect.active = true // 是否激活
effect.raw = fn // 掛載原始對象
effect.deps = [] // 當前effect的dep數組
effect.options = options // 傳入的options
return effect
}
// 每次effect運行都會從新收集依賴,deps是effect的依賴數組,須要所有清空
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這個函數常出如今reactive的getter函數中,用於依賴收集git
源碼詳解見註釋github
function track(target: object, type: TrackOpTypes, key: unknown) {
// activeEffect爲空表示沒有依賴
if (!shouldTrack || activeEffect === undefined) {
return
}
// targetMap依賴管理Map,用於收集依賴
// 檢查targetMap中有沒有target,沒有則新建
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// dep用來收集依賴函數,當監聽的key值發生變化,觸發dep中的依賴函數更新
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
// 開發環境會觸發onTrack,僅用於調試
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
複製代碼
Trigger常出如今reactive中的setter函數中,用於觸發依賴更新web
源碼詳解見註釋算法
function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) {
// 獲取依賴Map,若是沒有則不須要觸發
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
// 使用Set保存須要觸發的effect,避免重複
const effects = new Set<ReactiveEffect>()
// 定義依賴添加函數
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
// 將depsMap中的依賴添加到effects中
// 只爲了理解和原理的話 各個分支不用細看
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
})
}
// 若是存在scheduler則調用
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
// 觸發effects中的全部依賴函數
effects.forEach(run)
}
複製代碼
瞭解了Track用於依賴收集,Trigger用於依賴觸發,那麼他們的調用時機是何時呢?來看看Reactive的源碼就清楚了,源碼詳解見註釋。vuex
注:源碼結構較爲複雜(封裝),爲便於理解原理,如下爲簡化源碼。
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是Vue中經常使用且好用的一個屬性,這個屬性的值在依賴改變後同步進行改變,在依賴未改變時使用緩存的值。
Show me the Code
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
}
複製代碼
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,
// 響應式數據更新後將dirty賦值爲true
// 下次執行getter判斷dirty爲true即從新計算computed值
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)
// 當響應式數據更新後dirty爲true
// 從新計算數據後,將dirty賦值爲false
if (self._dirty) {
self._value = this.effect()
self._dirty = false
}
// 依賴收集
track(self, TrackOpTypes.GET, 'value')
// 返回計算後的值
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
複製代碼
Watch主要用於對某個變量的監聽,並作相應的處理
Vue3中不只重構了watch,還多了一個WatchEffect API
用於對某個變量的監聽,同時能夠經過callBack拿到新值和舊值
watch(state, (state, prevState)=>{})
複製代碼
每次更新都會執行,自動收集使用到的依賴
沒法獲取到新值和舊值,可手動中止監聽
onInvalidate(fn)
傳入的回調會在watchEffect
從新運行或者watchEffect
中止的時候執行
const stop = watchEffect((onInvalidate)=>{
// ...
onInvalidate(()=>{
// ...
})
})
// 手動中止監聽
stop()
複製代碼
Show me the Code
// 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)
}
複製代碼
如下爲刪減版源碼,理解核心原理便可
詳情見註釋
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
// 對不一樣的狀況作getter賦值
if (isRef(source)) {
// ref經過.value獲取
getter = () => (source as Ref).value
forceTrigger = !!(source as Ref)._shallow
} else if (isReactive(source)) {
// reactive直接獲取
getter = () => source
deep = true
} else if (isArray(source)) {
// 若是是數組,作遍歷處理
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)) {
// 若是是函數的狀況
// 有cb則爲watch,沒有則爲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 {
// 異常狀況
getter = NOOP
// 拋出異常
__DEV__ && warnInvalidSource(source)
}
// 深度監聽邏輯處理
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)
}
}
// 記錄oldValue,並經過runner獲取newValue
// callback的封裝處理爲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
// 經過讀取配置,處理job的觸發時機
// 並再次將job的執行封裝到scheduler中
let scheduler: ReactiveEffectOptions['scheduler']
if (flush === 'sync') { // 同步執行
scheduler = job
} else if (flush === 'post') { // 更新後執行
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// default: 'pre'
// 更新前執行
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()
}
}
}
// 使用effect反作用處理依賴收集,在依賴更新後調用scheduler(其中封裝了callback的執行)
const runner = effect(getter, {
lazy: true,
onTrack,
onTrigger,
scheduler
})
// 收集依賴
recordInstanceBoundEffect(runner, instance)
// 讀取配置,進行watch初始化
// 是否有cb
if (cb) {
// 是否馬上執行
if (immediate) {
job()
} else {
oldValue = runner()
}
} else if (flush === 'post') {
// 是否更新後執行
queuePostRenderEffect(runner, instance && instance.suspense)
} else {
runner()
}
// 返回手動中止函數
return () => {
stop(runner)
if (instance) {
remove(instance.effects!, runner)
}
}
}
複製代碼
Mixin意爲混合,是公共邏輯封裝利器。
原理比較簡單,那就是合併。
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)
)
// 對mixin中的對象進行遍歷
for (const key in from) {
// 若是存在則進行覆蓋處理
if (strats && hasOwn(strats, key)) {
to[key] = strats[key](to[key], from[key], instance && instance.proxy, key)
} else {
// 若是不存在則直接賦值
to[key] = from[key]
}
}
return to
}
複製代碼
簡單粗暴放進Set,調用時依次調用
function mergeHook( to: Function[] | Function | undefined, from: Function | Function[] ) {
return Array.from(new Set([...toArray(to), ...toArray(from)]))
}
複製代碼
Vuex是在Vue中經常使用的狀態管理庫,在Vue3發佈後,這個狀態管理庫也隨之發出了適配Vue3的Vuex4
爲何每一個組件均可以經過
this.$store
訪問到store數據?
爲何Vuex中的數據都是響應式的
new Vue
,建立了一個Vue實例,至關於借用了Vue的響應式。mapXxxx是怎麼獲取到store中的數據和方法的
總的來看,能夠把Vue3.x理解爲一個每一個組件都注入了的mixin?
去除冗餘代碼看本質
export function createStore (options) {
return new Store(options)
}
class Store{
constructor (options = {}){
// 省略若干代碼...
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)
}
// 省略若干代碼...
}
}
function resetStoreState (store, state, hot) {
// 省略若干代碼...
store._state = reactive({
data: state
})
// 省略若干代碼...
}
複製代碼
export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate?: RootHydrateFunction ): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
// 省略部分代碼....
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
version,
// 省略部分代碼....
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
},
// 省略部分代碼 ....
}
}
複製代碼
下面接着看provide實現
install (app, injectKey) {
// 實現經過inject獲取
app.provide(injectKey || storeKey, this)
// 實現this.$store獲取
app.config.globalProperties.$store = this
}
複製代碼
provide(key, value) {
// 已存在則警告
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.`
)
}
// 將store放入context的provide中
context.provides[key as string] = value
return app
}
// context相關 context爲上下文對象
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)
}
}
複製代碼
import { useStore } from 'vuex'
export default{
setup(){
const store = useStore();
}
}
複製代碼
function useStore (key = null) {
return inject(key !== null ? key : storeKey)
}
複製代碼
function inject( key: InjectionKey<any> | string, defaultValue?: unknown, treatDefaultAsFactory = false ) {
const instance = currentInstance || currentRenderingInstance
if (instance) {
// 有父級實例則取父級實例的provides,沒有則取根實例的provides
const provides =
instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides
// 經過provide時存入的key取出store
if (provides && (key as string | symbol) in provides) {
return provides[key as string]
// 省略一部分代碼......
}
}
複製代碼
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
}
}
複製代碼
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;
}
複製代碼
可從vue中引入provide、inject、getCurrentInstance等API進行庫開發 / 高階用法,這裏不過多贅述。
瞭解Vue3的Diff算法優化前,能夠先了解一下Vue2的Diff算法
本部分注重把算法講清楚,將不進行逐行源碼分析
最長遞增子序列 減小Dom元素的移動,達到最少的 dom 操做以減少開銷。
關於最長遞增子序列算法能夠看看最長遞增子序列
Vue2中對vdom進行全量Diff,Vue3中增長了靜態標記進行非全量Diff
對vnode打了像如下枚舉內的靜態標記
export enum PatchFlags{
TEXT = 1 , //動態文本節點
CLASS = 1 << 1, //2 動態class
STYLE = 1 << 2, //4 動態style
PROPS = 1 << 3, //8 動態屬性,但不包含類名和樣式
FULL_PROPS = 1 << 4, //16 具備動態key屬性,當key改變時,需進行完整的diff比較
HYDRATE_EVENTS = 1 << 5,//32 帶有監聽事件的節點
STABLE_FRAGMENT = 1 << 6, //64 一個不會改變子節點順序的fragment
KEYED_FRAGMENT = 1 << 7, //128 帶有key屬性的fragment或部分子節點有key
UNKEYEN_FRAGMENT = 1 << 8, //256 子節點沒有key的fragment
NEED_PATCH = 1 << 9, //512 一個節點只會進行非props比較
DYNAMIC_SLOTS = 1 << 10,//1024 動態slot
HOISTED = -1, //靜態節點
//指示在diff過程當中要退出優化模式
BAIL = -2
}
複製代碼
<div>
<p>Hello World</p>
<p>{{msg}}</p>
</div>
複製代碼
對msg變量進行了標記
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
複製代碼
Vue2中不管是元素是否參與更新,每次都會從新建立
Vue3中對於不參與更新的元素,只會被建立一次,以後會在每次渲染時候被不停地複用
之後每次進行render的時候,就不會重複建立這些靜態的內容,而是直接從一開始就建立好的常量中取就好了。
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, "Xmo"),
_createVNode("p", null, "Xmo"),
_createVNode("p", null, "Xmo"),
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
/* * 靜態提高後 */
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
複製代碼
默認狀況下onClick會被視爲動態綁定,因此每次都會去追蹤它的變化
可是由於是同一個函數,因此沒有追蹤變化,直接緩存起來複用便可。
// 模板
<div>
<button @click="onClick">btn</button>
</div>
// 使用緩存前
// 這裏咱們尚未開啓事件監聽緩存,熟悉的靜態標記 8 /* PROPS */ 出現了,
// 它將標籤的 Props (屬性) 標記動態屬性。
// 若是咱們存在屬性不會改變,不但願這個屬性被標記爲動態,那麼就須要 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
// 使用緩存後
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
複製代碼
它的意思很明顯,onClick 方法被存入 cache。
在使用的時候,若是能在緩存中找到這個方法,那麼它將直接被使用。
若是找不到,那麼將這個方法注入緩存。
總之,就是把方法給緩存了。