stay hungry stay foolish

vue渲染原理

vue有两种构建方式(独立构建,运行时构建),独立构建包括了编译器部分,运行时构建不包括编译器

template,el,render的区别

template,el,render都可以指定具体渲染的内容,当实例参数中同时存在这三个参数时,其优先级为render>template>el,具体参考如下:

<!DOCTYPE html>
<html>

<head>
    <title>demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
</head>

<body>
    <div class="vapp-1"></div>
    <div class="vapp-2"></div>
    <div class="vapp-3"></div>
</body>
<script type="text/javascript">
new Vue({
    el: '.vapp-1', //此时el只是指定了渲染后的挂载点
    data: {
        info: '这是通过el属性获取挂载元素的outerHTML方式渲染。'
    },
    template: '<p>这是template属性模板渲染。</p>',
    render: function(h) {
        return h('p', {}, '这是render属性方式渲染。')
    }
})

new Vue({
    el: '.vapp-2', //此时el只是指定了渲染后的挂载点
    data: {
        info: '这是通过el属性获取挂载元素的outerHTML方式渲染。'
    },
    template: '<p>这是template属性模板渲染。</p>'
})

new Vue({
    el: '.vapp-3', //此时el.outerHTML为待编译的模板
    data: {
        info: '这是通过el属性获取挂载元素的outerHTML方式渲染。'
    }
})
</script>

</html>

结果:

其实这三种编译模式本质都一样,template和el模板都将被编译成render函数,vue渲染是最终都是执行render函数来进行dom的挂载。

如果构建vue实例时没有指定render函数,则必须使用独立构建的vue库文件(vue.js属于独立构建的库文件),如果将上例代码中的vue.js换成vue.runtime.js(运行时库文件),则将报错。

<!DOCTYPE html>
<html>

<head>
    <title>demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.runtime.js"></script>
</head>

<body>
    <div class="vapp-1"></div>
    <div class="vapp-2"></div>
    <div class="vapp-3"></div>
</body>
<script type="text/javascript">
new Vue({
    el: '.vapp-1',
    data: {
        info: '这是通过el属性获取挂载元素的outerHTML方式渲染。'
    },
    template: '<p>这是template属性模板渲染。</p>',
    render: function(h) {
        return h('p', {}, '这是render属性方式渲染。')
    }
})

new Vue({
    el: '.vapp-2',
    data: {
        info: '这是通过el属性获取挂载元素的outerHTML方式渲染。'
    },
    template: '<p>这是template属性模板渲染。</p>'
})

new Vue({
    el: '.vapp-3',
    data: {
        info: '这是通过el属性获取挂载元素的outerHTML方式渲染。'
    }
})
</script>

</html>

结果:

webpack打包vue项目时默认使用的是运行时库,如果需要使用独立构建的库,可以配置别名

resolve: {
    alias: {
        'vue$': 'vue/dist/vue.common.js', //针对CommonJS模式
        //'vue$': 'vue/dist/vue.esm.js', //针对ES Module模式
        //'vue$': 'vue/dist/vue.js', //针对UMD模式
    }
}

render函数

render函数没有template里面v-if,v-for等功能,要想实现,需要我们自己手动模拟相关功能。

render模拟v-if

<!DOCTYPE html>
<html>

<head>
    <title>demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.min.js"></script>
</head>

<body>
    <div id="app1">
        <div v-if="level==1">这是level1</div><div v-if="level==2">这是level2</div>
    </div>
    <div id="app2">
    </div>
</body>
<script type="text/javascript">
new Vue({
    el: '#app1',
    data: {
        level: 1
    }
})

new Vue({
    data: {
        level: 2
    },
    render: function(createElement){
        if(this.level == 1){
            var dom = createElement('div',{},'这是level1');
        }else{
            var dom = createElement('div',{},'这是level2');
        }
        return dom;
    }
}).$mount('#app2')

</script>

</html>

结果:

render函数模拟v-model

<!DOCTYPE html>
<html>

<head>
    <title>demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.min.js"></script>
</head>

<body>
    <div id="app">
        <my-component ref="input" @input="input"></my-component>
    </div>
</body>
<script type="text/javascript">
Vue.component('my-component',{
    data: {
        value: ''
    },
    render: function(createElement) {
        var self = this;
        var dom = createElement('input', {
            domProps: {
                value: 'default',
            },
            on: {
                input: function(event) {
                    self.value = event.target.value;
                    self.$emit('input',self.value);
                }
            }
        });
        return dom;
    }
})
new Vue({
    el: '#app',
    methods: {
        input: function(value) {
            console.log(value,this.$refs.input.value);
        }
    }
})
</script>

</html>

render函数之$slots

render函数里的$slots只能在组件里使用,vue全局实例里的render函数没有$slots的概念

<!DOCTYPE html>
<html>

<head>
    <title>demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.min.js"></script>
</head>

<body>
    <div id="app">
        <my-component><span>lisong</span></my-component>
    </div>
</body>
<script type="text/javascript">
Vue.component('my-component',{
    render: function(createElement) {
        var self = this;
        var dom = createElement('div', 'hello '+this.$slots.default[0].children[0].text); //hello lisong
        console.log(this.$slots);
        return dom;
    }
})
new Vue({
    el: '#app'
})
</script>

</html>

createElement

createElement详细参数

{
  // 和`v-bind:class`一样的 API
  'class': {
    foo: true,
    bar: false
  },
  // 和`v-bind:style`一样的 API
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 正常的 HTML 特性
  attrs: {
    id: 'foo'
  },
  // 组件 props
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  domProps: {
    innerHTML: 'baz'
  },
  // 事件监听器基于 `on`
  // 所以不再支持如 `v-on:keyup.enter` 修饰器
  // 需要手动匹配 keyCode。
  on: {
    click: this.clickHandler
  },
  // 仅对于组件,用于监听原生事件,而不是组件内部使用
  // `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  // 赋值,因为 Vue 已经自动为你进行了同步。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // Scoped slots in the form of
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果组件是其他组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其他特殊顶层属性
  key: 'myKey',
  ref: 'myRef'
}