前端开发那些事让前端飞

说说 Vue.js 中的 Render 函数

2019-01-13  本文已影响6人  deniro

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 函数,对数组的每一项创建出子组件。

相关知识点:

效果:

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 中的关键属性。这种手法主要运用于独立组件的开发。

效果:

以上示例

上一篇下一篇

猜你喜欢

热点阅读