vue 把组件挂载到视图_vue 源码解析(3-2-3,模板编译和组件化)

本文深入探讨Vue的模板编译过程,包括如何将模板转化为渲染函数,解析AST,优化和生成VNode。同时,文章介绍了Vue的组件化机制,详细解析了组件的创建和挂载流程,以及组件实例的生命周期。通过学习,读者能够理解Vue的编译原理和组件工作方式。
摘要由CSDN通过智能技术生成

vue相关的源码分析最后一篇,模板编译和组件化

推荐先去看下总结:vue源码总结

模板编译

模板编译的主要目的是将模板 (template) 转换为渲染函数 (render)

title

somecontent

渲染函数 renderrender(h){

return h('div',[

h('h1',{on{click:this.handler}},'title'),

h('p','somecontent')

])

}

模板编译的作用:Vue 2.x 使用 VNode 描述视图以及各种交互,用户自己编写 VNode 比较复杂

用户只需要编写类似 HTML 的代码 - Vue 模板,通过编译器将模板转换为返回 VNode 的render 函数

.vue 文件会被 webpack 在构建的过程中转换成 render 函数

带编译器版本的 Vue.js 中,使用 template 或 el 的方式设置模板

例如:

Vue模板编译过程

{{ msg }}

Vue.component('comp', {

template: '

I am a comp
'

})

const vm = new Vue({

el: '#app',

data: {

msg: 'Hello compiler'

},

methods: {

handler () {

console.log('test')

}

}

})

console.log(vm.$options.render)

编译出:(function anonymous(){

with(this) {

return _c(

"div",

{ attrs: { id: "app" } },

[

_m(0),

_v(""),

_c("p", [_v(_s(msg))]),

_v(""),

_c("comp", { on: { myclick: handler } }),

],

1

);

}

});

这里this指向了vm,也就是this._c ,vm._c

_c 是 createElement() 方法,定义的位置 instance/render.js 中

我们通过我上一篇知道 vm._c(生成虚拟dom)是对编译生成的 render 进行渲染的方法,vm.$createElement(生成虚拟dom)是对手写render 函数进行渲染的方法.

其他相关的渲染函数(_开头的方法定义),在 instance/render-helps/index.js 中。

这里安利一个小工具:Vue Template Explorer

模板编译的过程,主要是三点解析

优化

生成

编译模板的入口:

src\platforms\web\entry-runtime-with-compiler.js

在调用$mount 挂载时 会调用compileToFunctions 把template转换成render函数(内部调用_c()生成虚拟dom)。

下面就是调试compileToFunctions来看下生成渲染函数的过程.compileToFunctions: src\compiler\to-function.js

complie(template, options):src\compiler\create-compiler.j

baseCompile(template.trim(), finalOptions):src\compiler\index.js

parse:解析器将模板解析为抽象语树 AST,只有将模板解析成 AST 后,才能基于它做优化或者生成代码字符串。

src\compiler\index.js

这里再提供一个工具可以查看转换出来的得到的 AST tree

v-if/v-for 结构化指令只能在编译阶段处理,如果我们要在 render 函数处理条件或循环只能使用js 中的 if 和 forVue.component('comp', {

data:(){

return {msg:'mycomp'}

},

render(h){

if(this.msg){

return h('div',this.msg)

}

return h('div','bar')

}

})

优化 - optimize优化抽象语法树,检测子节点中是否是纯静态节点

一旦检测到纯静态节点,例如:hello整体是静态节点

永远不会更改的节点提升为常量,重新渲染的时候不在重新创建节点

在 patch 的时候直接跳过静态子树

生成 - generate

把抽象语法树生成字符串形式的 js 代码

AST 抽象语法树很大,我们不在这里做过多学习,主要学习理念.

组件化机制组件化可以让我们方便的把页面拆分成多个可重用的组件

组件是独立的,系统内可重用,组件之间可以嵌套

有了组件可以像搭积木一样开发网页

从源码的角度来分析 Vue 组件内部如何工作组件实例的创建过程是从上而下

组件实例的挂载过程是从下而上

全局组件的定义Vue.component('comp', {

template: '

I am a comp
'

})

Vue.component() 入口

创建组件的构造函数,挂载到 Vue 实例的 vm.options.component.componentName =Ctor//src\core\global-api\index.js

//注册Vue.directive()、Vue.component()、Vue.filter()

initAssetRegisters(Vue)

//src\core\global-api\assets.js

if(type==='component'&&isPlainObject(definition))

{

definition.name=definition.name||id

definition=this.options._base.extend(definition)

}

……

//全局注册,存储资源并赋值

// this.options['components']['comp']=Ctor

this.options[type+'s'][id]=definition

Vue.options._base=Vue

Vue.extend()

组件构造函数的创建export function initExtend (Vue: GlobalAPI) {

/**

* Each instance constructor, including Vue, has a unique

* cid. This enables us to create wrapped "child

* constructors" for prototypal inheriance and cache them.

*/

Vue.cid = 0

let cid = 1

/**

* Class inheritance

*/

Vue.extend = function (extendOptions: Object): Function {

extendOptions = extendOptions || {}

// Vue 构造函数

const Super = this

const SuperId = Super.cid

// 从缓存中加载组件的构造函数

const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})

if (cachedCtors[SuperId]) {

return cachedCtors[SuperId]

}

const name = extendOptions.name || Super.options.name

if (process.env.NODE_ENV !== 'production' && name) {

// 如果是开发环境验证组件的名称

validateComponentName(name)

}

const Sub = function VueComponent (options) {

// 调用 _init() 初始化

this._init(options)

}

// 原型继承自 Vue

Sub.prototype = Object.create(Super.prototype)

Sub.prototype.constructor = Sub

Sub.cid = cid++

// 合并 options

Sub.options = mergeOptions(

Super.options,

extendOptions

)

Sub['super'] = Super

// For props and computed properties, we define the proxy getters on

// the Vue instances at extension time, on the extended prototype. This

// avoids Object.defineProperty calls for each instance created.

if (Sub.options.props) {

initProps(Sub)

}

if (Sub.options.computed) {

initComputed(Sub)

}

// allow further extension/mixin/plugin usage

Sub.extend = Super.extend

Sub.mixin = Super.mixin

Sub.use = Super.use

// create asset registers, so extended classes

// can have their private assets too.

ASSET_TYPES.forEach(function (type) {

Sub[type] = Super[type]

})

// enable recursive self-lookup

// 把组件构造构造函数保存到 Ctor.options.components.comp = Ctor

if (name) {

Sub.options.components[name] = Sub

}

// keep a reference to the super options at extension time.

// later at instantiation we can check if Super's options have

// been updated.

Sub.superOptions = Super.options

Sub.extendOptions = extendOptions

Sub.sealedOptions = extend({}, Sub.options)

// cache constructor

// 把组件的构造函数缓存到 options._Ctor

cachedCtors[SuperId] = Sub

return Sub

}

}

组件的构造函数原型继承自vue,也执行了init方法,也就会调用$mount()方法,也会生成一个渲染watcher,也就验证了之前说的一个组件对应一个渲染watcher,这个组件构造函数被缓存到 options._Ctor。

后面生成vNode时,会根据Ctor创建组件的VNode.

组件创建和挂载

组件 VNode 的创建过程创建根组件,首次 _render() 时,会得到整棵树的 VNode 结构

整体流程:new Vue() --> $mount() --> vm._render() --> createElement() --> createComponent()

创建组件的 VNode,初始化组件的 hook 钩子函数

生成组件的vnode时初始化了installComponentHooks钩子

这里调用了createComponentInstanceForVnode

这样我们就可以串通了,生成虚拟dom vnode时检测是组件createComponent 的时候,注册init钩子,然后后面调用init钩子时,

调用 createComponentInstanceForVnode 实例化组件,newvnode.componentOptions.Ctor(options)

实例化完了调用:child.$mount(hydrating?vnode.elm:undefined,hydrating)

调用组件对象的$mount(),生成渲染watcher,并把组件挂载到页面

组件实例的创建和挂载过程

Vue._update() --> patch() --> createElm() --> createComponent()

在patch的createElm时

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {

let i = vnode.data

if (isDef(i)) {

const isReactivated = isDef(vnode.componentInstance) && i.keepAlive

if (isDef(i = i.hook) && isDef(i = i.init)) {

// 调用 init() 方法,创建和挂载组件实例

// init() 的过程中创建好了组件的真实 DOM,挂载到了 vnode.elm 上

i(vnode, false /* hydrating */)

}

// after calling the init hook, if the vnode is a child component

// it should've created a child instance and mounted it. the child

// component also has set the placeholder vnode's elm.

// in that case we can just return the element and be done.

if (isDef(vnode.componentInstance)) {

// 调用钩子函数(VNode的钩子函数初始化属性/事件/样式等,组件的钩子函数)

initComponent(vnode, insertedVnodeQueue)

// 把组件对应的 DOM 插入到父元素中

insert(parentElm, vnode.elm, refElm)

if (isTrue(isReactivated)) {

reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)

}

return true

}

}

}

在这里我们看到执行了init,init() 的过程中创建好了组件的真实 DOM,挂载到了 vnode.elm 上. 挂载到界面上,vnode.elm时对界面真实dom的引用.

这样结合我们三篇,就可以看到vue的加载运行了,其实主要是前面两篇,

最后一篇只是在前面两篇,基础上加了组件在vNode中的渲染时机.

也就是说生成虚拟dom时,判断是组件的情况时,把对应位置的vNode传递,然后调用组件继承自vue原型的init方法初始化,然后再调用$mount方法挂载到对应vNode.elm真实dom上.

好了,记得有点模糊.

有时间再改下把.

本文内容借鉴于拉钩大前端训练营

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值