在Vue项目中,想要动态为页面上添加组件,是一件十分容易就能够实现的事情,这必须要得益于Vue框架的强力支持! 但有时候往往就是由于事情变得简单起来后,却突然忘记了要如何去实现。这大概就是对代码走火入魔的一种解释吧!

为页面动态添加组件,在实现区别上,我总结出了2种:

  1. 数据动态渲染已有组件。
  2. 先生成组件再添加到界面上。

〇、提出需求

假设现有一需求:要求实现某标签的添加,删除功能。效果如下:

这种效果想必还是比较合适用来理解组件的动态添加和移除的。 下面将对两种实现方案分别进行讲述。在这之前先预览一下案列项目结构,不用担心,这是使用 vue-cli@3 工具创建的 项目,应该和大多数人的项目目录结构相似:

一、数据动态渲染组件

在 Vue 的官方文档中,条件渲染列表渲染这两处文档直接为你展示了如何通过数据来控制组件的渲染, 不过有一点要注意的是,Vue 官方文档,都是在讲述html文件中如何使用vue,而并没有在单文件组件的篇幅里进行 较多的讲解。虽然用法的确是相同的,但不免理解起来有一层隔阂。

此处,就将以单文件组件解释说明如何完成组件的动态渲染,也就是动态的添加和移除什么的。

1、建立标签组件

使用数据来动态渲染组件,前提是要先有这个组件,所以就先把组件创建出来,在 components 目录下创建名为 tag.vue 的组件,组件代码如下:

  1. <!-- tag.vue -->
  2. <template>
  3. <div class="tag">
  4. {{text}}
  5. <button @click="onCloseClick">×</button>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. name: "tag",
  11. props: {
  12. // 标签内容
  13. text: String
  14. },
  15. methods: {
  16. onCloseClick () {
  17. // 将删除标签事件暴露除去
  18. this.$emit("delete", this.text);
  19. }
  20. }
  21. }
  22. </script>
  23. <style scoped>
  24. .tag {
  25. display: inline-block;
  26. margin: 3px;
  27. }
  28. </style>

这个组件只有简单的删除功能,能够表示意思意思就好!

2、修改页面,实现渲染

修改 App.vue 文件,引入tag组件,然后根据数据对tag组件进行动态的渲染:

  1. <!-- App.vue -->
  2. <template>
  3. <div>
  4. <h3>标签管理</h3>
  5. <!-- 标签容器已经被初始化而且内容多余0条,则显示内容 -->
  6. <div v-if="tags && tags.length > 0">
  7. <tag v-for="tag in tags" :text="tag" :key="tag" @delete="onTagDelete"/>
  8. </div>
  9. <!-- 否则,标签只是被初始化了,但是没有数据 -->
  10. <div v-else-if="tags">没有标签了</div>
  11. <!-- 标签容器还没被初始化 -->
  12. <div v-else>标签加载中...</div>
  13. <button @click="addTag">添加标签</button>
  14. </div>
  15. </template>
  16. <script>
  17. import Tag from './components/tag.vue';
  18. import Util from './Util';
  19. export default {
  20. name: 'app',
  21. components: {
  22. Tag
  23. },
  24. created() {
  25. // 从网络加载tag数据.
  26. Util.getTagsFromNet()
  27. .then(result => {
  28. if (result.code !== 200) {
  29. return Promise.reject(new Error(result.message));
  30. }
  31. // 数据加载成功,将数据赋值给 tags 即可。
  32. this.tags = result.data;
  33. })
  34. .catch(error => {
  35. window.alert(error.message);
  36. })
  37. },
  38. methods: {
  39. // 添加tag
  40. addTag() {
  41. this.tags.push("标签" + this.tags.length);
  42. },
  43. onTagDelete(tag) {
  44. // 从数组中移除。
  45. this.tags.splice(this.tags.indexOf(tag), 1);
  46. }
  47. },
  48. data() {
  49. return {
  50. tags: undefined, // 初始化是没有 tag数据的,在页面打开后,通过网络请求获取标签。
  51. }
  52. }
  53. }
  54. </script>
  55. <style scoped></style>

运行程序,效果如下:

最终可以看到,这样的方式来实现动态组件渲染,宗旨就是:数据有,组件有,数据无,组件无。这也是Vue框架十分 建议的实现方式。我本人也建议使用这样的方式来实现。

二、先生成组件再添加到界面上

此方法不用事先将组件引入到界面中。而是当数据获取到时,使用数据建立组件,然后将组件添加到界面上。 这种思路显然完全正确无误,不过在Vue项目里,这样虽说可以实现,但是门槛略高。但了解这种方法也会为今后的开发 带来不少的便利。

1、了解 Vue 实例的创建

要进行下一步的讲解,先必须要了解 Vue 实例的创建。在我们平时开发Vue但也项目是,通常都能在 main.js 里看到这样的代码:

  1. // main.js
  2. import Vue from 'vue'
  3. import App from './App.vue'
  4. Vue.config.productionTip = false
  5. new Vue({
  6. render: h => h(App),
  7. }).$mount('#app')

这很容易理解,因为这样就算创建了我们单页面的整个根Vue实例了。那如果要创建我们自己想要的动态组件, 并且创建出来的组件要可以被加入到这个根实例里面去,又该怎么办?这个时候,就要祭出另一个强悍的方法了:

Vue.extend

这个方法允许我们自己随时创建 vue 实例,但是这个方法的学习门槛实在是高,先示列用法,下面是此方法的一个使用示列:

  1. // 创建组件逻辑
  2. let TagClass = Vue.extend({
  3. // 向界面渲染的dom方法。
  4. render (createElement) {
  5. return createElement(
  6. 'div',
  7. {
  8. style: {
  9. display: 'inline-block',
  10. margin: '3px',
  11. backgroundColor: '#d9d9d9'
  12. }
  13. },
  14. [
  15. this.text,
  16. createElement(
  17. 'button',
  18. {
  19. on: {
  20. 'click': this.onCloseClick
  21. }
  22. },
  23. '×'
  24. )
  25. ]
  26. );
  27. },
  28. methods: {
  29. onCloseClick () {
  30. this.$el.remove();
  31. // 将删除标签事件暴露除去
  32. this.$emit("delete", this.text);
  33. }
  34. },
  35. data () {
  36. return {
  37. text: "文本内容", // 什么这个tag标签使用的文字是传入的标签文字内容
  38. }
  39. }
  40. });
  41. // tag 就是想要的组件实例。
  42. let tag = new TagClass();

对单文件组件有所心得的朋友肯定还是对这段代码能够摸清楚一些的,但是这个方法并不是所有人都能一目了然。 其中有:

  1. 没有了 template 模板。
  2. 新增了 render 方法。

现在使用 vue-cli@3 创建的项目,都是只包含vue运行时功能的vue项目,所以,在代码里动态构建的组件,不能使用 template 了,那些组件模板页面其实在build的时候也会转换成render函数实现,所以在代码里动态构建组件只能使用 render 方法来进行页面的渲染。 而这个方法不难,难点可能就是render方法参数 createElement,这个参数它是一个可执行的方法,这个方法有3个参数:

  1. createElement(参数1, 参数2, 参数3);
  2. // 参数1: 可以是别的组件,可以是原生dom组件。
  3. // 参数2: 一个对象,这个对象里的数据将作为 "参数1" 的属性信息写入到组件或dom上。
  4. // 参数3: 数组或字符串或createElement方法返回的内容, 这个部分的数据将作为 "参数1" 的子内容出现。

createElement 参数2 详解

这个方法能够完成任何dom的创建以及嵌套都没问题,难点就在于如何去灵活的使用它,首要一点就是了解参数的具体含义, 参数2 作为dom属性配置,承载着许多的必要功能,下面是截取自vue官网的对象配置详细介绍:

  1. // 参数2 配置对象介绍
  2. {
  3. // 与 `v-bind:class` 的 API 相同,
  4. // 接受一个字符串、对象或字符串和对象组成的数组
  5. 'class': {
  6. foo: true,
  7. bar: false
  8. },
  9. // 与 `v-bind:style` 的 API 相同,
  10. // 接受一个字符串、对象,或对象组成的数组
  11. style: {
  12. color: 'red',
  13. fontSize: '14px'
  14. },
  15. // 普通的 HTML 特性
  16. attrs: {
  17. id: 'foo'
  18. },
  19. // 组件 prop
  20. props: {
  21. myProp: 'bar'
  22. },
  23. // DOM 属性
  24. domProps: {
  25. innerHTML: 'baz'
  26. },
  27. // 事件监听器在 `on` 属性内,
  28. // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
  29. // 需要在处理函数中手动检查 keyCode。
  30. on: {
  31. click: this.clickHandler
  32. },
  33. // 仅用于组件,用于监听原生事件,而不是组件内部使用
  34. // `vm.$emit` 触发的事件。
  35. nativeOn: {
  36. click: this.nativeClickHandler
  37. },
  38. // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  39. // 赋值,因为 Vue 已经自动为你进行了同步。
  40. directives: [
  41. {
  42. name: 'my-custom-directive',
  43. value: '2',
  44. expression: '1 + 1',
  45. arg: 'foo',
  46. modifiers: {
  47. bar: true
  48. }
  49. }
  50. ],
  51. // 作用域插槽的格式为
  52. // { name: props => VNode | Array<VNode> }
  53. scopedSlots: {
  54. default: props => createElement('span', props.text)
  55. },
  56. // 如果组件是其它组件的子组件,需为插槽指定名称
  57. slot: 'name-of-slot',
  58. // 其它特殊顶层属性
  59. key: 'myKey',
  60. ref: 'myRef',
  61. // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  62. // 那么 `$refs.myRef` 会变成一个数组。
  63. refInFor: true
  64. }

现在,有了对 extend 方法的了解和 createElement 方法的加持,还有什么办不到的。 要了解更多 Vue.extend 方法的详情,请参考:Vue官方API。这里还有一篇描述使用构造函数时如何传递参数的记录《构造函数式组件如何传递参数》。 要了解更多 createElement 方法的详情,请参考:Vue官方文档。

2、编写代码,实现效果。

现在可以修改 App.vue 文件,将其修改为先生成组件后显示到界面的逻辑,修改完成后代码如下:

  1. <template>
  2. <div>
  3. <h3>标签管理</h3>
  4. <!-- 放置tag的容器,从网络获取的tags都要放在这个div里面 -->
  5. <div ref="tagsContext">
  6. <div v-if="!tags">标签加载中...</div>
  7. <div v-else-if="tags && tags.length === 0">没有标签</div>
  8. </div>
  9. <button @click="addTag">添加标签</button>
  10. </div>
  11. </template>
  12. <script>
  13. import Util from './Util';
  14. export default {
  15. name: 'app',
  16. components: {},
  17. created() {
  18. // 从网络加载tag数据.
  19. Util.getTagsFromNet()
  20. .then(result => {
  21. if (result.code !== 200) {
  22. return Promise.reject(new Error(result.message));
  23. }
  24. // 数据加载成功,将数据赋值给 tags 即可。
  25. this.tags = result.data;
  26. // 拿到了数据,进行渲染:
  27. for (let i = 0; i < this.tags.length; i++) {
  28. this.initTagComponentAndShowTag(this.tags[i]);
  29. }
  30. })
  31. .catch(error => {
  32. window.alert(error.message);
  33. })
  34. },
  35. methods: {
  36. // 添加tag
  37. addTag() {
  38. let tagText = "标签" + this.tags.length;
  39. this.tags.push(tagText);
  40. this.initTagComponentAndShowTag(tagText);
  41. },
  42. // 有tag被删除
  43. onTagDelete(tag) {
  44. // 从数组中移除。
  45. this.tags.splice(this.tags.indexOf(tag), 1);
  46. },
  47. // 初始化一个tag组件并且显示到界面上。
  48. initTagComponentAndShowTag (tagText) {
  49. let tag = Util.newTag(tagText);
  50. // 添加删除监听
  51. tag.$on("delete", this.onTagDelete);
  52. // 挂载组件,即创建虚拟dom。
  53. tag.$mount();
  54. // 将组件添加到显示tag的div里面
  55. this.$refs.tagsContext.appendChild(tag.$el);
  56. }
  57. },
  58. data() {
  59. return {
  60. tags: undefined, // 初始化是没有 tag数据的,在页面打开后,通过网络请求获取标签。
  61. }
  62. }
  63. }
  64. </script>
  65. <style scoped></style>

再次运行程序,得到效果,与使用数据动态渲染完全一致:

这次就不再是先使用 Tag 组件了,为了避嫌,我甚至连 Tag 组件都没有引入。可以看到,在网络数据获取完成时, 通过循环构造组件并添加到了界面上,在 initTagComponentAndShowTag 方法中,使用Util.newTag 方法完成了组件的动态创建。由于组件的创建方法属于比较低层次级别方法,所以与界面逻辑剥离开。整个 Util.js 的代码如下:

  1. import Vue from 'vue';
  2. export default {
  3. newTag (tagText) {
  4. // 创建组件逻辑
  5. let TagClass = Vue.extend({
  6. // 向界面渲染的dom方法。
  7. render (createElement) {
  8. return createElement(
  9. 'div',
  10. {
  11. style: {
  12. display: 'inline-block',
  13. margin: '3px',
  14. backgroundColor: '#d9d9d9'
  15. }
  16. },
  17. [
  18. this.text,
  19. createElement(
  20. 'button',
  21. {
  22. on: {
  23. 'click': this.onCloseClick
  24. }
  25. },
  26. '×'
  27. )
  28. ]
  29. );
  30. },
  31. methods: {
  32. onCloseClick () {
  33. // 移除dom
  34. this.$el.remove();
  35. // 然后将删除标签事件暴露除去
  36. this.$emit("delete", this.text);
  37. }
  38. },
  39. data () {
  40. return {
  41. text: tagText, // 为这个tag标签使用的文字是传入的标签文字内容
  42. }
  43. }
  44. });
  45. return new TagClass();
  46. },
  47. // 模拟从网络获取tag数据。
  48. getTagsFromNet () {
  49. return new Promise((resolve) => {
  50. setTimeout(() => {
  51. let tags = [];
  52. for (let i = 0; i < 6; i++) {
  53. tags.push("标签" + i);
  54. }
  55. resolve({
  56. code: 200,
  57. data: tags,
  58. msg: 'success'
  59. });
  60. }, 2000);
  61. });
  62. }
  63. }

代码中注释比较详细,应该能了解其中含义。不论时那种方案最终都能实现效果,但是显然第一种方案代码更加优雅, 第二种代码显得比较凌乱(也许是我写的比较凌乱)。从开发复杂度上来讲,第一种也是远胜第二种。不过了解了第二种 方法,收益也是非常的肥厚!

三、归纳总结

这两种方法建议使用第一种去实现组件的添加或移除,第二种方案作为知识点要掌握。其中难点在于对Vue实例的本质了解、 对Vue渲染方法的了解,以及对Vue实例的使用熟悉程度。其中本例最重要的就是结合使用 Vue.extend 和 createElement 方法。

本文中涉及的项目资料你可以在百度网盘下载, 提取码:m3y1。注意本资料不包含第四小节(下一节)的内容。

四、组件动态切换

本小节为后来新增的内容,为更加完善本文标题所传达的知识点,本小节有必要新增。

有时候我们在使用组件的过程中,会出现这样一种需求:在同一个位置根据不同的某个状态值显示不同的组件。比如说常见的订单状态标志,存在着:未支付、已支付、配送中、派件中等等状态时,我们就要根据订单数据里某一个状态来动态显示这个状态标志了。对于这种简单标志效果展示性的需求,我们可以直接使用v-if语句来实现,在同一个位置根据不同的状态显示不同的标志。

1、使用 v-if

对于简单组件,或者直接是一个dom元素,我们使用v-if是可以的,不仅提升可读性后期维护性也会大大提升,扩展空间也很大。就像是这样:

  1. <div>
  2. <span class='un-pay' v-if='orderStatus === 1'>未支付</span>
  3. <span class='pay-ed' v-else-if='orderStatus === 2'>已支付</span>
  4. <span class='dliving' v-else-if='orderStatus === 3'>配送中</span>
  5. <span class='taking' v-else-if='orderStatus === 4'>已支付</span>
  6. <span class='other' v-else-if='orderStatus === 5'>等等其他更多状态</span>
  7. </div>

在从上述代码可以看到,在div下同一时间永远都只会显示一个标志。对于这种简单组件或dom,直接使用v-if会更加直观。但如果我们要根据某个标志来显示不同的复杂组件时,如果再写一堆v-if在这儿堆积起来,显得有点臃肿且难以理解了。

为了更优雅的实现这样的需求,下面介绍另一种解决方案。

2、内置组件 component

component其实算不上是一个组件,但它有提供了一个强大的功能,就好像它是一个‘床’,这个‘床’上要放一个什么组件,可以你自己来确定,甚至还可以在这一个‘床’上随时来回切换各个不同的自定义组件。

那么有这样一个好东西,必须要用起来。下面来看一个典型的场景:

这个示列展示了在同一个component‘床’提供的位置里动态的根据状态显示了不同的自定义组件。下面来看代码:

  1. <template>
  2. <!-- 用这个按钮来演示触发状态变化 -->
  3. <button v-on:click="onChangeClick">点击切换状态({{someStatus}})</button>
  4. <hr>
  5. <!-- 使用内置 component 组件,在这里面将显示我们希望的自己的组件 -->
  6. <component v-bind:is="currentComponent"></component>
  7. </template>
  8. <script>
  9. import mybutton from './mybutton';
  10. import myfile from './myfile';
  11. import myimg from './myimg';
  12. export default {
  13. // 这三个组件都要在本界面展示,所以都注册进来。
  14. components: {
  15. mybutton: mybutton,
  16. myfile: myfile,
  17. myimg: myimg
  18. },
  19. data() {
  20. return {
  21. // 定义当前展示的是哪一个组件,默认就展示 mybutton 好了
  22. currentComponent: 'mybutton',
  23. // 定义状态变量
  24. someStatus: 1, // 假设某状态
  25. // 1、显示mybutton组件
  26. // 2、显示myfile组件
  27. // 3、显示myimg组件
  28. }
  29. },
  30. methods: {
  31. // 点击切换状态按钮
  32. onChangeClick: function () {
  33. // 逻辑很简单,就是从1到3来回变化
  34. if (this.someStatus === 3) {
  35. this.someStatus = 1;
  36. } else {
  37. this.someStatus++;
  38. }
  39. }
  40. },
  41. watch: {
  42. // 监听状态变化
  43. someStatus(newvalue) {
  44. // 逻辑也很简单,根据不同的状态为 currentComponent 赋不同的值就可以了
  45. if (newvalue === 1) {
  46. this.currentComponent = 'mybutton';
  47. } else if (newvalue === 2) {
  48. this.currentComponent = 'myfile';
  49. } else if (newvalue === 3) {
  50. this.currentComponent = 'myimg';
  51. }
  52. }
  53. }
  54. }
  55. </script>

上述代码中可以看到,使用component内置组件,他可以根据你传递的 is变量来确定具体使用哪一个组件,我们这里演示使用的组件都不需要参数,而如果你的自定义组件又需要一些参数的时候,如何将这些参数通过component传递给自己的组件呢,其实很简单:

  1. <component v-bind:is='xxx' name='Jack' :age='age'></component>

简单的将参数写在component上就可以了,你在你自己的组件里怎么定义的,在这里也整么使用,上述代码写了2个参数,一个固定值 Jack, 和另一个变量值 age。如果你各个自定义组件又不同的参数又该怎么办?不用担心,你都可以全写在这个component上面的。

注意事项:每当切换 is 的值时,都会创建新的对应组件,在上列中来说,就是每次状态切换,为 currentComponent 赋新值时,component 里面显示的我们自己的组件每次都会新建一个,意味着如果你的自定义组件里有维护着状态逻辑的话,那么可能会因为这样的切换而丢失。而如何让它们切换的时候不新建,而是复用之前已经创建好的呢?只需要在 component 外面包裹一层 <keep-alive></keep-alive> 就可以啦。

转自https://www.microanswer.cn/blog/9

【Vue】动态添加组件的两种实现相关推荐

  1. vue动态设置文字布局方式_详解Vue动态添加模板的几种方法

    动态添加模板需要收集原始数据的页面,这个时候我们需要很多原始数据收集模板,下面给大家详解Vue 动态添加模板的几种方法,希望对你学习这方面知识有所帮助. 通常我们会在组件里的 template 属性定 ...

  2. java jframe添加面板_JFrame添加组件的两种方式

    对JFrame添加组件有两种方式:1) 用getContentPane()方法获得JFrame的内容面板,再对其加入组件:frame.getContentPane().add(childCompont ...

  3. vue 修改模板{{}}标签_详解Vue 动态添加模板的几种方法

    以下方法只适用于 Vue1.0 版本,推荐系数由高到低排列. 通常我们会在组件里的 template 属性定义模板,或者是在*.vue文件里的 template 标签里写模板.但是有时候会需要动态生成 ...

  4. android动态居中布局,Android动态添加布局的两种方式

    释放双眼,带上耳机,听听看~! 前言 大多数时候我们布局都是用xml来布局的,但有些时候也是会用到动态布局的,尤其是在一些大项目中,动态布局更是体现的淋漓尽致. 所以今天我们就来学习一些动态加添布局的 ...

  5. android动态改变布局,Android 动态添加布局的两种方式

    前言 大多数时候我们布局都是用xml来布局的,但有些时候也是会用到动态布局的,尤其是在一些大项目中,动态布局更是体现的淋漓尽致. 所以今天我们就来学习一些动态加添布局的两种方式,分别是 动态添加xml ...

  6. Qt Qml动态创建对象/组件的两种方式

    Qml动态创建对象有两种方式: a.使用Loader b.使用javaScript 1.Loader加载机制: 加载过程: 通过source,来加载qml文件. 通过sourceComponent,来 ...

  7. vue 在线编辑excel表格(原生和使用组件的两种方式)

    vue 在线编辑excel表格(原生和使用组件的两种方式) top表头和left表头格式的表格 <template><div><!-- 用组件写的编辑表格 -->& ...

  8. Vue渲染组件的两种方式

    <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8" ...

  9. vue动态添加路由之避坑指南

    你是否遇到了: addRouter后出现白屏 路由守卫出现死循环 踩了很多坑之后,我终于悟到了vue动态添加路由的正确打开方式: 为了设计权限,在前端我们通常采取两种方式 1.在用户登录时获取该用户权 ...

最新文章

  1. j2ee与mysql乱码过滤_mysql 在 j2ee中配置的乱码问题处理
  2. 一套代码小程序WebNative运行的探索02
  3. c++如何计算程序运行的时间
  4. nyoj-205--求余数--(大整数取模)
  5. python函数手册_python学习手册——内置函数(上)
  6. java判断类型_Java中类型判断的几种方式 - 码农小胖哥 - 博客园
  7. java 中 正则 正则表达式 匹配 url
  8. 有人说清华本科含金量最高,研究生的含金量就低了,这是为什么?
  9. 2D 转换之 scale
  10. 当前仍在编辑文章 - Java Excel处理 - 实操案例与方法手册
  11. STM32学习心得一:FlyMcu软件配置(STM32串口下载软件)
  12. 听说今年金三银四变成金一银二了。
  13. 腾讯云从良心云转变成“凉心云”,乱封禁服务器与域名怎么办?
  14. 计算机 90学时培训总结,90学时培训心得体会
  15. 如何使用.NET来打造一个QQ界面
  16. 《构建之法:现代软件工程》阅读提问
  17. 聚合收款码有限制吗?怎么办理?
  18. 10余年软件开发与系统架构经验,一起聊聊软件开发技术、系统架构技术、以及程序员最真实可行的职场打怪技能,代码之外的生存软技能。
  19. oracle之查询某一列是否含有英文字符
  20. pod spec lint 报错 fatal: Remote branch 0.1.3 not found in upstream origin

热门文章

  1. 重读《程序员修炼之道:通向务实的最高境界(第2版)》的一些重要感悟
  2. 王牌御史手游怎么用电脑玩 王牌御史电脑版怎么玩
  3. pygame动画精灵
  4. 【极简写作】Emoji符号表情大全
  5. 接口测试如何准备测试数据
  6. 手机计算机不支持此操作系统,教你手机操作一键重装电脑系统
  7. 如何在其他电脑不登陆谷歌前提下获取之前所收藏的书签
  8. 交通预测论文阅读与总结
  9. PDF怎么加密?11 款最好的 PDF 加密软件
  10. Virbox LM 软件加密流程PDF全攻略