vue 渲染流程

vue 渲染流程

注意:以下代码都是关键代码,由本人手写并经过测试 ~ 并非直接down的源码。

使用方式

既然我们已经使用很多次vue了,那vue的使用方式大家一定不陌生。

假设我们在html中引入了打包好的vue.js文件,代码如下:在这个new Vue的构造函数中,我们传入了一个对象,对象的属性包括了data、el、mounted三个属性。今天只考虑这三个属性如何正确的渲染出来。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My-Vue</title>
</head>
<body>
    <div id="app" class="app">hello<span class='12312'>{{a.name}}</span>{{b}}</div>

    <script src="/dist/umd/vue.js"></script>   
    <script> 
    let vm = new Vue({
        el:'#app',
        data(){return{a:{name:'张三'},b:[[1],0]}},
        // template:"hello<span class='12312'>{{name}}</span>",
        mounted(){
			console.log('mounted')
		}
    })
    console.log('1234444444',vm.$el.innerHTML )
    setTimeout(()=>{
        vm.a.name = '李四';
        vm.b.push(5)
        vm.b[0].push(2)
    },1200)
    </script>
</body>
</html>

从我们调用vue的方式来看,能得到一个关键的信息,也就是这个暴露出来的Vue,它是一个函数。否则不能用new来调用。

那这个vue函数是怎么样的呢?

且听我细细道来~

Vue 函数及原型方法

vue是一个函数当然也是对象,对象是有原型的,原型上有很多方法,当我们new一个实例的时候,这个实例就能够获取构造函数原型上的方法。这时候就像子类可以继承父类的方法一样,就可以使用vue原型上的方法。这与js原型链的机制息息相关~

在vue暴露出去之前,他会往他的原型上挂载很多方法,以供在需要的时候直接使用。那他都会挂在哪些方法呢?

比如说我们的初始化init方法、返回vdom的render方法,进行渲染的mount方法以及update更新DOM的方法等等。这里只列举了下文可能会涉及的一些方法。

事实上,在VUE的源码中,不同用途的函数会进行分门别类的挂载。

如下,与初始化相关的函数,由initMixin()来完成挂载。与生命周期相关的函数由lifecycleMixin来完成挂载,等等~

这些mixin函数的根本操作就是 Vue.prototype.XXX = function (){ // *****},也就是往Vue上挂载很多方法。

当我们把Vue函数暴露出去的时候,他的原型已经有了我们需要的全部方法。

// 定义Vue函数
function Vue(options) {
    // 初始化操作
    this._init(options);
}
debugger
// 给Vue原型上添加方法
initMixin(Vue)
renderMixin(Vue)
lifecycleMixin(Vue)
// 等等其他功能的函数

渲染流程1、 调用init() 初始化

这个时候,我们new Vue() 执行 this._init(),这里的_init 其实就是在initMixin中进行挂载的。

initMixin函数的关键代码如下。

 function initMixin(Vue){
    // 初始化方式
    Vue.prototype._init = function (options){
        const vm = this
        vm.$options = mergeOptions(vm.constructor.$options || {},options)
        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')
        
        // 如果用户传入了el属性,需要将页面渲染出来
        vm.$options.el && vm.$mount(vm.$options.el)
    }
}

构造函数,也就是new的时候调用_init,就是这里挂载到vue上的_init。

init做的三件事:(这里只简单介绍哈~)

1、合并options ,vue的原型继承策略并不能很好的满足我们,因为在很多情况下,我们不是当前对象找不到就使用继承属性。而是有策略在的,举一个例子,vue的生命周期事件,当前对象和原型链上都有,但是不是当前对象有就不去取原项链的方法了,他是要依次执行两个方法。所以这一步确保每一个options 都正确且合理。

2、各模块的初始化 。这里同样包括很多初始化,比如生命周期、渲染、数据等等初始化。我们今天很多不会涉及到。这里比较重要的一点就是数据的初始化。也就是 initState(),这一块我们会将data数据创建成响应式数据。这样我们在修改了数据之后,就可以看到dom自动更新啦。详细可以看:Vue的响应式原理

3、调用mount函数渲染。

渲染流程2 调用$mount() 挂载

$mount的函数在不同的打包方式下是不一样的。这里我们已编译版本的为例进行介绍。(编译版本就是我们写的时候要写template 运行时版本就是写render函数的那一种)。

 Vue.prototype.$mount = function (el){
        const vm = this;
        const {render,template} = this.$options;
        let myRender  = render;
        if(!render){
            let myTemplate = template;
            if(!template){
                const dom = document.querySelector(el)
                vm.$options.$el = dom;
                if(dom){
                    myTemplate = dom.outerHTML
                }
            }
            // template 转 render
            myRender = compileToFunction(myTemplate)
            this.$options.render = myRender;
        }
        
        // 渲染当前的组件
        mountComponent(vm,el)
  }

function mountComponent (
    vm,
    el
  ) {
    vm.$el = el;
    if (!vm.$options.render) {
      vm.$options.render = createEmptyVNode;
    }
    // 拿到render函数之后,进行beforemount
    callHook(vm, 'beforeMount');
    // HACK: 平台相关 
    var updateComponent;
    updateComponent = function () {
        // vm._render() 返回虚拟dom
        // vm._update() 虚拟dom生成真实的dom
        console.log('-------更新------')
        vm._update(vm._render());
    };
    
    // 渲染watcher   
    new Watcher(vm, updateComponent, ()=>{}, null, true /* isRenderWatcher */);
  
    // NOTE: 组件更新原理
    // watcher 用来渲染的 
    // updateComponent  创建真实的dom
    if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, 'mounted');
    }
    return vm
}
  

挂载dom的核心逻辑就是:
1、有render函数吗 ? 如果有直接使用 , 如果没有,找到template通过 vue 模板编译 转成 render函数。这个render函数会在下边执行_render的时候执行并返回dom对应的vdom;
2、有了render函数 ,调用组件渲染 。这个函数呢,会new一个watcher ,在new的当下执行 updateComponent 调用update更新一次dom,往后当data发生改变时继续执行update更新dom,这样就做到了响应式。所以当我们在后边改vm的数据的时候,就会更新dom.张三变成李四。

后续

这只是Vue的核心渲染流程。至于他是怎么响应式的,是怎么编译的,又是怎么进行的组件的渲染的,dom的更新策略是什么 等等
这些东西且听我下次讲解~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值