模板编译 — 分析入口
最后更新于:2022-04-02 08:12:42
>[success] # 模板编译代码分析
~~~
1.其实整个$mount方法在vue有两个,一个是专门解析模板在'platforms\web\runtime-with-compiler.js'
另一是直接解析渲染函数的在'\platforms\web\runtime\index.js',
2.我们解读分析的代码版本是包含'platforms\web\runtime-with-compiler.js'里面的$mount
~~~
>[info] ## 分析源码中的代码
~~~
1.之前说过即使使用了模板的方式,最后也转成了渲染函数,vue 也给我们提供了可以帮忙转换模板
的vue版本,也提供了不帮忙转换需要写渲染函数的版本,无论是那个版本都是$mount 作为入口
站在我的角度来说,我可能可以做一个变量来判断当前用户使用的版本然后一顿if-else来做区分,
vue在这里是怎么做的呢?
2.在带编译版本的'$mount'使用前vue保留了他直接解析'渲染函数的$mount',可以看一下,下面的代码片段
~~~
* 代码片段
~~~js
/*把原本不带编译的$mount方法保存下来,在最后会调用。*/
const mount = Vue.prototype.$mount // 这个是\platforms\web\runtime\index.js 只解析渲染函数的$mount
/*挂载组件,带模板编译*/
Vue.prototype.$mount = function (){....} // platforms\web\runtime-with-compiler.js 中的$mount
~~~
>[danger] ##### 开始分析源码
~~~
1.这里要说明一下,下面源码中中文解释部分我使用的是'https://github.com/answershuto/learnVue'
这个github项目中 answershuto 大佬加的中文注释
2.通过源码可以解答上个章节第一个问题,'Vue 不能挂载在 body、html 这样的根节点上'
3.我们现在分析的是模板版本的$mount,可以发现他会先判断你是否有'render'这参数也是直接影响到vue
是使用了'渲染函数'的方式,还是'模板方式'
~~~
* 源码
~~~js
/*把原本不带编译的$mount方法保存下来,在最后会调用。*/
const mount = Vue.prototype.$mount
/*挂载组件,带模板编译*/
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to or - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
/*处理模板templete,编译成render函数,render不存在的时候才会编译template,否则优先使用render*/
if (!options.render) {
let template = options.template
/*template存在的时候取template,不存在的时候取el的outerHTML*/
if (template) {
/*当template是字符串的时候*/
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
/*当template为DOM节点的时候*/
template = template.innerHTML
} else {
/*报错*/
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
/*获取element的outerHTML*/
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
/*将template编译成render函数,这里会有render以及staticRenderFns两个返回,这是vue的编译时优化,static静态不需要在VNode更新时进行patch,优化性能*/
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`${this._name} compile`, 'compile', 'compile end')
}
}
}
/*调用const mount = Vue.prototype.$mount保存下来的不带编译的mount*/
return mount.call(this, el, hydrating)
}
~~~
>[danger] ##### 模板的使用方式分析
~~~
1.通过源码可以发现'模板的使用方式'有两种一种是你直接绑定'el' 中形式,一种是你使用'template'属性的
对这几种使用方式来做个说明
~~~
* 先看el 的方式
~~~
1.先分析一下el这种情况,就需要分析'query(el)'方法在源码'platforms\web\util\index.js',
有一个'返回一个元素的DOM实例对象'方法
/*返回一个元素的DOM实例对象*/
export function query (el: string | Element): Element {
if (typeof el === 'string') {
const selected = document.querySelector(el)
if (!selected) {
process.env.NODE_ENV !== 'production' && warn(
'Cannot find element: ' + el
)
return document.createElement('div')
}
return selected
} else {
return el
}
}
2.也就是说你的el如果是字符串可以使用'querySelector() 方法返回文档中匹配指定 CSS 选择器的一个元素'
或者你可以传入一个'dom实例对象' 因此我们可以写法如下
var vm = new Vue({
el: "#app", // 也可以绑定css选择器
})
var vm = new Vue({
el: document.getElementById('app'), // 也可以绑定css选择器
})
~~~
* 在看template方式
~~~
1.第一步依旧需要el,第二步声明'template',这里要注意你在使用template不仅是在vue的opition中声明'template'
也需要声明el
2.而且template声明的内容要用template 标签包裹
3.即使你定义了el 里面的内容也会被template给替换掉
';
{{msg}}
{{msg}}123
// 或者直接绑定字符串dom形式
var vm = new Vue({
el: "#app",
template: '2222{{msg}}
',
data: {
msg: "第一个案例"
}
})
// 字符串id形式
var vm = new Vue({
el: "#app",
template: '#app7777',
data: {
msg: "第一个案例"
}
})
~~~
>[danger] ##### 现在来对着生命周期图来看这块
~~~
1.当然你实在不想写el 也可以这么写
new Vue({
render: h => h(App)
}).$mount("#app");
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/13/14/13141c8cea9139bb9416f6c853e48d56_757x428.png)
>[danger] ##### 小的细节代码说明
~~~
1.其实发现一段小的代码'idToTemplate(template)',这段代码是下面的代码组成的,利用闭包做的缓存的代码
function cached(fn) {
const cache = Object.create(null)
return (function cachedFn(str) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
})
}
/*根据id获取templete,即获取id对应的DOM,然后访问其innerHTML*/
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
~~~