详解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 相关文章推荐
JS 无法通过W3C验证的处理方法
Mar 09 Javascript
JS和jquery获取各种屏幕的宽度和高度的代码
Aug 02 Javascript
JS分页效果示例
Oct 11 Javascript
js利用数组length属性清空和截短数组的小例子
Jan 15 Javascript
node.js中的socket.io入门实例
Apr 26 Javascript
JavaScript实现班级随机点名小应用需求的具体分析
May 12 Javascript
jQuery实现带有洗牌效果的动画分页实例
Aug 31 Javascript
jQuery实现动态删除LI的方法
May 30 jQuery
vue axios用法教程详解
Jul 23 Javascript
vux uploader 图片上传组件的安装使用方法
May 15 Javascript
JS动态插入脚本和插入引用外部链接脚本的方法
May 21 Javascript
微信小程序学习笔记之跳转页面、传递参数获得数据操作图文详解
Mar 28 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
Apache下禁止php文件被直接访问的解决方案
2013/04/25 PHP
php读取文件内容的几种方法详解
2013/06/26 PHP
php实现复制移动文件的方法
2015/07/29 PHP
Yii使用技巧大汇总
2015/12/29 PHP
深入浅析PHP无限极分类的案例教程
2016/05/09 PHP
javascript中this做事件参数相关问题解答
2013/03/17 Javascript
js substr支持中文截取函数代码(中文是双字节)
2013/04/17 Javascript
jquery.cookie() 方法的使用(读取、写入、删除)
2013/12/05 Javascript
jQuery中$.ajax()和$.getJson()同步处理详解
2015/08/12 Javascript
详解jQuery中的事件
2016/12/14 Javascript
vue2.0移除或更改的一些东西(移除index key)
2017/08/28 Javascript
js中url对象化管理分析
2017/12/29 Javascript
通过一次报错详细谈谈Point事件
2018/05/17 Javascript
详解VUE调用本地json的使用方法
2019/05/15 Javascript
vue-cli3 配置开发与测试环境详解
2019/05/17 Javascript
微信自定义分享链接信息(标题,图片和内容)实现过程详解
2019/09/04 Javascript
extjs图形绘制之饼图实现方法分析
2020/03/06 Javascript
深入理解NumPy简明教程---数组1
2016/12/17 Python
Python爬取数据保存为Json格式的代码示例
2019/04/09 Python
对python中url参数编码与解码的实例详解
2019/07/25 Python
pytorch中如何使用DataLoader对数据集进行批处理的方法
2019/08/06 Python
python之PyQt按钮右键菜单功能的实现代码
2019/08/17 Python
python代码实现TSNE降维数据可视化教程
2020/02/28 Python
Python如何通过百度翻译API实现翻译功能
2020/04/02 Python
华为菲律宾官方网站:HUAWEI Philippines
2021/02/23 全球购物
Linux的文件类型
2016/07/05 面试题
物业经理求职自我评价
2013/09/22 职场文书
驾驶员岗位职责
2014/01/29 职场文书
计算机相关专业自荐信
2014/07/02 职场文书
教师文明餐桌光盘行动倡议书
2015/04/28 职场文书
电影红河谷观后感
2015/06/11 职场文书
行政处罚听证告知书
2015/07/01 职场文书
《社戏》教学反思
2016/02/22 职场文书
python flask开发的简单基金查询工具
2021/06/02 Python
浅谈Python魔法方法
2021/06/28 Java/Android
实体类或对象序列化时,忽略为空属性的操作
2021/06/30 Java/Android