详解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中查找最近的共有祖先元素的实现代码
Dec 30 Javascript
Javascript数组操作函数总结
Feb 05 Javascript
jquery实现跳到底部,回到顶部效果的简单实例(类似锚)
Jul 10 Javascript
JS继承之借用构造函数继承和组合继承
Sep 07 Javascript
详谈jQuery Ajax(load,post,get,ajax)的用法
Mar 02 Javascript
微信小程序实现添加手机联系人功能示例
Nov 30 Javascript
C#实现将一个字符转换为整数
Dec 12 Javascript
vue webpack实用技巧总结
Apr 24 Javascript
Webpack path与publicPath的区别详解
May 03 Javascript
Vue数据绑定简析小结
May 07 Javascript
IntelliJ IDEA编辑器配置vue高亮显示
Sep 26 Javascript
element el-tree组件的动态加载、新增、更新节点的实现
Feb 27 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
DC宇宙的第一个英雄,堪称动漫史鼻祖,如今成为美国文化的象征
2020/04/09 欧美动漫
Ajax PHP分页演示
2007/01/02 PHP
php disk_free_space 返回目录可用空间
2010/05/10 PHP
PHP通过引用传递参数用法分析
2016/12/01 PHP
PHP简单实现正则匹配省市区的方法
2018/04/13 PHP
JavaScript中变量提升 Hoisting
2012/07/03 Javascript
基于jquery的图片幻灯展示源码
2012/07/15 Javascript
jquery遍历checkbox的注意事项说明
2014/02/21 Javascript
jQuery+ajax实现动态执行脚本的方法
2015/01/27 Javascript
jQuery删除一个元素后淡出效果展示删除过程的方法
2015/03/18 Javascript
JQuery显示、隐藏div的几种方法简明总结
2015/04/16 Javascript
addEventListener()与removeEventListener()解析
2017/04/20 Javascript
jQuery图片缩放插件smartZoom使用实例详解
2017/08/25 jQuery
Angular整合zTree的示例代码
2018/01/24 Javascript
使用live-server快速搭建本地服务器+自动刷新的方法
2018/03/09 Javascript
vue2.0基于vue-cli+element-ui制作树形treeTable
2019/04/30 Javascript
在vue中使用console.log无效的解决
2020/08/09 Javascript
解决vue组件没显示,没起作用,没报错,但该显示的组件没显示问题
2020/09/02 Javascript
[01:35:13]DOTA2-DPC中国联赛 正赛 DLG vs PHOENIX BO3 第一场 1月18日
2021/03/11 DOTA
[01:33]完美世界DOTA2联赛PWL S3 集锦第二期
2020/12/21 DOTA
Python图片裁剪实例代码(如头像裁剪)
2017/06/21 Python
Python对多属性的重复数据去重实例
2018/04/18 Python
利用Python实现在同一网络中的本地文件共享方法
2018/06/04 Python
python3.6数独问题的解决
2019/01/21 Python
在 Python 中使用 7zip 备份文件的操作
2020/12/11 Python
详解H5 活动页之移动端 REM 布局适配方法
2017/12/07 HTML / CSS
美国护肤咨询及美容产品电商:Askderm
2017/02/24 全球购物
安德玛加拿大官网:Under Armour加拿大
2019/10/02 全球购物
美国工业用品采购网站:Zoro.com
2020/10/27 全球购物
施工协议书范本
2014/04/22 职场文书
离婚答辩状怎么写
2015/05/22 职场文书
毕业生的自我鉴定表范文
2019/05/16 职场文书
教学工作总结范文5篇
2019/08/19 职场文书
如何使用Maxwell实时同步mysql数据
2021/04/08 MySQL
浅谈tf.train.Saver()与tf.train.import_meta_graph的要点
2021/05/26 Python
聊聊CSS粘性定位sticky案例解析
2022/06/01 HTML / CSS