“This is the 19th day of my participation in the First Challenge 2022, for more details: First Challenge 2022”.
preface
This article on Vue3.0 responsive source code for learning, Vue official document: in-depth responsive principle
One of the most unique features of Vue is its non-invasive and responsive system. Data models are proxied JavaScript objects. And when you modify them, the view is updated.
Responsive API experience
If you want to use reactive data in Vue3 setup, you can use the Reactive and Refs apis
<body>
<div id="app">
<h1>vue3 reactivity API</h1>
<p>{{ state.counter }}</p>
<p>{{ counter2 }}</p>
</div>
<script>
const app = Vue.createApp({
setup() {
const state = Vue.reactive({
counter: 1
})
setInterval(() = > {
state.counter++
}, 1000)
Vue.watch(() = > state.counter, () = >{})const counter2 = Vue.ref(1)
setInterval(() = > {
counter2.value++
}, 1000)
Vue.watch(counter2, () = >{})return {
state,
counter2
}
}
})
app.mount('#app')
</script>
</body>
Copy the code
Differences in use
-
Ref () takes a primitive data type and transforms it into a reactive feature, and Reactive () is a reactive feature for an Object
-
To use or modify ref data, add.value. To use or modify rective data, use it directly
-
The.value is not required if the ref is used directly in HTML after the export, and the object data wrapped in rective requires an outer key value, such as state.xxx
-
The data in rective can be used directly in HTML by toRef, which converts toRefs into ref exports
// Convert a single attribute
const state = reactive({
foo: 1.bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) / / 2
state.foo++
console.log(fooRef.value) / / 3
Copy the code
// Convert the entire object
const state = reactive({
foo: 1.bar: 2
})
const stateAsRefs = toRefs(state)
// the ref and the original property are "linked"
state.foo++
console.log(stateAsRefs.foo.value) / / 2
stateAsRefs.foo.value++
console.log(state.foo) / / 3
Copy the code
Reactive function source code parsing
-
Find the location defined by Reactive () packages\reactivity\ SRC \ reaction.ts
-
Reactive () returns the result of createReactiveObject(), which can be used to turn a normal object into a reactive object
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
// How do I convert an incoming plain object into a responsive object
return createReactiveObject(
target,
false,
mutableHandlers, /* Proxy handler for ordinary objects */
mutableCollectionHandlers,
reactiveMap
)
}
Copy the code
createReactiveObject()
To the first parametertarget
(i.e.reactive
Wrapped object) made a layerProxy
The agentMDN Proxy document- Generally, the incoming objects are ordinary objects, so in
Proxy
In thehandlerWill usebaseHandlers
Is in thereactive()
In the incomingmutableHandlers
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {...// Create a proxy object, using the original object passed as the proxy target
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
Copy the code
mutableHandlers
locationpackages\reactivity\src\baseHandlers.ts
, of which thegetter
和setter
An interception operation was performed
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
Copy the code
- getterIf the proxy object is an object and not read-only, the proxy object type will be executed
track()
To collect dependencies
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {...// Target is array processing
const targetIsArray = isArray(target)
if(! isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver)
}
// Target is an object
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// If it is not read-only
if(! isReadonly) {// Establish the relationship between target,key, and dependent functions
track(target, TrackOpTypes.GET, key)
}
if (shallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
constshouldUnwrap = ! targetIsArray || ! isIntegerKey(key)return shouldUnwrap ? res.value : res
}
// If the object is processed recursively
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
Copy the code
- tracklocation
packages\reactivity\src\effect.ts
, will put the collected dependencies intodepsMap
Object, and then trace the dependenciestrackEffects
Trigger an update at some point in the future
// Rely on collection
export function track(target: object, type: TrackOpTypes, key: unknown) {...let depsMap = targetMap.get(target)
if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}let dep = depsMap.get(key)
if(! dep) { depsMap.set(key, (dep = createDep())) } ...// Rely on trace
trackEffects(dep, eventInfo)
}
Copy the code
- After collecting dependencies, you can watch in a single step to see how the data changes trigger updates, which will be explained in the next article