Render函数

9.1 什么是Virtual Dom

Virtual Dom并不是真正意义上的DOM,而是一个轻量级的JavaScript对象,在状态发生变化时,Virtual Dom会进行Diff运算,来更新只需要被替换的DOM,而不是全部重绘。

与DOM操作相比,Virtual Dom是基于JavaScript计算的,所以开销会小很多。

export interface VNode {
    tag?: string;
    data?: VNodeData;
    children?: VNode[];
    text?: string;
    elm?: Node;
    ns?: string;
    context?: Vue;
    key?: string | number;
    componentOptions?: VNodeComponentOptions;
    componentInstance?: Vue;
    parent?: VNode;
    raw?: boolean;
    isStatic?: boolean;
    isRootInsert?: boolean;
    isComment?: boolean;
}

具体含义如下:

· tag 当前节点的标签名

· data 当前节点的数据对象

VNodeData代码如下:

export interface VNodeData {
    key?: string | number;
    slot?: string;
    scopedSlots?: { [key: string]: ScopedSlot };
    ref?: string;
    tag?: string;
    staticClass?: string;
    class?: any;
    staticStyle?: { [key: string]: any };
    style?: Object[] | Object;
    props?: { [key: string]: any };
    attrs?: { [key: string]: any };
    domProps?: { [key: string]: any };
    hook?: { [key: string]: Function };
    on?: { [key: string]: Function | Function[] };
    nativeOn?: { [key: string]: Function | Function[] };
    transition?: Object;
    show?: boolean;
    inlineTemplate?: {
        render: Function;
        staticRenderFns: Function[];
    };
    directives?: VNodeDirective[];
    keepAlive?: boolean;
}

· children 子节点,数组,也是VNode类型。

· text 当前节点的文本,一般文本节点或注释节点会有该属性。

· elm 当前虚拟节点对应的真实的DOM节点。

· ns 节点的namespace。

· context 编译作用域。

· functionalContext 函数化组件的作用域。

· key 节点的key属性,用于作为节点的标识,有利于patch的优化。

· componentOptions 创建组件实例时会用到的信息选项。

· child 当前节点对应的组件实例。

· parent 组件的占位节点。

· raw 原始html。

· isStatic 静态节点的标识。

· isRootInsert 是否作为根节点插入,被<transition>包裹的节点,该属性的值为false。

· isComment 当前节点是否是注释节点。

· isCloned 当前节点是否为克隆节点。

· isOnce 当前节点是否有v-once指令。

使用Virtual Dom就可以完全发会JavaScript的编程能力。在多数场景中,我们使用template就足够了,但在一些特定的场景下,使用Virtual Dom会更简单。

9.2 什么是Render函数

<div id="app">
    <anchor :level="2" title="特性">特性</anchor>
</div>
<script>
    Vue.component('anchor', {
        props: {
            level: {
                type: Number,
                required: true
            },
            title: {
                type: String,
                default: ''
            }
        },
        render: function (createElement) {
            return createElement(
                'h' + this.level,
                [
                    createElement(
                        'a',
                        {
                            domProps: {
                                href: '#' + this.title
                            }
                        },
                        this.$slots.default
                    )
                ]
            )
        }
    });
    var app = new Vue({
        el: '#app'
    })
</script>

9.3 createElement用法

9.3.1 基本参数

  createElement(
        // {String | Object | Function}
        // 一个HTML标签,组件选项,或一个函数
        // 必须return上述其中一个
        'div',
        // {Object}
        // 一个对应属性的数据对象,可选
        // 可以在template中使用
        {
            // 稍后详细介绍
        },
        // {String | Array}
        // 子节点(VNodes),可选
        [
            createElement('h1', 'hello world'),
            createElement(MyComponent, {
                props: {
                    someProp: 'foo'
                }
            }),
            'bar'
        ]
    )

第一个参数必选,可以是一个HTML标签,也可以是一个组件或函数;第二个是可选参数,数据对象,在template中使用。第三个是子节点,也是可选参数,用法一致。

对于第二个参数“数据对象”。具体的选项如下:

{
    //和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
    },
    //自定义指令
    directives: [
        {
            name: 'my-custom-directive',
            value: '2',
            expression: '1+1',
            arg: 'foo',
            modifiers: {
                bar: true
            }
        }
    ],
    //作用域slot
    //{name: props => VNode | Array<Vnode> }
    scopedSlots: {
        default: props => h('span', props.text)
    },
    //如果子组件有定义slot的名称
    slot: 'name-of-slot',
    //其他特殊顶层属性
    key: 'myKey',
    ref: 'myRef'
}

Render函数并不是在任何场景都是最优写法,只有当代码中有很多相似或重复的代码块时使用该函数可读性更高,否则还是使用template写法更加简洁。

9.3.2 约束

         所有的组件树中,如果VNode是组件或含有组件的slot,那么VNode必须是唯一的。

重复渲染多个组件的方法很多。重复子组件如下示例:

<div id="app">
         <ele></ele>
</div>
<script>
         //局部生命组件
         var Child = {
                   render:function(createElement){
                            return createElement('p','text');
                   }
         };
         Vue.component('ele',{
                   render:function(createElement){
                            return createElement('div',
                                     Array.apply(null,{
                                               length:5
                                     }),map(function(){
                                               return createElement(Child);
                                     })
                            )
                  }
         });
         var app = new Vue({
                   el:'#app'
         })
</script>

含有组件的slot,重复多个代码如下:

<div id="app">
         <ele>
                   <div>
                            <Child></Child>
                   </div>
         </ele>
</div>
<script>
         //全局注册组件
         Vue.component('Child',{
                   render:function(createElement){
                            return createElement('p','text');
                   }
         });
         Vue.component('ele',{
                   render:function(createElement){
                            //克隆slot节点的方法
                            function cloneVNode(vnode){
                                     //递归所有子节点
                                     const cloneChildren = vnode.children &&
                                     vnode.children.map(function(vnode){
                                               return cloneVNode(vnode);
                                     });
                                     const cloned = createElement(
                                              vnode.tag,
                                               vnode.data,
                                               cloneChildren
                                     );
                                     cloned.text = vnode.text;
                                     cloned.isComment = vnode.isComment;
                                     cloned.componentOptions = vnode.componentOptions;
                                     cloned.elm = vnode.elm;
                                     cloned.context = vnode.context;
                                     cloned.ns = vnode.ns;
                                     cloned.isStatic = vnode.isStatic;
                                     cloned.key = vnode.key;
                                  
                                     return cloned;
                            }
                            const vNodes = this.$slots.default;
                            const cloneVNodes = vNodes.map(function(vnode){
                                     return cloneVNode(vnode);
                            });
                            return createElement('div',
                                     vNodes,
                                     cloneVNodes,
                            )
                   }
         });
         var app = new Vue({
                   el:'#app'
         })
</script>

9.3.3 使用JavaScript代替模板功能

         在render函数中不能够使用Vue内置的指令,可以用原生的JavaScript实现。

比如v-if:

render:function(createElement){
         if(this.show){
                   return createElement('p','show的值为true');
         }else{
                   return createElement('p','show的值为false');
         }
}
render:function(createElement){
         var nodes = {};
         for(var i=0;i<this.list.length;i++){
                   nodes.push(createElement('p',this.list[i]));
         }
         return createElement('div',nodes);
}
<div id="app">
         <ele></ele>
</div>
<script>
         Vue.component('ele',{
                   render:function(createElement){
                            var _this = this;
                            return createElement('div',{
                                     createElement('input',{
                                               domProps:{
                                                        value:this.value
                                               },
                                               on:{
                                                        input:function(event){
                                                                 _this.value = event.target.value;
                                                        }
                                               }
                                     }),
                                     createElement('p','value:'+this.value)
                            })
                   },
                   data:function(){
                            return {
                                     value:''
                            
                   }
         });
         var app = new Vue({
                   el:'#app'
         })
</script>

9.4 函数化组件

         Vue.js提供了一个functionnal的布尔值选项,设置为true可以使组件无状态和无实例,也就是没有data和this上下文。这样更容易渲染,开销小很多。

         使用函数化组件时,render函数提供第二个参数context来提供临时上下文组件需要的data、props、slots、children、parent都是通过上下文传递。This.level改写为context.props.level,this.$slots.default改写为context.children。

9.5 JSX

         JSX是一种看起来像HTML,但实际上是JavaScript的语法扩展,它用更接近DOM结构的形式描述一个组件的UI和状态信息。需要插件babel-plugin-transform-vue-jsx来支持jsx语法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值