美文网首页前端开发那些事让前端飞
说说 Vue.js 中的 Render 函数

说说 Vue.js 中的 Render 函数

作者: deniro | 来源:发表于2019-01-13 14:34 被阅读6次

    1 引子

    锚点是网页中超级链接的一种,又叫命名锚记。命名锚记像一个迅速定位器一样是一种页面内的超级链接,运用相当普遍。它的英文名是 anchor。

    使用命名锚记可以在文档中设置标记,这些标记通常放在文档的特定主题处或顶部。然后可以创建到这些命名锚记的链接,这些链接可快速将访问者带到指定位置。

    如果把这个功能封装为组件,它一般是这样写的:

    html:

    <div id="app">
        <anchor :level="3" title="news">6光年远“超级地球”或存在生命</anchor>
        <script type="text/x-template" id="anchor">
            <div>
                <h1 v-if="level===1">
                    <a :href="'#'+title">
                        <slot></slot>
                    </a>
                </h1>
                <h2 v-if="level===2">
                    <a :href="'#'+title">
                        <slot></slot>
                    </a>
                </h2>
                <h3 v-if="level===3">
                    <a :href="'#'+title">
                        <slot></slot>
                    </a>
                </h3>
                <h4 v-if="level===4">
                    <a :href="'#'+title">
                        <slot></slot>
                    </a>
                </h4>
                <h5 v-if="level===5">
                    <a :href="'#'+title">
                        <slot></slot>
                    </a>
                </h5>
                <h6 v-if="level===6">
                    <a :href="'#'+title">
                        <slot></slot>
                    </a>
                </h6>
            </div>
        </script>
    </div>
    <div style="margin-top: 1500px;">
    </div>
    <a id="news">
        研究人员发现,距离我们6光年远的一颗巨大的“超级地球”行星可能存在简单生命。Barnard b(或GJ 699 b)是最近发现的一颗围绕巴纳德星(是距离地球第二近的恒星)运行的“超级地球”行星。Barnard
        b被认为极度寒冷,温度与木卫二相似,约为摄氏零下150度。
    </a>
    

    js:

    Vue.component('anchor', {
        template: '#anchor',
        props: {
            level: {
                type: Number,
                required: true
            },
            title: {
                type: String,
                default: ''
            }
        }
    });
    
    
    var app = new Vue({
        el: '#app',
        data: {}
    });
    

    当点击锚点 “6光年远“超级地球”或存在生命” 时,会跳到当前页所指定的内容。锚点粗细根据 level 值来决定。

    这样写的缺点是:组件的 template 代码冗长,只有少部分不同,其它大部分都是相同的。

    我们可以使用 Render 函数,通过拼接字符串的形式来构造 <h> 元素。

    html:

    <div id="app2">
        <anchor2 :level="3" title="news">6光年远“超级地球”或存在生命 </anchor2>
    </div>
    

    js:

    Vue.component('anchor2', {
        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 app2 = new Vue({
        el: '#app2',
        data: {}
    });
    

    效果相同,但这里的场景,使用 Render 函数明显简化了代码。

    以上示例 DEMO

    2 createElement

    我们使用 createElement 来构建 Vue.js 的 Virtual Dom 模板。

    2.1 参数

    createElement 有三个参数:

    参数 是否必选 说明
    HTML 标签、组件选项或函数 必选 -
    数据对象 可选 在 template 中使用。
    子节点 可选 可为 String 或 Array。

    上述示例中,加上注释,我们就会更清楚一些:

    return createElement(
        // HTML 标签、组件选项或函数(String|Object|Function)
        'h' + this.level,
        //对应属性的数据对象(可选)
        {},
        //子节点,可为 String 或 Array
        [
            createElement(
                'a',
                {
                    domProps: {
                        href: '#' + this.title
                    }
                },
                this.$slots.default
            )
        ]
    )
    

    其中的数据对象是这样的结构:

    //对应属性的数据对象(可选)
    {
        //对应 v-bind:class
        'class': {},
        //对应 v-bind:style
        style: {},
        //HTML 属性
        attrs: {},
        //组件 props
        props: {},
        //DOM 属性
        domProps: {},
        //自定义 on 事件监听器,不支持修饰器
        on: {},
        //仅适用于组件,用于监听原生事件
        nativeOn: {},
        //自定义指令
        directives: [],
        //作用域 slot
        // {name:props => VNode | Array<VNode>}
        scopedSlots: {},
        //子组件中的 slot 名称(如果组件中有定义)
        slot: 'xxx',
        //其它自定义属性
        xxx: 'xxx'
    }
    

    在此之前,我们在 template 中都是在组件标签中使用 v-bind:class 等指令,而在 Render 函数中,这些都定义在数据对象中咯。

    虽然 Render 函数灵活,但在某些场景下却显得臃肿。让我们来看一个例子,假设我们需要定义一个绑定了 class 以及 click 的简单 div 组件,如果用 template 方式,是这样编码的:

    html:

    <div id="app">
        <e></e>
    </div>
    

    js:

    Vue.component('e', {
        template: '\
        <div id="e"\
        :class="{show:isShow}"\
        @click="click">后挡风玻璃上的细线竟有如此妙用</div>\
        ',
        data: function () {
            return {
                isShow: true
            }
        },
        methods: {
            click: function () {
                console.log('点击');
            }
        }
    })
    
    var app = new Vue({
        el: '#app',
        data: {}
    });
    

    css:

    .show {
        cursor: pointer
    }
    

    如果使用 Render 函数,那么编码方式是这样的:

    Vue.component('e2',{
        render:function (createElement) {
            return createElement(
                'div',
                {
                    class:{
                        'show':this.isShow
                    },
                    attrs:{
                        id:'e2'
                    },
                    on:{
                        click:this.click
                    }
                },
                '后挡风玻璃上的细线竟有如此妙用'
            )
        },
        ...
    });
    

    所以,在这个场景中,使用 template 的编码方式更简洁。

    2.2 限制

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

    假设我们希望在子节点内渲染出两个子组件。

    2.2.1 错误示例 1 —— 重复使用组件

    html:

    <div id="app3">
        <e3></e3>
    </div>
    

    js:

    var Child3 = {
        render: function (createElement) {
            return createElement('p', '《怪物猎人》系列世界观设定科普');
        }
    };
    Vue.component('e3',{
        render: function (createElement) {
            //使用组件 Child3 来创建子节点
            var ChildNode= createElement(Child3);
            return createElement('div',[
                ChildNode,ChildNode
            ])
    
        }
    });
    
    var app3 = new Vue({
        el: '#app3',
        data: {}
    });
    

    渲染结果:

    <div><p>《怪物猎人》系列世界观设定科普</p></div>
    

    因为受到限制,所以实际只渲染出一个子组件!

    2.2.2 错误示例 2 —— 重复使用组件的 slot

    html:

    <div id="app4">
        <e4>
            <div>
                <Child4></Child4>
            </div>
        </e4>
    </div>
    

    js:

    //全局注册组件
    Vue.component('Child4', {
        render: function (createElement) {
            return createElement('p', '高端商务本存在必要性解读:解决用户痛点更专业');
        }
    });
    Vue.component('e4', {
        render: function (createElement) {
            return createElement('div', [
                this.$slots.default,
                this.$slots.default
            ])
        }
    });
    var app4 = new Vue({
        el: '#app4',
        data: {}
    });
    
    

    渲染结果:

    <div>
        <p>高端商务本存在必要性解读:解决用户痛点更专业</p>
    </div>
    
    

    也是因为受到限制,所以实际只渲染出一个子组件!

    有以下方法可以渲染出多个组件——

    2.2.3 循环与工厂函数

    html:

    <div id="app5">
        <e5></e5>
    </div>
    

    js:

    var Child5 = {
        render: function (createElement) {
            return createElement('p', '智能科技如何助力实体经济和大众创业?');
        }
    };
    Vue.component('e5', {
        render: function (createElement) {
            return createElement('div', [
                Array.apply(null, {
                    length: 3
                }).map(function () {
                    return createElement(Child5);
                })
            ])
        }
    });
    var app5 = new Vue({
        el: '#app5',
        data: {}
    });
    
    

    这里通过 apply 工厂函数设定了一个长度为 3 的数组,并通过 map 函数,对数组的每一项创建出子组件。

    相关知识点:

    • 每个函数都包含两个非继承来的的方法:apply() 和 call(),它们都用于在特定的作用域下调用函数,实际上等于设置函数体内 this 对象的值。apply() 接收两个参数(运行函数的作用域、参数数组),其中的参数数组可以是 Array 实例,也可以是 arguments 对象。
    • map() 是迭代方法,它对数组的每一项运行给定函数,返回每次函数调用的结果组成的数组。

    效果:

    2.2.4 深度拷贝 slot

    html:

    <div id="app6">
        <e6>
            <div>
                <Child6></Child6>
            </div>
        </e6>
    </div>
    

    js:

    Vue.component('Child6', {
        render: function (createElement) {
            return createElement('p', '线上教育发展将呈现四大趋势');
        }
    });
    Vue.component('e6', {
        render: function (createElement) {
            //拷贝 slot 节点
            function copy(vnode) {
                //递归遍历所有子节点,并拷贝
                const children = vnode.children && vnode.children.map(function (vnode) {
                    return copy(vnode);
                });
                const element = createElement(
                    vnode.tag,
                    vnode.data,
                    children
                );
                element.text = vnode.text;
                element.isComment = vnode.isComment;
                element.componentOptions = vnode.componentOptions;
                element.elm = vnode.elm;
                element.context = vnode.context;
                element.ns = vnode.ns;
                element.isStatic = vnode.isStatic;
                element.key = vnode.key;
                return element;
            }
    
            const vNodes = this.$slots.default;
            const copyVNodes1 = vNodes.map(function (vnode) {
                return copy(vnode);
            });
            const copyVNodes2 = vNodes.map(function (vnode) {
                return copy(vnode);
            });
    
            return createElement('div', [
                vNodes, copyVNodes1, copyVNodes2
            ])
        }
    });
    var app6 = new Vue({
        el: '#app6',
        data: {}
    });
    

    在 Render 函数中,我们创建了一个拷贝 slot 节点的工厂函数,通过递归将 slot 中的所有子节点都做了拷贝,同时还复制了 vnode 中的关键属性。这种手法主要运用于独立组件的开发。

    效果:

    以上示例

    相关文章

      网友评论

        本文标题:说说 Vue.js 中的 Render 函数

        本文链接:https://www.haomeiwen.com/subject/hqdcdqtx.html