详解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 相关文章推荐
jquery 子窗口操作父窗口的代码
Sep 21 Javascript
javascript 循环读取JSON数据的代码
Jul 17 Javascript
jQuery数据缓存功能的实现思路及简单模拟
May 27 Javascript
js 左右悬浮对联广告特效代码
Dec 12 Javascript
jQuery EasyUI datagrid实现本地分页的方法
Feb 13 Javascript
JavaScript基于setTimeout实现计数的方法
May 08 Javascript
在JavaScript中使用NaN值的方法
Jun 05 Javascript
JavaScript操作表单实例讲解(上)
Jun 20 Javascript
浅谈jQuery this和$(this)的区别及获取$(this)子元素对象的方法
Nov 29 Javascript
详解使用 Node.js 开发简单的脚手架工具
Jun 08 Javascript
Vuex中的State使用介绍
Jan 19 Javascript
vue+element实现打印页面功能
May 20 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
让Json更懂中文(JSON_UNESCAPED_UNICODE)
2011/10/27 PHP
Window 7/XP 安装Apache 2.4与PHP 5.4 的过程详解
2013/06/02 PHP
PHP+JS实现大规模数据提交的方法
2015/07/02 PHP
jquery中输入验证中一个不错的效果
2010/08/21 Javascript
JQuery 选择器、过滤器介绍
2011/02/14 Javascript
JavaScript高级程序设计(第3版)学习笔记2 js基础语法
2012/10/11 Javascript
HTML长文本截取含有HTML代码同样适用的两种方法
2013/07/31 Javascript
浅谈JS闭包中的循环绑定处理程序
2014/11/09 Javascript
详解JavaScript编程中正则表达式的使用
2015/10/25 Javascript
JS清除文本框内容离开在恢复及鼠标离开文本框时触发js的方法
2016/01/12 Javascript
ES6生成器用法实例分析
2017/04/10 Javascript
JavaScript异步上传图片文件的实例代码
2017/07/04 Javascript
mpvue 单文件页面配置详解
2018/12/02 Javascript
个人小程序接入支付解决方案
2019/05/23 Javascript
vue图片加载失败时用默认图片替换的方法
2019/08/29 Javascript
python调用API实现智能回复机器人
2018/04/10 Python
利用Pandas读取文件路径或文件名称包含中文的csv文件方法
2018/07/04 Python
Python发展简史 Python来历
2019/05/14 Python
基于python框架Scrapy爬取自己的博客内容过程详解
2019/08/05 Python
Python爬虫 批量爬取下载抖音视频代码实例
2019/08/16 Python
python 实现多线程下载m3u8格式视频并使用fmmpeg合并
2019/11/15 Python
加拿大购物频道:The Shopping Channel
2016/07/21 全球购物
澳大利亚领先的睡衣品牌:Peter Alexander
2016/08/16 全球购物
澳大利亚潮流尖端的快时尚品牌:Cotton On
2016/09/26 全球购物
办公室保洁员岗位职责
2013/12/02 职场文书
初中同学聚会邀请函
2014/02/03 职场文书
环保公益广告语
2014/03/13 职场文书
梅花魂教学反思
2014/04/25 职场文书
音乐节策划方案
2014/06/09 职场文书
暑期政治学习心得体会
2014/09/02 职场文书
党员检讨书范文
2014/12/27 职场文书
小学推普周活动总结
2015/05/07 职场文书
教学质量月活动总结
2015/05/11 职场文书
2016年清明节期间群众祭祀活动工作总结
2016/04/01 职场文书
CSS3实现的侧滑菜单
2021/04/27 HTML / CSS
Apache Kafka 分区重分配的实现原理解析
2022/07/15 Servers