mini-vue
By implementing a Mini-VUe3 to deepen my understanding of VUE, learn slowly and record what I have learned
This Mini-vue3 is learned through The Git warehouse of Cui Xiaorui. Teacher Ruan Yifeng recommended to learn Vue3
The warehouse address: Mini-Vue3
Source: the mini – vue
Pure own learning, if there are mistakes, but also hope you correct, including
In the study-every-day warehouse, there are big boss brain map, need to take oh ~
Vue3 dependency graph module
1. Responsive systemsreactivity
Speaking of VUE, we all know about vUE’s two-way data binding, which allows us to manipulate data and pages declaratively based on the MVVM model
Vue as the middle layer, responsive system, is an indispensable part, first to achieve a simple version of the reactivity responsive system module, based on this, to facilitate the implementation of the following functions
In Vue3, the ReActivity module, as a separate module, can be provided as a tool for others to use, so it is relatively independent and easy to read and understand what it is doing
1.1 Proxy Proxy object
Responsivity in VUe2 is implemented via get/set of the Object.defineProperty Api
Vue3 is implemented through the Proxy object. Why do YOU need to change it? There are many articles in this article.
-
Based on Proxy Reflect reflection, can be agents in almost all of object’s behavior, there are a dozen, including the get/set/deleteProperty/definePropertygetOwnPropertyDescriptor etc, we should try to be no dead Angle response type
-
Secondly, Proxy is the current research direction of browser, and its performance will only be better and faster in the future
For details, go to MDN to learn about Proxy
1.2 Reactivity
We have the Proxy to know the object to Proxy, can know the object’s property value changes, so when to collect related dependencies, create Proxy object?
These are functions that we’re familiar with
Reactive/Readonly these methods wrap our object as a proxy object and create getter/setter methods
The minimalist version
export function reactive(raw) {
return new Proxy(raw, {
get(target, key) {
const res = Reflect.get(target, key)
// Do dependency tracing
track(target, key)
return res
}
}),
set(target, key, value) {
const res = Reflect.set(target, key, value)
// Trigger the update
trigger(target, key)
return res
}
}
Copy the code
Since we need to distinguish whether isReadonly is a read-only object, we provide an identifier through a closure
function createGetter(isReadonly = false) {
return function get(target, key) {
const res = Reflect.get(target, key)
// Dependency tracing, since read-only objects do not change, we do not need to trace
if(! isReadonly) { track(target, key) }return res
}
}
Copy the code
1.3 Effect Side effect function
By using Reactive/Readonly we get the proxy object, and it should be noted that there are two functions, track/trigger, that do dependency tracking and data change notification
Our responsive system needs to create dependencies through the effect side effect function, in vue’s case the render function, which accesses the properties of the proxy object and triggers get to get our dependencies, Then, when the data changes, the render function in the set can be told to create a new VNode to compare the diff algorithm and update the DOM
This is a simple version of the model and the implementation of the data structure, in the source code, VUE will be fn abstract into a class, encapsulation, convenient to achieve more functions
Look at the code
/ * * *@description Side effects function, collect dependencies *@param { Function } fn* /
export function effect(fn, options?) {
// 1. Initialize
const _effect = newReactiveEffect(fn, options? .shceduler) extend(_effect, options)// 2. Call the 'run' method, that is, call fn to trigger the internal 'get/set'
_effect.run()
// 3. Return 'runner' function
const runner: any = _effect.run.bind(activeEffect)
runner.effect = _effect
return runner
}
Copy the code
ReactiveEffectabstractedfn
class
/ * * *@description The collected dependent function class */
export class ReactiveEffect {
private _fn: () = > void
publicshceduler? :() = > void | undefined
deps: any[] = []
constructor(fn: () => void, shceduler? : () = >void) {
this._fn = fn
this.shceduler = shceduler
}
run() {
// 1. Set the target of dependency collection to the current instance
activeEffect = this
// 2. Execute 'fn' and call internal 'get' to collect 'fn'
const result = this._fn()
return result
}
}
Copy the code
track: Collects dependent functions
/ * * *@description When the 'get' method is called, dependency collection * is performed@param Target The object currently tracked *@param Key Indicates the current access key */
export function track(target, key) / /console.log('trigger track -> target:${target} key:${key}`) // Get the current tracing objectlet depsMap = targetMap.get(target) // Check whether a dependency exists. If not, add itif (! depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// @desc: get the dependency of the current object's 'key'
let dep = depsMap.get(key)
// If no, add one
if(! dep) { dep =new Set()
depsMap.set(key, dep)
}
// @desc: Manually trigger 'track' to allow others to join a responsive system such as' ref '
trackEffects(dep)
}
export function trackEffects(dep) {
// @desc: If it has already been added, avoid adding it again
if(! dep.has(activeEffect)) {// Add the dependency to the corresponding 'dep'
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
Copy the code
**trigger: ** Trigger dependent change method
/ * * *@description When the 'set' method is called, the change function * is triggered@param Target The object currently tracked *@param Key Indicates the current access key */
export function trigger(target, key) {
// console.log(' trigger trigger -> target: ${target} key:${key} ')
const depsMap = targetMap.get(target)
const dep = depsMap.get(key)
// @desc: Manually trigger 'trigger' so that others can join the responsive system, such as' ref '
triggerEffects(dep)
}
export function triggerEffects(dep) {
for (const effect of dep) {
if (effect.shceduler) {
effect.shceduler()
} else {
effect.run()
}
}
}
Copy the code
1.4 API implementation
The basic shelf is set up, through the model and basic introduction, there is a basic concept, the following to specific implementation of Vue3 in various apis
effect
Look at the basic implementation
/ * * *@description Side effects function, collect dependencies *@param { Function } fn* /
export function effect(fn, options?) {
// 1. Initialize
const _effect = newReactiveEffect(fn, options? .shceduler)// Object.assign makes it easy to add attributes to instances, such as onStop, to listen for callbacks to exit dependent systems
extend(_effect, options)
// 2. Call the 'run' method, that is, call fn to trigger the internal 'get/set'
_effect.run()
// 3. Return 'runner' function
const runner: any = _effect.run.bind(activeEffect)
runner.effect = _effect
return runner
}
Copy the code
Effect side effects need to be used with dependencies, also known as reactiveeffects. Let’s look at collecting dependencies in detail
/ * * *@description The collected dependent function class */
export class ReactiveEffect {
private _fn: () = > void
After the first reactive trigger, let the user decide what to do with subsequent set operations
publicshceduler? :() = > void | undefinedonStop? :() = > void
deps: any[] = []
active: boolean = true
constructor(fn: () => void, shceduler? : () = >void) {
this._fn = fn
this.shceduler = shceduler
}
run() {
// After executing 'stop', dependency collection should be avoided and the dependency collection switch should not be enabled
// Fn function execution rights are retained because it exits the reactive system
if (!this.active) {
return this._fn()
}
// 1. Enable dependency collection
shouldTrack = true
// 2. Set the target for dependency collection
activeEffect = this
// 3. Execute 'fn' and call internal 'get' to collect 'fn'
const result = this._fn()
// 4. Disable the dependency collection function
shouldTrack = false
return result
}
Exit the responsive system
stop() {
// Whether in a responsive system
if (this.active) {
clearupEffect(this)
// If a callback is given, the callback is performed
if (this.onStop) this.onStop()
this.active = false}}}Copy the code
ActiveEffect is set when the _fn method is called in the run method, which is passed in at the time of the call, and then collected into the reactive mapping table, which is the track method of get
/ * * *@description When the 'get' method is called, dependency collection * is performed@param Target The object currently tracked *@param Key Indicates the current access key */
export function track(target, key) {
// @desc: No collection status, return directly
if(! isTracting())return
// console.log(' trigger track -> target: ${target} key:${key} ')
// Get the current trace object 'targetMap' is a global variable used to manage the entire project
let depsMap = targetMap.get(target)
// Check whether a dependency exists. If not, add it
if(! depsMap) { depsMap =new Map()
targetMap.set(target, depsMap)
}
// @desc: get the dependency of the current object's 'key'
let dep = depsMap.get(key)
// If no, add one
if(! dep) { dep =new Set()
depsMap.set(key, dep)
}
// @desc: Manually trigger 'track' to allow others to join a responsive system such as' ref '
trackEffects(dep)
}
export function trackEffects(dep) {
// @desc: If it has already been added, avoid adding it again
if(! dep.has(activeEffect)) {// Add the dependency to the corresponding 'dep'
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
export function isTracting() {
return shouldTrack && activeEffect
}
Copy the code
The trigger in the set is invoked when the next responsive object changes
/ * * *@description When the 'set' method is called, the change function * is triggered@param Target The object currently tracked *@param Key Indicates the current access key */
export function trigger(target, key) {
// console.log(' trigger trigger -> target: ${target} key:${key} ')
const depsMap = targetMap.get(target)
const dep = depsMap.get(key)
// @desc: Manually trigger 'trigger' so that others can join the responsive system, such as' ref '
triggerEffects(dep)
}
export function triggerEffects(dep) {
for (const effect of dep) {
if (effect.shceduler) {
// If the user needs to own the operation, use this scheme
effect.shceduler()
} else {
effect.run()
}
}
}
Copy the code
reactive/readonly
These two methods are used to create reactive objects, and this is a Proxy object
// Reuse logic through encapsulation
export function reactive(raw) {
return createActiveObject(raw, mutableHandlers)
}
export function readonly(raw) {
return createActiveObject(raw, readonlyHandlers)
}
function createActiveObject(raw: any, baseHandler) {
return new Proxy(raw, baseHandler)
}
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key) {
// isReactive()
if (key === ReactiveFlag.IS_REACTIVE) {
return! isReadonly }// isReadonly()
if (key === ReactiveFlag.IS_READONLY) {
return isReadonly
}
const res = Reflect.get(target, key)
// Whether shallow proxy, if yes directly return
if (shallow) {
return res
}
// Deep proxy
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
// Do dependency tracing
if(! isReadonly) { track(target, key) }return res
}
}
function createSetter(isReadonly = false) {
return function set(target, key, value) {
if (isReadonly) {
// Cannot be set, an error message is reported
console.warn(`Cannot be edited key: The ${String(key)}, it is readonly`)
return true
}
const res = Reflect.set(target, key, value)
// Trigger the update
trigger(target, key)
return res
}
}
// Avoiding repeated calls to 'createGet/set' can be done by caching
const get = createGetter()
const set = createSetter()
const readonlyGet = createGetter(true)
const readonlySet = createSetter(true)
export const mutableHandlers = {
get,
set
}
export const readonlyHandlers = {
get: readonlyGet,
set: readonlySet
}
Copy the code
And the response of the shallow shallowReactive/shallowReadonly type object
shallowReactive/shallowReadonly
/ / Readonly similarly
export function shallowReadonly(raw) {
return createActiveObject(raw, shallowReadonlyHandlers)
}
const shallowReadonlyGet = createGetter(true.true)
// set, use readonly, because no new values can be set
export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
get: shallowReadonlyGet
})
Copy the code
isReactive/isReadonly/isProxy
// See if there is a closure flag by triggering get
export const enum ReactiveFlag {
IS_REACTIVE = '__v_reactive',
IS_READONLY = '__v_readonly'
}
// When creating a reactive object, it is operated through a closure, so whether or not it is a reactive object can be provided through the closure identifier
// if (key === ReactiveFlag.IS_REACTIVE) {
// return ! isReadonly
// }
// if (key === ReactiveFlag.IS_READONLY) {
// return isReadonly
// }
export function isReactive(value) {
return!!!!! value[ReactiveFlag.IS_REACTIVE] }export function isReadonly(value) {
return!!!!! value[ReactiveFlag.IS_READONLY] }export function isProxy(value) {
return isReactive(value) || isReadonly(value)
}
Copy the code
ref
Reactive is recommended because it has its drawbacks. It involves differences in assignment, such as direct assignment and missing response, which is the difference between variable assignment and attribute assignment. Ref changes the value property of the object, so the reactive will exist no matter how it is changed, while reactive is directly assigned to the variable, which is the memory reference of the changed variable
Look at the simplified implementation
class RefImpl {
private _value: any
private _raw: any
public dep: Set<ReactiveEffect>
public __v_isRef = true
constructor(value) {
this._raw = value
this._value = convert(value)
this.dep = new Set()}get value() {
// How to add responsive, manual 'track', then need to own 'trigger'
trackRefValue(this)
return this._value
}
set value(newValue) {
if (hasChanged(this._raw, newValue)) {
this._raw = newValue
this._value = convert(newValue)
triggerEffects(this.dep)
}
}
}
function convert(value) {
return isObject(value) ? reactive(value) : value
}
function trackRefValue(ref) {
if (isTracting()) {
trackEffects(ref.dep)
}
}
export function ref(value) {
return new RefImpl(value)
}
export function isRef(ref) {
return!!!!! ref.__v_isRef }export function unRef(ref) {
return isRef(ref) ? ref.value : ref
}
export function proxyRef(objectWithRef) {
return new Proxy(objectWithRef, {
get(target, key) {
// get operation to provide the result of unpacking
return unRef(Reflect.get(target, key))
},
set(target, key, value) {
// If the new value is ref directly, if not, value needs to be assigned
if(isRef(target[key]) && ! isRef(value)) {return (target[key].value = value)
}
return Reflect.set(target, key, value)
}
})
}
Copy the code
computed
With computed, it’s really clever to use a switch to manipulate whether or not you want to use a cache. You don’t call get, you don’t trigger a function to get a value. It’s lazy, and it uses shceduler so that it can be lazy and get a new value even after it changes
class ComputedRefImpl {
private _getter: any
private _value: any
private _dirty = true
private _effect: ReactiveEffect
constructor(getter) {
this._getter = getter
// @tips:
// 1. With 'effect', responsive object changes trigger 'getters' on their own, so' _dirty 'is meaningless
// 2. So use 'shceduler' to customize the operation after the dependency collection
// 3. Set '_dirty' to 'true' to get the latest value the next time you call 'get'
this._effect = new ReactiveEffect(getter, () = > {
this._dirty = true})}get value() {
// @desc: Use the switch to avoid repeatedly calling 'getter' and cache the return value '_value'
if (this._dirty) {
this._dirty = false
return (this._value = this._effect.run())
}
return this._value
}
}
// @TODO setter
export function computed(getter) {
return new ComputedRefImpl(getter)
}
Copy the code
1.5 summarize
- By implementing the short version, we read
vue
The source code, also can be more handy - The implementation of the reactive system version is relatively simple compared to other packages, and we use the model, and
get/set
In a way that triggerstrack/trigger
, dependency collection and trigger updates, can clearly understand this mode, I have to say simple and easy to use,computed
The implementation is also very skilled, really need you to play their own imagination, they can also go to do more useful tools