[Vue Principle] Events of generate Splice Binding in Compile-Source Edition

Posted by IanM on Tue, 13 Aug 2019 09:18:32 +0200

It's not easy to write articles. Give a compliment to your brother.


Focus on Vue source code sharing, the article is divided into vernacular version and source version, vernacular version helps to understand the working principle, source version helps to understand the internal details, let's learn together.
Research based on Vue version [2.5.17]

If you think the typesetting is ugly, please click on the link below or pull down to pay attention to the public number below.

[Vue Principle] Events of generate Splice Binding in Compile-Source Edition

Today we're going to explore the splicing of events. In the last article, we talked about the splicing of node data in generation phase.

Events are part of it, but because there are too many, too many, too many, I plan to talk about them separately.

This is the last article of Compile! Eventually it's finished.... It's really disgusting. I don't think I can find such a detailed compile article on the internet.

This article is very long, more than 10,000 words, but it is all code and examples to help us understand, nothing, all the way down the wall without pressure, the source code is simplified.

Okay, let's start with the text.

As we know from the previous article, node data splicing calls the genData method.

Now let's review this approach (only event-related is retained)

function genData$2(el, state) {   



    var data = '{';


   .....Other attribute splicing has been omitted

   // Event
   if(el.events) {
        data += genHandlers(el.events, false) + ",";
   } 

   // Primary events
   if(el.nativeEvents) {
        data += genHandlers(el.nativeEvents, true) + ",";
   }



   data = data.replace(/,$/, '') + '}';   



   return data

}

Yes, events are divided into component native events and events

Component native events

As the name implies, it's a native DOM event that binds components, like this

Event

This includes a wide range of custom events on components and native events on labels, as follows

The methods they call are genHandlers, which splice the result values directly onto the data. What's the difference?

The difference is the last parameter passed to genHandlers. Now let's cut the perspective to the genHandlers method.

genHandlers

function genHandlers(
    events, isNative

) {    

    var res = isNative ? 'nativeOn:{': 'on:{';    

    var handler = events[name]    



    for (var name in events) {

        res += ` ${name} : ${ genHandler(name,handler )} ,`
    }    



    return res.slice(0, -1) + '}'

}

It's very obvious here.

When you pass in isNative=true, the event is the component's native DOM event, which needs to be stitched together on the nativeOn:{} string

Otherwise, it's an event, spliced onto the on:{} string

Let's start with a simple example to familiarize ourselves with.

The spliced string looks like this

`_c('div', {
    on: { "click": aa }
},[
    _c('test', {
        on: { "test": cc },
        nativeOn: {
            "input":$event=>{
                return bb($event)
            }
        }
    })
])`

Okay, you probably have an idea. Now we'll explore the intricate splicing of events.

Because there are many modifiers involved

Before this time, let's introduce the key values that have been set in Vue.

Key internal configuration

Vue configures several commonly used keys. Of course, you can override the configurations by setting the keynames internally.

It's very simple. After a while, there's nothing to say, but I'll use it later.

Keep in mind that keyCodes and keyName are variables that configure keyboards inside Vue

// Keyboard and code mapping

var keyCodes = {    

    esc: 27,  tab: 9,    

    enter: 13, space: 32,    

    up: 38, left: 37,    

    right: 39, down: 40,    

    delete: [8, 46]

};



// Keyboard name mapping

var keyNames = {    

    esc: 'Escape',    

    tab: 'Tab',    

    enter: 'Enter',    

    space: ' ',  // IE11 uses keynames without Arrow prefix

    up: ['Up', 'ArrowUp'],    

    left: ['Left', 'ArrowLeft'],    

    right: ['Right', 'ArrowRight'],    

    down: ['Down', 'ArrowDown'],    

    delete: ['Backspace', 'Delete']

};

After looking at the key configuration of Vue, let's look at the modifier for the internal configuration of Vue.

Modifier internal configuration

Believe that you have used modifiers in the project, it is very convenient, right, is the following stop stop stop stop stop stop, it is very convenient, right?

It's not too convenient. In fact, Vue doesn't do much. It just helps us assemble a few sentences automatically.

Take a look at the modifier for the Vue configuration

var modifierCode = {    

    stop: '$event.stopPropagation();',    

    prevent: '$event.preventDefault();',    

    ctrl: genGuard("!$event.ctrlKey"),    

    shift: genGuard("!$event.shiftKey"),   

    alt: genGuard("!$event.altKey"),    

    meta: genGuard("!$event.metaKey"),    

    self: genGuard("$event.target !== $event.currentTarget"),    

    left: genGuard("'button' in $event && $event.button !== 0"),    

    middle: genGuard("'button' in $event && $event.button !== 1"),    

    right: genGuard("'button' in $event && $event.button !== 2")

};

Like stop, prevent can be spliced directly into callbacks

For example, if the event name you bind is aaa roar and the modifier stop is used, it will be spliced like this.

"function($event ){ " + 

    "$event.stopPropagation();" + 

    " return "+ aaa +"($event);" +

"}"

You must have seen that the other modifiers use a function genGuard. Look at it right away.

var genGuard = function(condition) {    

    return ` if ( ${ condition } ) return null `

}

This function is simple to stir-fry chicken, which is to give your event callback stitching execution conditions (when you use the relevant modifier)

For example, when ctrl is used, it will be stitched together

if( !$event.ctrlKey ) return null

That is to say, when you press not ctrl, return directly without executing the following

In the middle of a mouse, for example, it splices together.

if( "'button' in $event && $event.button !== 1" ) 
return null

When the mouse clicks on a button that is not 1, return directly and do not execute the following (the following will execute the method you bind)

Keyboard modifier

Now let's take a look at how Vue splices the modifiers on your keys and see a way to do that.

This method, which will be used later, is predicted here, remember.

function genKeyFilter(keys) {    



    var key = keys.map(genFilterCode).join('&&')   



    return ` if( !('button' in $event) && ${ key } )
             return null `

}

This method is similar to the previous method in terms of stitching content, except that the conditions are different.

` if( !('button' in $event) && ${ key } )  
return null `

This key must be a very important condition. How do you get it? Two things.

keys,genFilterCode

Now let's talk about it one by one.

1 Parameter keys

It's an array that holds the key modifier you added, which can be a number or a key name, such as

So keys = ['enter', 86], where enter is the enter key, which is defined internally in Vue, 86 is the value of the key name v.

It then traverses keys and calls genFilterCode one by one to process each key value.

Now look at the source code.

The function genFilterCode that appears in 2

function genFilterCode(key) {    



    // Binding modifier is a number

    var keyVal = parseInt(key);    



    if (keyVal) {        

        return "$event.keyCode!==" + keyVal

    }    



    // The bound modifier is the key name

    var keyCode = keyCodes[key];    

    var keyName = keyNames[key];  



    return `
        _k(
            $event.keyCode , 
            ${ key } , ${ keyCode }, 
            ${ $event.key } , ${ keyName }
        )
    `

}

If the parameter key is a number

That's easy. Return the string directly.

"$event.keyCode!==" + keyVal

The callback can then be spliced together with an execution condition, such as the key being a number 86.

if( !('button in $event) && $event.keyCode!== 86 )  return null

This means that when you press a key that is not 86, you return it, because you are listening for 86. Pressing other keys is definitely not going to be executed.

If the parameter key is the key name

If your key is a name, it becomes NAN after parseInt, and you go to the next step.

Oh, yeah, it seems to go straight back and get the key value and the key name from the keyName and keyCode objects.

The keyName and keyCode have been listed before. They are two large objects. Forget to flip them up. What do you get from the variables inside the Vue?

For example, your key name is enter, after getting it

So keyName = Enter, keyCode = 13

However, this key name does not necessarily exist in the keyName and keyCode defined by Vue itself, so it may also be undefined that you get from these two variables.

Because of this key name, you can customize it

Custom key name

You can see the official documents specifically.

https://cn.vuejs.org/v2/api/#...

3genFilterCode return value

Now let's look at his return value.

`_k(
    $event.keyCode ,    

    ${ key } , 

    ${ keyCode },

    $event.key , 

    ${ keyName }

)`

Look at the strings above. Let's talk about them one by one.

1 where parameters

Five parameters, key is the key name or key value that you bind, as mentioned above in keyName and keyCode

The remaining two parameters

$event.keyCode / $event.key

Event is the event object, needless to say

$event.keyCode is the value of the key you pressed

event.key is the name of the key you pressed

For example, if you press v, the event object will contain information about the key.

What is method _k?

If the function _k is returned, such as pressing v, the execution condition of the event callback becomes

if( 
    !('button' in $event) &&
    _k(
        $event.keyCode ,        

        'v' , undefined,

        $event.key  , undefined
    )
)  return null

That is to say, every time you press the keyboard, you call _k to check the keys.

_ The ontology of k is actually checkKeyCodes, the source code is as follows

function checkKeyCodes(
    eventKeyCode, key, keyCode, 
    eventKeyName, keyName

) {    



    // For example, key imports a custom name aaaa

    // keyCode gets the actual number of aaaa from keyNames defined by Vue
    // keyName gets the alias of aaaa from keyCode defined by Vue
    // And with user-defined criteria, it can override Vue's internal definitions.
    var mappedKeyCode = config.keyCodes[key] ||keyCode;    



    // The key is only in the keyCode defined inside the Vue 

    if (keyName && eventKeyName && !config.keyCodes[key]) {        

        return isKeyNotMatch(keyName, eventKeyName)

    } 

    // This key is only in the user-defined keyCode
    else if (mappedKeyCode) {        

        return isKeyNotMatch(mappedKeyCode, eventKeyCode)

    } 

    // Original key name
    else if (eventKeyName) {        

        return hyphenate(eventKeyName) !== key

    }
}

At first glance, it seems very complicated? In fact, the function is to check the key.

For example, if you bind v, when you press v, this function returns false.

If the other key is pressed, it returns true, and the callback execution condition is true, so it returns null directly without executing the callback bound below.

Panwai:'button'in $event

When the mouse is pressed, the expression is true.

When the keyboard is pressed, the event object does not have the button value.

So [!'button'in $event] means that the button must be pressed without a mouse.

But this condition only exists in [non-built-in modifiers], that is to say, ctrl bonds do not have this condition.

So you can bind click + ctrl events, such as

When binding like this, direct click is invalid, you must press ctrl and click again

But you can't bind click + normal keys, such as click+v

Even if you're so bound, it's useless. A direct click will trigger without v.

Now let's move on to the checkKey Codes above.

1 config.keyCodes

This is the key list you configure. Here is my configuration.

2 function isKeyNotMatch

Check whether the keys pressed match the key-value pairs configured by the [discord]

function isKeyNotMatch(
    expect, actual

) {    



    if (Array.isArray(expect)) {        

        return expect.indexOf(actual) === -1

    } else {        

        return expect !== actual

    }
}

Notice that the matches inside are===-1 and!==, which means that the mismatch returns true and the match returns false.

For example, the key you press is enter, and the Vue is internally configured with {enter:"Enter"}

The parameter actual is the key name obtained by calling the event object passed in by isKeyNotMatch and is also "Enter"

So call this function.

isKeyNotMatch( "Enter" , "Enter" )

If the match is successful, it returns false, that is, checkKeyCode (_k) returns false, and the callback executes a filter condition that is false and does not return null, then the following will be executed

The same is true for configuring arrays

3 The function hyphenate that appears

var hyphenate = function(str) {    
    return str.replace(/\B([A-Z])/g, '-$1').toLowerCase()
}

Change Hump to Name

The official Vue document says that hump modifiers are not supported

Matching order of 4 checkKeyCodes

1. Match the key-value pairs of Vue internal configuration first

But to ensure that this key does not exist in user redefinition, because user-defined is the main requirement.

2. Matching user-defined key-value pairs

3. Matching the original key-value pairs

That is to say, you can write the keyname on the keyboard directly.

Like this, the keys v and b are bound.

Ouch, we've talked so much about it. Now we're ready to start our main course.

The female pig foot genHandler that appeared in the first genHandlers is used to process the modifier one by one.

genHandler

This function is a bit long to deal with modifiers, but the content is not complicated and the logic is clear, but it must be annoying at first sight. Although I have greatly simplified it, you can skip to the later analysis and take a look at it.

function genHandler(name,handler) {    



    // No bound callback returns an empty function
    if (!handler) {        

        return 'function(){}'

    }    



    // If the bindings are arrays, they are normalized one by one.
    if (Array.isArray(handler)) {    
    

        return "[" + handler.map(handler) => {            

            return genHandler(name, handler);

        }).join(',') + "]"



    }    



    // Start parsing individual callbacks

    var isMethodPath = simplePathRE.test(handler.value);    

    var isFunctionExpression = fnExpRE.test(handler.value);    



    // No modifier

    if (!handler.modifiers) {     

   

        // Is a method, or has a function keyword, which can be used as a callback directly
        if (isMethodPath || isFunctionExpression) {            

            return handler.value

        }        



        // Inline statement, need to wrap a layer
        return "function($event){" + handler.value + ";}" 
    } 

    else {        



        var code = ''; // Save key modifiers

        var genModifierCode = ''; // Save internal modifiers
        var keys = [];        



        for (var key in handler.modifiers) { 

          

            if (modifierCode[key]) {

                ....Pull it out and put it in the back.
            } 
            
            // Precise System Modifier
            else if (key === 'exact') {
                ....Pull it out and put it in the back.
            } 

            // Ordinary keys
            else {
                keys.push(key);
            }
        }  

      

        // Start splicing event callback!!!



        // Key modifiers outside the definition of splicing Vue
        if (keys.length) {
            code += genKeyFilter(keys);
        }   

    

        // Perform modifiers like prevent and stop after keystroke filtering

        if (genModifierCode) {
            code += genModifierCode;
        }        



        // Event callback subject

        var handlerCode = 
              isMethodPath                



              // Execute the functions you bind

              ? "return " + handler.value + "($event)"
              : (
                  isFunctionExpression                    



                  // If the callback contains the function keyword, the function is also executed

                  ? "return " + handler.value + "($event)"
                  : handler.value;
              )        



        return ` function($event) { 

              ${ code + handlerCode }         
        } `

    }


}

Let's start by parsing the above code a little bit

1. Regularity of Appearance

// Contains function keywords

var fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;



// Common method name

var simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/;

Without explaining so much, just look at the examples.

fnExpRE Specially Matches Function Body

SimePathRE Specialized Matching Function Name

So the isMethodPath in it indicates whether the event-bound value is a function name or not.

isFunctionExpression indicates whether the bound value is a function body

2 Binding handler.modifiers

In the parse phase, the bound events are resolved, and all modifiers are resolved into an object

such as

So handler.modifiers can traverse directly to get each modifier

3 Collect internal modifiers

This is the extracted code.

if (modifierCode[key]) {    

    // Event modifiers within splicing

    genModifierCode += modifierCode[key];
}

First determine whether the modifier exists in the modifier inside the Vue

As for modifier Code, you can see above. It's already recorded.

For example, roar

So splicing modifier roar, genModifierCode is

` 
  $event.stopPropagation(); 
  if( 'button' in $event && $event.button !== 1" ) 
    return null

`

4 Collection system modifier key exact

Maybe you don't know what the system modifier key is. I didn't know it from the beginning. I read the source code before I knew it was there. Here's the exact

Official Document Address: https://cn.vuejs.org/v2/guide...

In my understanding, canceling the key combination means

For example, if you bind the key v, how many trigger conditions are there?

1. Press v key directly

2. ctrl + v, shift +v, alt +v and so on

If you add the modifier exact

The trigger condition, then, is triggered only when you press this key, which means that pressing other keys together is invalid.

Look at the source code extracted from genHandler

else if (key === 'exact') {    



    var modifiers = handler.modifiers;


    genModifierCode +=

    genGuard(
        ['ctrl', 'shift', 'alt', 'meta']        



        // Filter to get the keys without modifiers written

        .filter(keyModifier = >{            

            return ! modifiers[keyModifier];

        })
    
        .map(keyModifier = >{            

            return "$event." + keyModifier + "Key"

        })

        .join('||')
    );
}

Look at the source code, there are four matching key combinations

ctrl , shift , alt ,meta

Look at the source code, it's to screen out the function keys that you don't bind.

When the selected key is pressed, it returns null directly.

For example, you bind ctrl and use exact

Then only when you press ctrl will the callback be triggered

Look at the stitching results

` _c('input', {
    on: {
        "keyup" $event =>{

            if (!$event.ctrlKey) 

                return null;

            if ( $event.shiftKey || $event.altKey || $event.metaKey) 
                return null;

            return testMethod($event)
        }
    }
}) `

1. When ctrl is not pressed

2. Press ctrl, but also press shfit and other function keys.

Similarly, it's the same for other common keys. When you don't need to use a combination key, just add exact.

5. Collect other keys

else{
    keys.push(key);
}

This is the most common, when you add modifiers, not in the modifiers defined inside the Vue, there is no exact

Then collect the keys you add in an array directly.

such as

So keys = ['v','b','c']

6 Stitching Event Callback

Now let's start with the most important part of this function. Cheer up.

Three Key Points of Splicing Event Callback

1. Stitching key modifier

2. Splicing built-in modifiers

3. Stitching event callback

1 splice key modifier

if (keys.length) {
    code += genKeyFilter(keys);
}

genKeyFilter has already been mentioned above. If you forget, you can look back. Here's a result.

such as

So keys = ['v','b','c']

The final code is

` if(
    !('button' in $event)&&
    _k($event.keyCode,"v",undefined,$event.key,undefined)&&
    _k($event.keyCode,"b",undefined,$event.key,undefined)&&
    _k($event.keyCode,"c",undefined,$event.key,undefined)
)
return null; `

Three _k functions were called

2 splicing interior modifier

if (genModifierCode) {
    code += genModifierCode;
}

genModifierCode is the collection of internal modifiers and modifiers with exact, as described above.

The stitching result is code

` 
 $event.stopPropagation();
 if(!$event.shiftKey) return null;
`

3 Stitching Event Callback Subject

Here's the callback you're bound to. You can try the source code first.

var handlerCode = 

    isMethodPath    



    // Execute the functions you bind

    ? "return " + handler.value + "($event)"
    : (
        isFunctionExpression        



        // If the callback contains the function keyword, the function is also executed

        ? "return (" + handler.value + "($event))"
        : handler.value;
    )

1. If you bind a method name

For example, the method name you bind is

So handler Code is

"return aaa($event)"`

2. If there is a function body

such as

So handler Code is

"return (function(){}($event))"

3. If only inline statements

such as

So handler Code is

aa = 33

Just think of this statement as part of the callback.

Procedure

Unknowingly, we've talked a lot, and finally we'll use an example to play with the process.

Collect built-in modifiers

1. Stitching key modifier

2. Splicing built-in modifiers

3. Stitching event callback

4. Callback of Packaging Events

For example, the following template, let's see how to splice events

1. Start traversal modifiers

2. Encountering ctrl is a built-in modifier to collect

genModifierCode = ` 
    if( !$event.ctrlKey ) return null
 `

3. Continue traversing, encounter v, not built-in, collect keys

keys.push('v')

4. End of traversal and start splicing key modifiers

Call genKeyFilter, pass in keys, and get the code

code = `
    if(
        !('button' in $event)&&
        _k($event.keyCode,"v",undefined,$event.key,undefined)
    ) 
    return null;
`

5. Put the built-in modifier behind the key modifier

code = code + genModifierCode

6. Start splicing event callback, is a method name

handlerCode = ` 
    return test($event)
 `

7. ok, the last step, start assembling

` funciton($event){

    if( !$event.ctrlKey ) return null;

    if(
        !('button' in $event)&&
         _k($event.keyCode,"v",undefined,$event.key,undefined)
    )
    return null;

    return test($event)
}`

Then the event splicing is completed!!!!!!!!!!

Well, don't forget, we're splicing into render and flipping up to genHandlers.

The above is just about event callbacks. If we want to splice them into render callbacks, we need to do something else.

Add the event name, splice the on object string, like this, see the genHandlers above.

`,on:{
    keyup:... Above our stitching callback} 

`

Thank you for watching. It's hard work.

Last

In view of my limited ability, there will inevitably be omissions and errors. Please forgive me. If there are any improper descriptions, please contact me in the background and get the red envelope.

Topics: Javascript Vue Attribute