写作不易,未经作者容许禁止以任何模式转载!
如果感觉文章不错,欢送关注、点赞和分享!
继续分享技术博文,关注微信公众号 👉🏻 前端 LeBron
Effect 和 Reactive
effect 作为 Vue 响应式原理中的外围,在 Computed、Watch、Reactive 中都有呈现
次要和 Reactive(Proxy)、track、trigger 等函数配合实现收集依赖,触发依赖更新
-
Effect
- 副作用依赖函数
-
Track
- 依赖收集
-
Trigger
- 依赖触发
Effect
effect 能够被了解为一个副作用函数,被当做依赖收集,在响应式数据更新后被触发。
Vue 的响应式 API 例如 Computed、Watch 都有用到 effect 来实现
-
先来看看入口函数
- 入口函数次要是一些逻辑解决,外围逻辑位于 createReactiveEffect
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
}
- createReactiveEffect
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
Track 这个函数常呈现在 reactive 的 getter 函数中,用于依赖收集
源码详解见正文
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
Trigger 常呈现在 reactive 中的 setter 函数中,用于触发依赖更新
源码详解见正文
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)
}
Reactive
理解了 Track 用于依赖收集,Trigger 用于依赖触发,那么他们的调用机会是什么时候呢? 来看看 Reactive 的源码就分明了,源码详解见正文。
注:源码构造较为简单(封装),为便于了解原理,以下为简化源码。
-
总结来说
- 在 getter 时进行依赖收集
- 在 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 是 Vue 中罕用且好用的一个属性,这个属性的值在依赖扭转后同步进行扭转,在依赖未扭转时应用缓存的值。
-
Vue2
- 在 Vue2 中 Computed 的实现通过嵌套 watcher,实现响应式数据的依赖收集,间接链式触发依赖更新。
-
Vue3 中呈现了 effect,从新实现了 Computed 属性
- effect 能够被了解为副作用函数,被当做依赖收集,在响应式数据更新后被触发。
Show me the Code
-
读完这段 computed 函数会发现,这里只是做了简要的 getter 和 setter 的赋值解决
-
computed 反对两种写法
- 函数
- getter、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
}
-
外围逻辑都在 ComputedRefImpl 中,咱们接着往下看
- 通过 dirty 变量标记数据是否为旧数据
- 在响应式数据更新后将 dirty 赋值为 true
- 在下一次 get 时,dirty 为 true 时进行从新计算,并将 dirty 赋值为 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,
// 响应式数据更新后将 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
Watch 次要用于对某个变量的监听,并做相应的解决
Vue3 中不仅重构了 watch,还多了一个 WatchEffect API
- Watch
用于对某个变量的监听,同时能够通过 callBack 拿到新值和旧值
watch(state, (state, prevState)=>{})
- WatchEffect
每次更新都会执行,主动收集应用到的依赖
无奈获取到新值和旧值,可手动进行监听
onInvalidate(fn)
传入的回调会在watchEffect
从新运行或者watchEffect
进行的时候执行
const stop = watchEffect((onInvalidate)=>{
// ...
onInvalidate(()=>{// ...})
})
// 手动进行监听
stop()
watch 和 watchEffect 的不同点
- watch 惰性执行,watchEffect 每次代码加载都会执行
- watch 可指定监听变量,watchEffect 主动依赖收集
- watch 可获取新旧值,watchEffect 不行
- watchEffect 有 onInvalidate 性能,watch 没有
- watch 只可监听 ref、reactive 等对象,watchEffect 只可监听具体属性
Source Code
Show me the Code
- 这里能够看到 watch 和 watchEffet 的外围逻辑都封装到了 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
以下为删减版源码,了解外围原理即可
详情见正文
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
Mixin 意为混合,是公共逻辑封装利器。
原理比较简单,那就是合并。
-
合并分为对象的合并和生命周期的合并
-
对象,mergeOption
- 类型 Object.assign 的合并,会呈现笼罩景象
-
生命周期,mergeHook
- 合并会将两个生命周期放入一个队列,顺次调用
-
- 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)
)
// 对 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
}
- mergeHook
简略粗犷放进 Set,调用时顺次调用
function mergeHook(to: Function[] | Function | undefined,
from: Function | Function[]) {return Array.from(new Set([...toArray(to), ...toArray(from)]))
}
Vuex4
Vuex 是在 Vue 中罕用的状态治理库,在 Vue3 公布后,这个状态治理库也随之收回了适配 Vue3 的 Vuex4
疾速过 Vuex3.x 原理
- 为什么每个组件都能够通过
this.$store
拜访到 store 数据?
- 在 beforeCreate 时,通过 mixin 的形式注入了 store
-
为什么 Vuex 中的数据都是响应式的
- 创立 store 的时候调用的是
new Vue
, 创立了一个 Vue 实例,相当于借用了 Vue 的响应式。
- 创立 store 的时候调用的是
-
mapXxxx 是怎么获取到 store 中的数据和办法的
- mapXxxx 只是一个语法糖,底层实现也是从 $store 中获取而后返回到 computed / methods 中。
总的来看,能够把 Vue3.x 了解为一个每个组件都注入了的 mixin?
Vuex4 原理探索
去除冗余代码看实质
createStore
-
从 createStore 开始看起
- 能够发现 Vuex4 中的 state 是通过 reactive API 去创立的响应式数据,Vuex3 中是通过 new Vue 实例
- dispatch、commit 的实现根本是封装了一层执行,不必过于关怀
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})
// 省略若干代码...
}
install
- Vuex 是以插件的模式在 Vue 中应用的,在 createApp 时调用 install 装置
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
},
// 省略局部代码 ....
}
}
-
Store 类的 install
- 实现通过 inject 获取
- 实现 this.$store 获取
上面接着看 provide 实现
install (app, injectKey) {
// 实现通过 inject 获取
app.provide(injectKey || storeKey, this)
// 实现 this.$store 获取
app.config.globalProperties.$store = this
}
app.provide 实现
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)
}
}
Vue.useStore
- 在 Vue3 Composition API 中应用 Vuex
import {useStore} from 'vuex'
export default{setup(){const store = useStore();
}
}
- useStore 的实现
function useStore (key = null) {return inject(key !== null ? key : storeKey)
}
Vue.inject
- 通过 provide 时存入的 key 取出 store
- 有父级实例则取父级实例的 provides,没有则取根实例的 provides
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]
// 省略一部分代码......
}
}
Vue.provide
- Vue 的 provide API 也比较简单,相当于间接通过 key/value 赋值
- 以后实例 provides 和父级实例 provides 雷同时,通过原型链建设连贯
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
}
}
注入
-
为什么每个组件实例都有 Store 对象了?
- 在创立组件实例的时候注入了 provides
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 进行库开发 / 高阶用法,这里不过多赘述。
Diff 算法优化
理解 Vue3 的 Diff 算法优化前,能够先理解一下 Vue2 的 Diff 算法
本局部重视把算法讲清楚,将不进行逐行源码剖析
-
Vue3 中的次要优化点为
- 在 updateChildren 时双端比拟 -> 最长递增子序列
- 全量 Diff -> 动态标记 + 非全量 Diff
- 动态晋升
updateChildren
-
Vue2
- 头 – 头比拟
- 尾 – 尾比拟
- 头 – 尾比拟
- 尾 – 头比拟
-
Vue3
- 头 – 头比拟
- 尾 – 尾比拟
- 基于最长递增子序列进行挪动 / 删除 / 新增
举个🌰
- oldChild [a,b,c,d,e,f,g]
- newChild [a,b,f,c,d,e,h,g]
-
首先进行头 – 头比拟, 比拟到不一样的节点时跳出循环
- 失去[a,b]
-
而后进行尾 – 尾比拟,比拟到不一样的节点时跳出循环
- 失去[g]
-
残余[f,c,d,e,h]
- 通过 newIndexToOldIndexMap 生成数组[5, 2, 3, 4, -1]
- 得出最长递增子序列 [2, 3, 4] 对应节点为[c, d, e]
- 残余的节点基于 [c, d, e] 进行挪动 / 新增 / 删除
最长递增子序列 缩小 Dom 元素的挪动,达到起码的 dom 操作以减小开销。
对于最长递增子序列算法能够看看最长递增子序列
动态标记
Vue2 中对 vdom 进行全量 Diff,Vue3 中减少了动态标记进行非全量 Diff
对 vnode 打了像以下枚举内的动态标记
- patchFlag
export const 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>
- 生成 vdom 源码
对 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
总结
- 对 vnode 进行标记,将须要动静更新和不须要动静更新的节点进行分类
- 动态节点仅需创立一次,渲染间接复用,不参加 diff 算法流程。
动态晋升
- 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
cacheHandlers 事件侦听器缓存
- 默认状况下 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。
在应用的时候,如果能在缓存中找到这个办法,那么它将间接被应用。
如果找不到,那么将这个办法注入缓存。
总之,就是把办法给缓存了。
- 掘金:前端 LeBron
- 知乎:前端 LeBron
- 继续分享技术博文,关注微信公众号 👉🏻 前端 LeBron