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] Generation Node Data Mosaic of Compile-Source Edition
In the last article, we talked about the splicing of different nodes. What we need to record in detail is the splicing of node data.
Node data, including props, attrs, events, etc.
In the last article we saw in genElement, each node needs to splice the node data, using the method of genData in the source code below.
function genElement() { .....Handling other types of nodes var data = genData$2(el, state); var children = genChildren(el, state); code = `_c('${el.tag}', $ { data ? ("," + data) : '' }, $ { children ? ("," + children) : '' })` }
genData$2
The source code of this function is a bit long, but don't be afraid. It's all about the judgment of various attributes, so the content is about the same, but it involves specific methods, which will be looked at in detail.
Come on, go through it first.
function genData$2(el, state) { var data = '{'; // First parse instructions var dirs = genDirectives(el, state); // String of instructions parsed on stitching if(dirs) { data += dirs + ','; } // Components with is bindings are not used directly if(el.component) { data += `tag: ${el.tag} , ` } // As mentioned in the previous article, dataGenFns contains functions for handling style and class. for(var i = 0; i < state.dataGenFns.length; i++) { data += state.dataGenFns[i](el); } // All attributes if(el.attrs) { data += ` attrs:{ ${genProps(el.attrs)) } ,` } // essential attribute if(el.props) { data += ` domProps:{ ${genProps(el.props)} }, ` } // Event if(el.events) { data += genHandlers(el.events, false) + ","; } // Primary events if(el.nativeEvents) { data += genHandlers(el.nativeEvents, true) + ","; } // slot without scope if( el.slotTarget && !el.slotScope ) { data += ` slot: ${ el.slotTarget } ,` } // Scope slot if(el.scopedSlots) { data += genScopedSlots(el.scopedSlots, state) + ","; } // Components use v-model if(el.model) { data += `model:{ value:${el.model.value}, callback:${el.model.callback}, expression:${el.model.expression}, },` } data = data.replace(/,$/, '') + '}'; return data }
First, this method returns a serialized string of an object, such as
" { a:b , c:d } "
So braces are added at the beginning and the end, and then the attributes are spliced together in the form of xx:yy.
Now let's take a look at the processing of different attributes one by one.
Stitching instructions
function genDirectives(el, state) { var dirs = el.directives; if (!dirs) return var res = 'directives:['; var hasRuntime = false; var i, l, dir, needRuntime; for (i = 0, l = dirs.length; i < l; i++) { dir = dirs[i]; needRuntime = true; // Processing methods for obtaining specific Vue instructions var gen = state.directives[dir.name]; // If this function exists, it proves that the instruction is internal. if (gen) { needRuntime = gen(el, dir); } if (needRuntime) { hasRuntime = true; res += `{ name: ${dir.name}, rawName: ${dir.rawName} ${ dir.value ? ",value:" + dir.value +", expression:" + dir.value : '' } ${ dir.arg ? ( ",arg:" + dir.arg ) : '' } ${ dir.modifiers ? (",modifiers:" + JSON.stringify(dir.modifiers)) : '' } }, ` } } if (hasRuntime) { return res.slice(0, -1) + ']' } }
First, we need to understand what string this method will return, such as
It returns such a string.
`directives:[{ name:"test", rawName:"v-test:a.b.c", value:222, expression:"arr", arg:"a", modifiers:{"b":true,"c":true} }]`
Each instruction is parsed into an object string and then spliced into an array of strings.
So here's a detailed record of some possible confusions
state.directives in functions
In the CodegenState article above, we have written about this
state.directives is an array that contains the processing functions of the internal instructions of the Vue, as follows
v-on,v-bind,v-cloak,v-model ,v-text,v-html
The variable needRuntime in a function
A token indicating whether the instruction data needs to be parsed into an object string, like this
`{ name:xxx, rawName:xxx, value:xxx, expression:xx, arg:xx, modifiers:xx }`
That is, does this instruction need to be spliced into render strings?
So what commands do you need and what commands do you not need?
Custom instructions need to be parsed and spliced into render strings
But Vue's internal instructions, some useful, some unnecessary, so come up with a needRunTime to judge.
The instructions of Vue, first of all, get a specific processing method and assign it to gen.
When gen returns true after processing, render, false or no splicing is needed.
For example, data from v-model instructions need to be spliced together with render, while v-text and v-html do not.
Look at the following examples
For example, the template above spliced into the following string and found that v-html did not appear in the string array of directives.
`_c('div',{ directives:[{ name:"model", rawName:"v-model", value:arr, expression:"arr" }], domProps:{ "innerHTML":_s(<span></span>) } })`
The variable hasRuntime in the function
A token indicating whether a return instruction string is required
genDirectives handles an array of instructions, and when the array is empty, there is no return value.
Then the render string will not have a directive string
If the instruction is not empty, hasRunTime is set to true, requiring a string to be returned
And add], at the end of the string, so that the string array is complete
Stitching Component
The parsing component here resolves the binding component with the is attribute
Simply, it's ok ay to stitch together the attributes of a tag.
Look at examples.
The original tag name is stitched behind the tag
` _c("test",{tag:"div"}) `
Stitching style
As mentioned in the previous article, state.dataGenFns is an array
Two functions are stored, one is parsing class, the other is parsing style.
It's very simple to put down the source code here.
Analyzing class
function genData(el) { var data = ''; if (el.staticClass) { data += "staticClass:" + el.staticClass + ","; } if (el.classBinding) { data += "class:" + el.classBinding + ","; } return data }
Analytical style
function genData$1(el) { var data = ''; if (el.staticStyle) { data += "staticStyle:" + el.staticStyle + ","; } if (el.styleBinding) { data += "style:(" + el.styleBinding + "),"; } return data }
It's really too simple, just splicing a few attributes directly.
Just give me an example.
`_c('div',{ staticClass:"a", class:name, staticStyle:{"height":"0"}, style:{width:0} }) `
Stitching attributes
Attribute splicing has only one function and the content is very simple.
function genProps(props) { var res = ''; for (var i = 0; i < props.length; i++) { var prop = props[i]; res += prop.name + ":" + prop.value + ","; } return res.slice(0, -1) }
As you can see, although there is only one way, in genData,, there are two kinds of splicing results
Stitching to el.attr
Stitching to el.props
Why do they splice together in different places?
Because it looks at where your attributes are placed.
If your attribute location is on the tag, it will be spliced into attr
If your attributes are located on the dom, they are spliced into the domProps.
For instance
For example, the following template, bbb is on the label, aaa is on the DOM
The result of splicing is
` _c('div',{ attrs:{"bbb":"bbb"}, domProps:{"aaa":11} }) `
Page labels do not see aaa
aaa can be found in the dom attribute
Splicing events
Event splicing, a lot of content, intended to be placed in another article for detailed record
Event splicing can also be divided into two types: native events and custom events, which are just spliced into different strings, but the processing method is the same.
The method involves various modifiers. Haha, I want to know why I can write such a convenient api.
Bind keys to prevent default events. Just write this
@keyup.enter.prevent="xxx"
Welcome to the next article
Splicing Ordinary Slot
It's the property of slot spliced directly.
` _c('test',[_c('span',{ attrs:{"slot":"name"}, slot:"name" })] ) `
If the component has slot and there is no slot attribute, then slot will not be spliced, and the default name "default" will be given directly afterwards.
Splicing Scope Slot
function genScopedSlots(slots, state) { return ` scopedSlots:_u([${ Object.keys(slots).map(key =>{ return genScopedSlot(key, slots[key], state) }) .join(',') }]) ` } function genScopedSlot(key, el, state) { var fn = ` function(${el.slotScope}){ return ${ el.tag === 'template' ? genChildren(el, state) : genElement(el, state) } } ` return `{ key:${key} , fn: ${fn} }` }
This function for dealing with scoped slot s seems a bit complicated, but it's actually a paper tiger.
Don't be afraid. Let's look at an example first.
It's spliced into strings, like this
` _c('div',{ scopedSlots:_u([{ key:"heder", fn:function(arr){return _c('div')} }]) }) `
This function traverses the el.scopeSlots array. Maybe you don't know what this array is about?
Again, here are two slot s.
After parse parsing, it becomes an ast, which is like this
{ tag:"test", scopedSlots:[{ slotScope: "arr" slotTarget: ""a"" tag: "div" },{ slotScope: "arr" slotTarget: ""b"" tag: "div" }] }
Yes, the scopedSlots array in the above object is traversed, and each item in the array is a separate slot.
genScopeSlot is then used to handle it separately, with the source code released on it.
After processing, a new array is formed, and genScopeSlot has nothing to say.
For splicing classification, it is necessary to determine whether the slot location label is template or not.
If it's a template, then his real slot is a child of the template and gets his child directly.
If it's not a template, it's a real slot.
Because template is a template node that comes with Vue, it does not exist.
Mosaic Component VModel
Yes, the model here is just a component v-model.
if (el.model) { data += `model: { value: $ { el.model.value }, callback: $ { el.model.callback }, expression: $ { el.model.expression }, }, ` }
The official website said how to use this.
v-model on a component defaults to take advantage of prop named value and events named input
That is to say, the starting point is to pass a value to the component and bind an event input.
Nothing to say, just record how the v-model of the component is spliced together.
Example
After parse analysis, ast is obtained
{ tag: "test", model:{ callback: "function ($$v) {num=$$v}" expression: ""num"" value: "num" } }
It's spliced into words and turned into strings.
` _c('test',{ model:{ value:num, callback:function ($$v) {num=$$v}, expression:"num" } }) `
Take a chestnut
Attribute splicing, let's finish. Finally, let's take an example.
Here's the template. Let's stitch it together.
Resolve it into the render string below, and after reading it, you will have mastered the content of generation.
Later you can see the packaged code written by someone else in Vue.
Even, you can restore it manually. If you are idle, you can write a method by yourself, pass in the render string, and automatically restore it to the template.
` _c('div', { attrs: { "b": "2" }, domProps: { "a": 11 } },[ _c('test', { scopedSlots: _u([{ key: "a", fn: function(arr) { return _c('strong') } }]), model: { value: (num), callback: function($$v) { num = $$v }, expression: "num" } },[_c('span')]) ]) `
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.