• vue 的模板引擎与渲染机制


    模板引擎

    vue 中如何解析模板,指令如何处理

    问题:

    模板是什么
    render函数
    render函数与 vdom

    模板是什么

    <div id='app'>
      <div>
        <input v-model="title"/>
        <button v-on:click="add">submit</button>
      </div>
      <ul>
        <li v-for="item in list">
          {{item}}
        </li>
      </ul>
    </div>
    

    这是一个模板

    那么模板是什么呢?

    1. 本质是字符串,是以字符串存在的,只不过像html
    2. 有逻辑,比如判断,循环这些,如v-if,v-for等,怎么会有逻辑呢,之前写html就没逻辑
    3. 与html格式很像,但有很大区别。首先html在语法上是不认识v-if,v-for这些的。第二个是html是静态的,没有逻辑,vue是动态的,有逻辑的。它们只是格式很像
    4. 但是最终模板还是要转换为html来显示的。

    那么它是怎么做到的呢?

    首先模板最终必须转换成js代码,因为:

    • 模板有逻辑,有v-if,v-for。必须用js才能实现(图灵完备的语言)
    • 模板要转化为html渲染页面,必须用js才能实现

    因此,模板最终要转换成一个js函数(render函数,也就是渲染函数)

    首先了解下with

    var obj = {
      name: 'zhangsan',
      age: 20,
      getAddress: function(){
        alert('beijing');
      }
    }
    
    // 不用with
    function fn(){
      alert(obj.name);
      alert(obj.age);
      obj.getAddress();
    }
    fn();
    
    // 使用width
    function fn1(){
      with(obj){
        alert(name);
        alert(age);
        getAddress();
      }
    }
    fn1();
    

    在实际开发中,尽量不要使用with。

    fn是我们正常的使用。

    fn1使用with的情况。

    两个是同样的效果,用with的里面,都不写是谁的属性,是谁统一的,用with包起来。
    这个可读性可能没那么强。

    render函数

    template 模板 通过 Compile 编译 得到 render函数。

    compile 编译可以分成 parse、optimize 与 generate 三个阶段,最终需要得到 render function。

    compile 编译过程,现阶段可以不需要完全掌握,只需要了解 解析的大致流程即可。

    简单的例子

    我们看最简单的一段模板

    <!DOCTYPE html>
    <html>
    	<head>
    		<meta charset="utf-8">
    		<title></title>
    		<script src="../lib/vue-2.4.0.js"></script>
    	</head>
    	<body>
    		<div id="app">
    		  <p>{{price}}</p>
    		</div>
    
    		<script type="text/javascript">
    			var vm = new Vue({
    				el: '#app',
    				data: {
    					price:100
    				}
    			})
    		</script>
    	</body>
    </html>
    
    

    把模板摘出来

    <div id="app">
    	<p>{{price}}</p>
    </div>
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <script src="./vue-2.5.13.js"></script>
    </head>
    <body>
        <div id="app">
            <p>{{price}}</p>
        </div>
    
        <script>
    	/**
    	* _c : 创建dom标签
    	* _v : 创建文本节点
    	* _s : toString
    	*/
            var vm = new Vue({
                el: '#app',
                data: {
                    price: 100
                }
            })
    		
    		// 这个模板最终生成的函数是下面这个
            // 以下是手写的 render 函数
            function render() {
                with(this) {  // this 就是 vm
                    return _c(
                        'div',
                        {
                            attrs: {'id': 'app'}
                        },
                        [
                            _c('p', [_v(_s(price))])
                        ]
                    )
                }
            }
    		
    		// 把函数翻译一下,也就是不用 with
            function render1() {
                return vm._c(
                    'div',
                    {
                        attrs: {'id': 'app'}
                    },
                    [
                        vm._c('p', [vm._v(vm._s(vm.price))])
                    ]
                )
            }
    
        </script>
    </body>
    </html>
    

    这个this就是vm这个实例,这个_c就是vm._c,用来创建dom标签的。

    第一个参数是个div。

    第二个参数是个对象,对象里面有属性。

    第三个参数是个数组,数组里面只有一个元素,这个_c肯定也是vm._c。

    这个_c第一个参数是p,第二个参数是个数组,里面也是一个数组,_v(_s(price)),这里面的price肯定是vm.price,就是data.price。

    然后前面的_s就是vm._s,就是toString函数。

    _v也是vm._v,用来创建文本节点的。

    总结:

    模板中所有信息都包含在render函数中。

    this即vm

    price即this.price即vm.price即data.price。

    _c即this._c即vm._c

    vm

    todo-list demo的render函数

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    <body>
      <!-- 模板 -->
      <div id="app">
        <input v-model='title'/>
        <button v-on:click='add'>submit</button>
        <ul v-for='item in list'>
          {{item}}
        </ul>
      </div>
        
      <!-- 源码 -->
      <script src="./vue-2.6.10.js"></script>
      <script>
        var data = {
          title: '',
          list: []
        }
        // 初始化 vue 实例
        var vm = new Vue({
          el: '#app',
          data: data,
          methods: {
            add: function(){
              this.list.push(this.title);
              this.title = ''
            }
          }
        })
      </script>
    </body>
    </html>
    

    然后通过这个例子,看看vue的render函数是什么样子的,在源码搜索code.render,然后打印出来。

    这是与上面模板对应的 render 函数。

    with(this) {
    	return _c('div', {
    			attrs: {
    				"id": "app"
    			}
    		},
    		[
    			_c('input', {
    				directives: [{ // 当title发生变化,会赋值给value,从而响应到input
    					name: "model",
    					rawName: "v-model",
    					value: (title),
    					expression: "title"
    				}],
    				domProps: {
    					"value": (title)
    				},
    				on: { // v-model相关  input 里面的数据改变,事件监听到,会赋值给 title
    					"input": function($event) {
    						if ($event.target.composing) return;
    						title = $event.target.value
    					}
    				}
    			}),
    			_v(" "),
    			_c('button', {
    					on: {
    						// 绑定 click 事件
    						"click": add
    					}
    				},
    				[_v("submit")]
    			),
    			_v(" "),
    			_l(
    				// v-for相关
    				(list),
    				function(item) {
    					return _c('ul', [_v("
     " + _s(item) + "
     ")])
    				}
    			)
    		], 2)
    }
    
    

    这就是这个 demo 所对应的 render 函数。

    在创建input的时候第二个参数有个directives。叫做指令,指令名字是model。value是title,也就是vm.title。

    后面_v(''),_v表示创建文本节点,主要是input和button有个换行,如果不换行,就不会有 _v去创建一个空的文本节点。

    _l 返回的是一个数组,针对list返回li的标签。

    现在可以根据 todo-list demo 的 render 函数,回顾一下 render 函数中:

    • v-model 是怎么实现的?
    • v-on 是怎么实现的?
    • v-for 是怎么实现的?

    渲染-vue的模板如何被渲染成html

    以上已经解决了模板中“逻辑”的问题(v-for v-if)

    还剩下模板生成 html 的问题

    另外,vm._c 是什么?render 函数返回了什么?

    先复习一下 vdom 的知识

    这里 vm._c 跟 snabbdom 里面的h()函数非常相似。

    vm._c 返回 vnode。那么 render 函数返回的也是 vnode。

    可以这样理解:

    • vm._c 其实就相当于 snabbdom 中的 h 函数
    • render 函数执行之后,返回的是 vnode

    vue的模板如何被渲染成html

    vm._update(vnode) {
      const prevVnode = vm._vnode;
      vm._vnode = vnode;
      if(!prevVnode){
        vm.$el = vm._patch_(vm.$el, vnode); // 第一次没有值
      } else {
        vm.$el = vm._patch_(prevVnode, vnode); // 有值的情况
      }
    }
    
    function updateComponent(){
      // vm._render即上面的render函数,返回vnode
      vm._update(vm._render())
    }
    

    render 函数和 vdom

    • updateComponent 中实现了 vdom 的 patch
    • 页面首次渲染执行 updateComponent
    • data 中每次修改属性,执行 updateComponent

    问题解答

    模板是什么?

    模板:字符串,有逻辑,嵌入 JS 变量……

    模板必须转换为 JS 代码(有逻辑、渲染 html、JS 变量)

    render 函数执行是返回 vnode

    updateComponent

    • updateComponent 中实现了 vdom 的 patch
    • 页面首次渲染执行 updateComponent
    • data 中每次修改属性,执行 updateComponent

    补充:vue的整个实现流程简易说明

    • 第一步:解析模板成 render 函数
    • 第二步:响应式开始监听
    • 第三步:首次渲染,显示页面,且绑定依赖
    • 第四步:data 属性变化,触发 rerender

    1. 把模板解析为 render 函数

    • 运用 with
    • 模板中的所有信息都被 render 函数包含
    • 模板中用到的 data 中的属性,都变成了 JS 变量
    • 模板中的 v-model v-for v-on 都变成了 JS 逻辑
    • render 函数返回 vnode

    2. 响应式开始监听

    • Object.defineProperty
    • 将data 的属性代理到 vm 上

    3. 首次渲染,显示页面且绑定依赖

    • 初次渲染,执行 updateComponent, 执行 vm._render()
    • 执行 render 函数,会访问到 data 下的数据
    • 会被响应式的 get 方法监听到
    • 执行 updateComponent,会走到 vdom 的 patch 方法
    • patch 将 vnode 渲染成 DOM,初次渲染完成

    4. data 属性变化,触发 rerender

    • 修改属性,被响应式的 set 监听到
    • set 中执行 updateComponent
    • updateComponent 重新执行 vm._render()
    • 生成的 vnode 和 prevVnode ,通过 patch 进行对比
    • 渲染到 html 中
  • 相关阅读:
    认识与设计Serverless(二)
    认识与设计Serverless(一)
    log4j2动态修改日志级别及拓展性使用
    log4j2高级配置(1)
    log4j2介绍及配置
    Java 并发编程篇
    JAVA多线程之volatile 与 synchronized 的比较
    springboot分布式锁学习
    springboot2连接多数据库mysql+oracle
    Mysql的分页查询优化
  • 原文地址:https://www.cnblogs.com/chrislinlin/p/12587723.html
Copyright © 2020-2023  润新知