前言:前面我们根据vue官网的生命周期图走了一遍vue的源码,感兴趣的小伙伴可以去看我之前的文章Vue源码解析(一),今天我们重点研究一下render跟mount方法,这也是vue中两个比较重要的方法了,废话不多说了,我们开撸~~
我们先回顾下上节内容,我们打开vue的源码,然后找到/vue/src/core/instance/init.js:
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
可以说这段代码就是贯穿了vue的整个生命周期,我们可以看到最后一行代码:
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
当我们的vue实例中有el属性的时候就会直接走vm的mount方法,那么如果我们不提供el属性的时候,我们该怎么走呢?
我们创建一个简单的vue页面:
page-a.vue:
<template>
<div id="page-a-container">
<button @click="onClick">登录</button>
</div>
</template>
<script>
import Render3 from '../components/Render3'
export default {
name: 'pageA',
mounted() {
},
methods: {
onClick() {
this.$store.dispatch('login')
}
}
}
</script>
<style scoped>
#page-a-container {
color: white;
font-size: 24px;
height: 100%;
}
</style>
然后我们在page-a.vue中引用一个叫Render3.js的组件:
export default {
name: 'Render3',
render() {
return <div>我是render3</div>
}
}
可以看到,我们直接用js文件定义了一个对象,对象里面有name属性跟render方法,render方法我们待会再分析,那么我们怎么才能page-a.vue中用起来呢?
方法一:
mounted() {
//继承vue的属性
let Render3Class=this.$options._base.extend(Render3)
//创建一个vue实例
let render3VM=new Render3Class({
//创建一个空元素
el: document.createElement('div')
})
//把render3VM的el添加到当前vue的el中
this.$el.appendChild(render3VM.$el)
},
然后我们运行代码:
我们还可以直接把组件注册为局部组件或者全局组件:
<template>
<div id="page-a-container">
<button @click="onClick">登录</button>
<render3></render3>
</div>
</template>
<script>
import Render3 from '../components/Render3'
export default {
name: 'pageA',
mounted() {
// let Render3Class=this.$options._base.extend(Render3)
// let render3VM=new Render3Class({
// el: document.createElement('div')
// })
// this.$el.appendChild(render3VM.$el)
},
methods: {
onClick() {
this.$store.dispatch('login')
}
},
components:{
Render3
}
}
</script>
<style scoped>
#page-a-container {
font-size: 24px;
height: 100%;
}
</style>
第二种我就不介绍了哈,小伙伴都会用,我们看一下第一种方式:
import Render3 from '../components/Render3'
export default {
name: 'pageA',
mounted() {
let Render3Class=this.$options._base.extend(Render3)
let render3VM=new Render3Class({
el: document.createElement('div')
})
this.$el.appendChild(render3VM.$el)
},
methods: {
onClick() {
this.$store.dispatch('login')
}
}
我们前面知道,当提供了el属性给vm的时候,直接会走mount方法,我们把el属性注释掉试试:
mounted() {
let Render3Class=this.$options._base.extend(Render3)
let render3VM=new Render3Class({
// el: document.createElement('div')
})
this.$el.appendChild(render3VM.$el)
},
可以看到,render3没有被渲染,那我们怎么办呢? 我们可以自己调用一下:
mounted() {
let Render3Class=this.$options._base.extend(Render3)
let render3VM=new Render3Class({
// el: document.createElement('div')
})
render3VM.$mount(document.createElement('div'))
this.$el.appendChild(render3VM.$el)
},
我就不截图了哈,效果跟之前一样,我们来分析一下mount方法,mount方法字面上意思就是“渲染”的意思,当我们直接执行:
render3VM.$mount(document.createElement('div'))
方法的时候,我们首先走了/vue/src/platforms/web/entry-runtime-with-compiler.js:
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
//在vm中传递的el属性
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 <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// 如果在options中没有传递render方法
if (!options.render) {
let template = options.template
//如果有template属性,就把template编译完毕后传递给render方法
if (template) {