详解Vue 动态添加模板的几种方法


Posted in Javascript onApril 25, 2017

以下方法只适用于 Vue1.0 版本,推荐系数由高到低排列。

通常我们会在组件里的 template 属性定义模板,或者是在 *.vue 文件里的 template 标签里写模板。但是有时候会需要动态生成模板的需求,例如让用户自定义组件模板,或者设置组件的布局。

例如要做一个类 select 的组件,用户传入 options 数据,通过 value prop 获取选中值,最基本的原型如下。

Vue.component('XSelect', {
 template: `
 <div class="select">
 <input :value="value" readonly />
 <div
 class="option"
 v-for="option in options"
 @click="value = option.value">
 <span v-text="option.label"></span>
 </div>
 </div>`,

 props: ['value','options']
})

如果此时需要增加一个 API 支持让用户自定义 option 部分的模板。此处用 slot 并不能解决问题。

通过 $options.template 修改

通过打印组件对象可以获得一个信息,在 $options 里定义了一个 template 属性,写在 template 标签里的模板,最终编译后也会在 $options.template 里。通过文档的生命周期 可以得知,在 created 的时候, 实例已经结束解析选项, 但是还没有开始 DOM 编译 也就是说,如果用户通过 prop 的数据我们可以获得,但是模板其实还没有渲染成 DOM。经过测试,在 created 修改 this.$options.template 是可以改变最终生成的 DOM 的,同时也能拿到 props 的内容。

那么我们可以修改下代码,使其支持自定义模板

Vue.component('XSelect', {
 props: [
'value',
'options',
 {
 name: 'template',
default:'<span v-text="option.label"></span>'
 }
 ],

 created() {
varoptionTpl =this.template

this.$options.template =`
 <div class="select">
 <input :value="value" readonly />
 <div
 class="option"
 v-for="option in options"
 @click="value = option.value">
${optionTpl}
 </div>
 </div>`
 }
})

用户使用是就可以传入模板了

<x-select
:value.sync="value"
template="<span>标签: {{ option.label }}, 值: {{ option.value }}</span>"
:options="[
 {value: 1, label: 'a'},
 {value: 2, label: 'b'},
 {value: 3, label: 'c'}
 ]">
</x-select>

可能存在的问题

我们知道 Vue 在内部帮我们做了许多优化,但是在这里可能会由于某些优化导致动态拼接的模板无法渲染成功。例如这里我们不使用 v-for 而是手工遍历 options 生成需要的 HTML

consttpl = options.map(opt =>`<div>${this.optionTpl}</div>`)

this.$options.template =`
 <div class="select">
 <input :value="value" readonly>
${tpl}
 </div>`

这里会导致一个 BUG,如果一个页面有多个 x-select 组件,并且 options 长度不一样,会导致长的 options 的组件后面几个选项渲染不出来。究其原因是 Vue 会帮我们缓存模板编译结果。翻看代码可以找到 vue/src/instance/internal/lifecycle.js 里有做优化,同时提供的 _linkerCachable 本意是给 内联模板 使用。我们可以通过设置 this.$options._linkerCachable = false 达到我们的目的。

这样我们就可以开发让用户自定义布局的组件了,用户传入布局参数,通过手工拼接模板,设置了 _linkerCachable = false 也不会被缓存。

通过 $options.partials 动态添加 partial

使用 partials 也能达到添加自定义模板的目的,但是通常的做法是要全局注册 partial,这么做并不优雅。 vue-strap 就是这么做的。如果重名了会被覆盖(初次渲染不会,但是数据更新重新渲染 DOM 时就会被覆盖)。

通过文档我们知道可以在组件内部通过 partials 属性注册局部的 partial,因此自然而然也可以在 this.$options.partials 去动态添加了。

Vue.component('XSelect', {
 template: `
 <div class="select">
 <input :value="value" readonly />
 <div
 class="option"
 v-for="option in options"
 @click="value = option.value">
 <partial name="option"></partial>
 </div>
 </div>`,

 props: ['template','value','options'],

 partials: {
 option: '<span v-text="option.label"></span>'
 },

 created() {
if(this.template) {
this.$options.partials.option =this.template
 }
 }
})

用 interpolate 渲染模板

这种方式就略显蛋疼,而且效率最差。 interpolate 也是我最开始做动态渲染模板想到的方式,不推荐使用。

Vue.component('XSelect', {
 template: `
 <div class="select">
 <input :value="value" readonly />
 <div
 class="option"
 v-for="option in options"
 @click="value = option.value"
 v-html="renderOption(option)">
 </div>
 </div>`,

 props: [
'value',
'options',
 {
 name: 'template',
default:'<span v-text="option.label"></span>'
 }
 ],

 methods: {
 renderOption(option) {
this.option = option
returnthis.$interpolate(this.template)
 }
 }
})

Vue2.0

目前并没有找到合适的解决方案。2.0 的 Vue 将 compile 工作提前,并且 compiler 也是单独一个包(除非你直接引用的是 vue.js 文件,包含 compiler 和 runtime,那么第一种方法是适用的),那么并不能动态的生成模板。除非用户传入的是 render 能识别的 DOM tree。

按照这样的思路,其实可以让用户传入的模板预先编译好,传入到组件内,拼接 DOM tree 看起来也能解决问题。那么可以这么玩。

看看就好, 性能太渣

首先要安装 Vue JSX 的 相关插件

组件

Vue.component({
 name: 'XSelect',

 render(h) {
// 这里获得的 this.template 其实是一个函数,调用该函数返回 DOM
// 因此这里的关键代码是拼接一个新的函数,接受 `option` 参数以及上下文
// 使用 new Function 创建一个新函数

return(
<divclass="select">
<inputvalue={this.value}readonly/>
 {
 this.options.map(option =>
<div
on-click={() => this.$emit('input', option.value) }
 class="option">
 { new Function('option', 'return ' + this.template)(option)(h) }
</div>
 )
 }
</div>)
 },

 props: ['template', 'value', 'options']
})

入口文件

newVue({
 el: '#app',

 data () {
return{
 value: ''
 }
 },

 created() {
// 初始化需要传入的模板,这里会被 Vue 的 JSX 插件转成 DOM tree
this.template = h =><span>标签: { option.label }, 值: { option.value }</span>
 },

 render(h) {
return(
<x-select
v-model="value"
:template="template"
:options="[
 {value: 1, label: 'a'},
 {value: 2, label: 'b'},
 {value: 3, label: 'c'}
 ]">
</x-select>)
 }
})

综上,在 Vue 1.x 里不存在 预编译 的概念,所以想动态修改模板还是有许多方式的,甚至还可以结合 <slot></slot> 取到 slot 里的内容拼接进模板里。但 2.0 就麻烦了,并找不到理想的方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
设置下载不需要倒计时cookie(倒计时代码)
Nov 19 Javascript
javascript new 需不需要继续使用
Jul 02 Javascript
让元素在网页中可拖动示例代码
Aug 13 Javascript
ajax请求乱码的解决方法(中文乱码)
Apr 10 Javascript
自编jQuery插件实现模拟alert和confirm
Sep 01 Javascript
JS实现控制表格内指定单元格内容对齐的方法
Mar 30 Javascript
使用coffeescript编写node.js项目的方法汇总
Aug 05 Javascript
Javascript的表单验证-提交表单
Mar 18 Javascript
Vue.js自定义指令学习使用详解
Oct 19 Javascript
Vue数字输入框组件的使用方法
Oct 19 Javascript
js重写alert事件(避免alert弹框标题出现网址)
Dec 04 Javascript
用几道面试题来看JavaScript执行机制
Apr 30 Javascript
详解vue-cli + webpack 多页面实例应用
Apr 25 #Javascript
基于Vue实现timepicker
Apr 25 #Javascript
VueJS如何引入css或者less文件的一些坑
Apr 25 #Javascript
详解Angular 4.x 动态创建组件
Apr 25 #Javascript
Angular 4.x中表单Reactive Forms详解
Apr 25 #Javascript
Angular 4.x 动态创建表单实例
Apr 25 #Javascript
AngularJS动态菜单操作指令
Apr 25 #Javascript
You might like
php中理解print EOT分界符和echo EOT的用法区别小结
2010/02/21 PHP
jQuery+PHP实现的掷色子抽奖游戏实例
2015/01/04 PHP
PHP实现过滤各种HTML标签
2015/05/17 PHP
php 生成签名及验证签名详解
2016/10/26 PHP
如何利用预加载优化Laravel Model查询详解
2017/08/11 PHP
tp5.1 框架join方法用法实例分析
2020/05/26 PHP
常用参考资料(手册)下载或者链接
2006/07/22 Javascript
javascript实现的在当前窗口中漂浮框的代码
2010/03/15 Javascript
理解Javascript_06_理解对象的创建过程
2010/10/15 Javascript
JavaScript检测弹出窗口是否已经关闭的方法
2015/03/24 Javascript
JavaScript获取页面中表单(form)数量的方法
2015/04/03 Javascript
详细解读JavaScript编程中的Promise使用
2015/07/27 Javascript
jQuery unbind 删除绑定事件详解
2016/05/24 Javascript
打造自己的jQuery插件入门教程
2016/09/23 Javascript
Mac下使用charles遇到的问题以及解决办法
2017/01/10 Javascript
HTML5+JS+JQuery+ECharts实现异步加载问题
2017/12/16 jQuery
微信小程序实现左右联动的实战记录
2018/07/05 Javascript
Vue+webpack项目配置便于维护的目录结构教程详解
2018/10/14 Javascript
vue中uni-app 实现小程序登录注册功能
2019/10/12 Javascript
用Python程序抓取网页的HTML信息的一个小实例
2015/05/02 Python
python实现关闭第三方窗口的方法
2019/06/28 Python
python 含子图的gif生成时内存溢出的方法
2019/07/07 Python
python设计微型小说网站(基于Django+Bootstrap框架)
2019/07/08 Python
Django生成PDF文档显示网页上以及PDF中文显示乱码的解决方法
2019/12/17 Python
全面解析CSS Media媒体查询使用操作(推荐)
2017/08/15 HTML / CSS
HTML5 语音搜索只需一句代码
2013/01/03 HTML / CSS
英国PC组件和在线电脑商店:SCAN
2019/04/18 全球购物
Linux不知道文件后缀名怎么判断文件类型
2012/04/26 面试题
创先争优制度
2014/01/21 职场文书
毕业生个人求职自荐信
2014/02/26 职场文书
写求职信要注意什么问题
2014/04/12 职场文书
2014医学院领导班子对照检查材料思想汇报
2014/09/19 职场文书
分居协议书范本(律师见证版)
2014/11/26 职场文书
Python机器学习之基础概述
2021/05/19 Python
关于Numpy之repeat、tile的用法总结
2021/06/02 Python
Tomcat项目启动失败的原因和解决办法
2022/04/20 Servers