More articles
preface
Reactive and ref have been mentioned before, and effect is the next step. Effect can be understood as a dependency collection process, and can be seen in code (in simplified code, you can see in index1.html).
Simplify the code
Vue3-effect source location
Depend on the collection
Dependencies are collected in both Reactive and REF. The GET and SET phases trigger track/trackEffects and trigger/triggerEffects respectively
Reactive: reactive
// get
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if(! isReadonly) { track(target, TrackOpTypes.GET, key) } } }// set
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
) :boolean {
if (target === toRaw(receiver)) {
if(! hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) }else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
}
}
Copy the code
Ref:
export function trackRefValue(ref: RefBase<any>) {
if (isTracking()) {
ref = toRaw(ref)
// Add dep attribute, Set type
if(! ref.dep) { ref.dep = createDep() } trackEffects(ref.dep) } }export function triggerRefValue(ref: RefBase<any>, newVal? :any) {
// DeP attribute exists, notifying dependency
if (ref.dep) {
triggerEffects(ref.dep)
}
}
class RefImpl<T> {
get value() {
trackRefValue(this)}set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
triggerRefValue(this, newVal)
}
}
}
Copy the code
The get phase triggers track or trackEffects to collect dependencies, while the set phase triggers triggers triggers triggers or triggerEffects to notify the viewer. In effect, track will eventually trigger trackEffects and trigger will eventually trigger triggerEffects. Therefore, the interpretation of effect from track can basically complete the analysis of the whole process. The reason not to start with trigger is that there is no dependency that can trigger a dependency
track
Track code is as follows (after source code simplification) :
const targetMap = new WeakMap(a)let activeEffect
const createDep = (effects) = > {
const dep = new Set(effects)
return dep
}
function isTracking() {
returnactiveEffect ! = =undefined
}
function track(target, type, key) {
if(! isTracking())return
// Whether there is a cache
let depsMap = targetMap.get(target)
// No cache storage, initialized to map
if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}// Get whether the current attribute is cached
let dep = depsMap.get(key)
// No cache storage, initialized to set
if(! dep) { depsMap.set(key, (dep = createDep())) } trackEffects(dep); }function trackEffects(dep) {
// Does it exist
letshouldTrack = ! dep.has(activeEffect)// There is no collection dependency
if(shouldTrack) { dep.add(activeEffect); activeEffect.deps.push(dep); }}Copy the code
After some optimization, activeEffect was finally added to the DEP. The data store relationship is as follows: TargetMap
-> depsMap
function effect(fn, options = {}) {
const _effect = new ReactiveEffect(fn)
// merge _effect exists
if(options) extend(_effect, options)
// Execute run initially
_effect.run()
const runner = _effect.run.bind(_effect)
runner.effect = _effect
// Return the runner so that the external can actively call run
return runner
}
Copy the code
ReactiveEffect is instantiated and run is executed when an initial (or appropriate) call to Effect is made, and the current ReactiveEffect instance is assigned to activeEffect when run is executed (code in the ReactiveEffect section), Finally, the runner can be returned, not only when the data is updated, but also can be actively triggered at the appropriate time. Here involves ReactiveEffect, look at the implementation:
class ReactiveEffect {
active = true
deps = []
constructor(fn, scheduler) {
this.fn = fn
// Customize scheduler if triggerEffects are triggered, otherwise fn is executed
this.scheduler = scheduler
}
run() {
// Assign the current instance to activeEffect
activeEffect = this
return this.fn()
}
stop() {
if(this.active) {
cleanupEffect(this)
this.active = false}}}/ / clear the effect
function cleanupEffect(effect) {
effect.deps.forEach((dep) = > dep.delete(effect))
effect.deps.length = 0
}
function stop(runner) {
runner.effect.stop();
}
Copy the code
Regardless of other considerations, the simplest process of track is as follows:
const targetMap = new WeakMap(a)class ReactiveEffect {
active = true
deps = []
constructor(fn) {
this.fn = fn
}
run() {
activeEffect = this
return this.fn()
}
}
function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
const runner = _effect.run.bind(_effect)
return runner
}
function track(target, key) {
let depsMap = targetMap.get(target)
let dep = depsMap.get(key)
trackEffects(dep);
}
function trackEffects(dep) {
letshouldTrack = ! dep.has(activeEffect)if(shouldTrack) { dep.add(activeEffect); activeEffect.deps.push(dep); }}Copy the code
Scenario assumptions
For the simplest scenario, update the DOM with id=app:
const state = reactive({ a: 100 })
effect(
() = > document.getElementById('app').innerText = state.a
)
Copy the code
When effect is called, fn is () => document.getelementById (‘app’).innertext = state.a, activeEffect is initialized. The current activeEffect is added to the DEP when state.a is called
If trigger triggers run in the activeEffect, the DOM can be updated as follows:
Get -> track -> Collect ActiveEffects
Set -> trigger -> Tells activeEffect
Next look at trigger
trigger
function trigger(target, type, key, newValue, oldValue) {
// Get the cache
const depsMap = targetMap.get(target)
// No cache means that track is never triggered, so return directly
if(! depsMap)return
let deps = [];
if(key ! = =void 0) deps.push(depsMap.get(key))
const effects = []
// Retrieve set data
for (const dep of deps) {
if(dep) { effects.push(... dep) } } triggerEffects(createDep(effects)) }function triggerEffects(dep) {
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect.scheduler) {
effect.scheduler();
} else{ effect.run(); }}}Copy the code
if (! DepsMap) return is an important action. If the value is not in the targetMap, track is not triggered, i.e. there is no observer (no notification object). Trigger will extract all effects and trigger triggerEffects. Scheduler is triggered when the developer passes in a scheduler, otherwise run -> fn is triggered
ref
The ref module adds a deP attribute and calls trackEffects or triggerEffects directly
Get -> trackEffects -> Collect activeEffects
Set -> triggerEffects -> Tell activeEffect
conclusion
With simplified code operation for easy understanding