概述
Vue 开发的一个基本思路就组件化,一切皆可通过组件实现。那Vue是怎么实现组件化渲染的呢?本文主要解决以下问题:
如何渲染一个简单自定义组件(包含 SFC编译概述)
父子组件的如何进行关联
实现代码为 简单组件渲染,实现和 Vue 的差异点见最后面的附录
DEMO展示
入口文件
import App from './App.vue'
import Vue from './src/vue'
new Vue({
el: '#app',
render(h) {
return h(App)
},
})
App.vue文件:
<template>
<div>
{{message}}
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
message: '我是一个组件'
}
}
}
</script>
初始效果为:
响应式更新:
原理解析
渲染一个简单组件
使用 SFC 形式编写组件,经过 vue-template-loader 编译解析后,生成一个对象,会将 SFC 中的 template模板编译生成一个 render 函数。编译之后会使用大量的快捷方式引用。上述 DEMO 中的App.vue编译之后的模板为:
function render () {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", [_vm._v("\n " + _vm._s(_vm.message) + "\n")])
}
所以渲染组件第一步是,需要提供这些快捷访问方式,在Vue中定义为 renderHelpers。
其次渲染组件和渲染文本区别在于渲染时,传递的是一个对象或者是一个元素标签,故渲染是需要针对不同数据做不同的适配。适配过程为:
createElement 里面对传入参数做判断,如果字符串类型,则渲染文本组件,否则渲染为组件
渲染组件时,先创建一个 VNode实例,tag 为自行拼接的一个字符串(渲染文本组件时tag为 div p 等html的tag)
在由 VNode生成真实Dom时,会先判断是否组件,针对组件会使用hook方式使用 Vue 的构造函数生成组件的实例,进行挂载。
整个流程为:
每一个组件其实都对应了一个 Vue 实例,所以所有组件内都可以共享全局的一些mixin,原型上面的方法、属性等。
组件VNode 会在 Patch过程中 通过hook方式创建相关的Vue实例,不太理解为什么需要使用 hook 方式渲染组件?
父子组件关联
每一个组件都是一个Vue实例,那父组件如何感知子组件存在呢?
在Vue中 还是借助了 JS 单线程结合全局变量这种思路实现的。
通过记录当前执行的组件,在创建子组件实例会会引用,从而创建连接,效果:
总结
可以简单理解Vue中一切皆组件,初始化时,是创建一个父组件实例(通过 new Vue 实现),然后可以递归生成子组件的实例(也是通过 new Vue 实现)。所以可以借助Vue的原型,Vue一些静态属性共享很多方法、属性等。
附录
代码实现和 Vue 实现源码有些出入,本着学习Vue思路,有些细节能简化简化。Vue中通过 Vue.extend 方法创建了一个继承自 Vue 的子类来渲染子组件,通过将初始化子组件所需要的参数都扩展到构造函数的options属性中。而自己实现的还是直接复用 Vue 的构造函数来创建子类。