current position:Home>Vue0.11 source code reading series 7: Supplement

Vue0.11 source code reading series 7: Supplement

2021-08-25 22:07:37 Corner Kobayashi

The first article leaves two questions :

1. How to trigger the update of the calculation property when the dependent property of the calculation property changes

2.watch Option or $watch What is the principle of the method

This article will analyze these two problems , In addition, take a brief look at how custom elements are rendered .

Compute properties

<p v-text="showMessage + ' I'm an unimportant string '"></p>
{
    data: {
        message: 'Hello Vue.js!'
    },
    computed: {
        showMessage() { 
            return this.message.toUpperCase()
        }
    }
}

Take this simple example , First, the calculated properties will also be attached to vue An attribute that becomes an instance on an instance :

for (var key in computed) {
    var userDef = computed[key]
    var def = {
        enumerable: true,
        configurable: true
    }
    if (typeof userDef === 'function') {
        def.get = _.bind(userDef, this)
        def.set = noop
    } else {
        def.get = userDef.get
            ? _.bind(userDef.get, this)
        : noop
        def.set = userDef.set
            ? _.bind(userDef.set, this)
        : noop
    }
    Object.defineProperty(this, key, def)
}

adopt this.xxx When accessing the calculated properties, we call the computed The function in the options .

Secondly, there is no difference between calculating attributes and ordinary attributes in the stage of template compilation instruction parsing , This v-text The instruction creates a Directive example , This Directive The instance will be initialized with showMessage + ' I'm an unimportant string ' Create a... For the unique flag Watcher example ,v-text The directive update The method will be used by this Watcher Collected by the instance , Added to its cbs In the array ,Watcher When instantiated, it assigns itself to Observer.target, And then showMessage + ' I'm an unimportant string ' This expression evaluates , It will also call the function that calculates the attribute showMessage(), After this function is called, it will reference all the properties it depends on , So this right here is message, This triggers message Of getter, So this Watcher The instance is added to message Dependent collection objects dep In the , Follow up when message A change in the value of triggers its setter It will traverse its dep Collected in Watcher example , Trigger Watcher Of update Method , Finally, it will traverse cbs Of the instructions added to the update Method , In this way, the instruction that depends on computing properties is updated .

It is worth noting that in this version , Calculated properties are not cached , Even if the dependent value does not change , Repeated reference to the value of the calculated attribute will also re execute the calculated attribute function we defined .

The listener

watch The listener of the option declaration is also invoked at last $watch Method , I already know in the first article $watch The main method is to create a Watcher example :

// exp Is the data we want to listen for , Such as :a、a.b
exports.$watch = function (exp, cb, deep, immediate) {
  var vm = this
  var key = deep ? exp + '**deep**' : exp
  var watcher = vm._userWatchers[key]
  var wrappedCb = function (val, oldVal) {
    cb.call(vm, val, oldVal)
  }
  if (!watcher) {
    watcher = vm._userWatchers[key] =
      new Watcher(vm, exp, wrappedCb, {
        deep: deep,
        user: true
      })
  } else {
    watcher.addCb(wrappedCb)
  }
}

about Watcher We are now familiar with , When instantiating, it will assign itself to Observer.target, Then trigger the evaluation of the expression , That is, the attribute we want to listen on , Trigger it gettter Then put the Watcher Collect its dependent collection objects dep in , As long as it's collected , This will be triggered after the subsequent attribute value changes Watcher Update , It will also trigger the above callback .

Custom component rendering

<my-component></my-component>
new Vue({
    el: '#app',
    components: {
        'my-component': {
            template: '<div>{{msg}}</div>',
            data() {
                return {
                    msg: 'hello world!'
                }
            }
        }
    }
})

In the first article, we mentioned that each component option will be created as an inheritance vue Constructor for :

image-20210114201622204

Then, in the template compilation phase, traversing the custom element will add a v-component attribute :

tag = el.tagName.toLowerCase()
component =
    tag.indexOf('-') > 0 &&
    options.components[tag]
if (component) {
    el.setAttribute(config.prefix + 'component', tag)
}

image-20210115100403284

Therefore, the user-defined component is also processed through instructions , Next, a link function will be generated ,component Belong to terminal A kind of instruction :

image-20210115100542982

Then we return to the normal instruction compilation process ,_bindDir The method will give v-component Instruction to create a Directive example , And then call component The directive bind Method :

{
    bind: function () {
        // el It's our custom element my-component
        if (!this.el.__vue__) {
            //  Create a comment element to replace the custom element 
            this.ref = document.createComment('v-component')
            _.replace(this.el, this.ref)
            //  Check for presence keep-alive Options 
            this.keepAlive = this._checkParam('keep-alive') != null
            //  Check for presence ref To reference the component 
            this.refID = _.attr(this.el, 'ref')
            if (this.keepAlive) {
                this.cache = {}
            }
            //  Parse constructor , That is, return the constructor generated in the option merging stage during initialization ,expression Here is the command value my-component
            this.resolveCtor(this.expression)
            //  Create a sub instance 
            var child = this.build()
            //  Insert the sub instance 
            child.$before(this.ref)
            //  Set up ref
            this.setCurrent(child)
        }
    }
} 

see build Method :

{
    build: function () {
        //  If there is a cache, return directly 
        if (this.keepAlive) {
            var cached = this.cache[this.ctorId]
            if (cached) {
                return cached
            }
        }
        var vm = this.vm
        if (this.Ctor) {
            var child = vm.$addChild({
                el: this.el,
                _asComponent: true,
                _host: this._host
            }, this.Ctor)// Ctor Is the constructor of the component 
            if (this.keepAlive) {
                this.cache[this.ctorId] = child
            }
            return child
        }
    }
}

This method is used to create sub instances , Called $addChild Method , Simplified as follows :

exports.$addChild = function (opts, BaseCtor) {
    var parent = this
    //  The parent instance is the above we new Vue Example 
    opts._parent = parent
       //  The root component is the root component of the parent instance 
    opts._root = parent.$root
    //  Create an instance of the custom component 
    var child = new BaseCtor(opts)
    return child
}

The above two methods mainly create an instance of the component constructor , Because the component constructor inherits vue, So before new Vue The initialization work will also be done again , What Observations 、 Traverse the custom component and all its child elements for template compilation, binding instructions, etc , Because we passed template Options , So the way to pass through in the first article _compile Calling in compile This will be handled before the method :

//  Here will be template The template string is converted to dom, The principle is simple , Create a document fragment , Create another div, Then set the template string to div Of innserHTML, Finally, put div All the elements in the document can be added to the document fragment 
el = transclude(el, options)
//  Compile and link the rest 
compile(el, options)(this, el)

Finally, if there is keep-alive Cache the instance , go back to bind In the method child.$before(this.ref)

exports.$before = function (target, cb, withTransition) {
    return insert(
        this, target, cb, withTransition,
        before, transition.before
    )
}
function insert (vm, target, cb, withTransition, op1, op2) {
    //  Get target element , Here is the bind Method 
    target = query(target)
    //  The element is not currently in the document 
    var targetIsDetached = !_.inDoc(target)
    //  Determine whether to use transition mode to insert , If the element is not in the document, it is inserted with a transition 
    var op = withTransition === false || targetIsDetached
    ? op1
    : op2
    //  If the target element has been inserted into the document and the component has not been mounted, you need to trigger attached Life cycle 
    var shouldCallHook =
        !targetIsDetached &&
        !vm._isAttached &&
        !_.inDoc(vm.$el)
    //  Inserted into the document 
    op(vm.$el, target, vm, cb)
    if (shouldCallHook) {
        vm._callHook('attached')
    }
    return vm
}

op Method will call transition.before Method to insert an element into the document , For a detailed analysis of transition insertion, please refer to vue0.11 Version source code reading series 6 : Transition principle .

At this point, the component has been rendered ,bind The last method was called setCurrent

{
    setCurrent: function (child) {
        this.childVM = child
        var refID = child._refID || this.refID
        if (refID) {
            this.vm.$[refID] = child
        }
    }
}

If we set a reference, such as :<my-component v-ref="myComponent"></my-component>, Then you can pass this.$.myComponent Access to this sub component .

keep-alive The working principle of is also very simple , Is to return the previous instance instead of creating a new instance , In this way, all States are still preserved .

summary

This series is basically over here , I'm sure not many people can see here , Because I wrote this series of source code reading for the first time , Overall, it's a little messy , In many places, the focus is not very prominent , The description may not be very detailed , It may not be very interesting to see , In addition, there will inevitably be mistakes , Welcome to point out .

Reading the source code is the only way that every developer can't go around , Whether it's to improve yourself or for an interview , After all, we should have a deeper understanding of what we are using all the time , This is also good for use , In addition, think and learn from others' excellent coding thinking , Can also make yourself better .

I have to say that reading the source code is very boring and boring , It's also difficult , It's easy to retreat , You can't understand many places if you don't know its function very well , Of course, we don't have to stick to these places , You don't have to read everything , A better way is to read with questions , For example, I want to understand the principle of a place , Then you can look at this part of the code , It's also interesting when you're immersed in it .

Don't talk much , White ~

copyright notice
author[Corner Kobayashi],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210825220729963g.html

Random recommended