程序员开发实例大全宝库

网站首页 > 编程文章 正文

第6章 Vue组件高级(vue组件用法)

zazugpt 2024-09-11 11:31:28 编程文章 16 ℃ 0 评论

上一章节我们讲解了组件的基础知识,本章我们将深入讲解组件的传值以及使用综合案例进行实践操作。

6.1 兄弟组件传值

在前面的内容中我们已经学习了父与子之间的传值,接下来我们来看如果组件之间的关系是平级的也就是兄弟组件之间该如何传值呢?兄弟传值有两种方式,一种是使用EventBus,另一种是借助父子传值实现兄弟传值,下面详细解释。

6.1.1 第一种方式Vue实例

我们先写两个兄弟组件,大家看案例代码:

例6-1 Demo0601.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>子组件向父组件传值</title>
  8. <!-- 在线引入vue.js,必须引入否则无法使用vue-->
  9. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  10. </head>
  11. <body>
  12. <div id="app">
  13. <h2>表白墙</h2>
  14. <likename></likename>
  15. <hr>
  16. <likewords></likewords>
  17. </div>
  18. <!-- 子组件模板 -->
  19. <template id="x1">
  20. <div>
  21. <label>请输入您喜欢人的名字:</label>
  22. <input type="text" v-model="username" />
  23. <input type="button" value="确定" />
  24. </div>
  25. </template>
  26. <template id="x2">
  27. <div>
  28. <h2>小丽</h2>
  29. <h4>你在我心中最美!</h4>
  30. </div>
  31. </template>
  32. </body>
  33. <script>
  34. // 确定名字组件
  35. let x1 = Vue.extend({
  36. template: "#x1",
  37. data() {
  38. return {
  39. username: ""
  40. }
  41. },
  42. });
  43. //确定名字后的表白组件
  44. let x2 = Vue.extend({
  45. template: "#x2"
  46. });
  47. var vm = new Vue({
  48. el: '#app',
  49. components: {
  50. 'likename': x1,
  51. 'likewords': x2
  52. }
  53. })
  54. </script>
  55. </html>

程序的运行结果如下:

组件2

组件1

图 6- 1 案例效果

我们现在要做的是在组件1中输入喜欢人的名字,点击确定按钮,把名字传给兄弟组件2中显示。

第一步:建立一个空的Vue实例,用来兄弟组件之间的数据传递:

let bus = new Vue();

第二步:在组件1中触发组件的事件:使用 $emit 触发,点击按钮的时候把名字传过去

// 确定名字组件

let x1 = Vue.extend({

template: "#x1",

data() {

return {

username: ""

}

},

methods: {

getName() {

bus.$emit("editName", this.username);//点击确定按钮把名字传过去

}

}

});

第三步:在组件2中接受,接受组件的事件: 使用 $on 接受,必须写在钩子函数中

//确定名字后的表白组件

let x2 = Vue.extend({

template: "#x2",

data() {

return {

name: "小丽",

}

},

methods: {

myfn() {

bus.$on("editName", (name) => {

this.name = name;

});

}

},

created() {

this.myfn();

}

});

也可以这样写,直接在钩子函数中去接受传过来的数据。

let x2 = Vue.extend({

template: "#x2",

data() {

return {

name: "小丽",

}

},

methods: {

},

created() {

bus.$on("editName", (name) => {

this.name = name;

});

}

});

完整代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>子组件向父组件传值</title>
  8. <!-- 在线引入vue.js,必须引入否则无法使用vue-->
  9. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  10. </head>
  11. <body>
  12. <div id="app">
  13. <h2>表白墙</h2>
  14. <likename></likename>
  15. <hr>
  16. <likewords></likewords>
  17. </div>
  18. <!-- 子组件模板 -->
  19. <template id="x1">
  20. <div>
  21. <label>请输入您喜欢人的名字:</label>
  22. <input type="text" v-model="username" />
  23. <input type="button" value="确定" @click="getName" />
  24. </div>
  25. </template>
  26. <template id="x2">
  27. <div>
  28. <input type="button" value="表白" />
  29. <h2>{{name}}</h2>
  30. <h4>你在我心中最美!</h4>
  31. </div>
  32. </template>
  33. </body>
  34. <script>
  35. let bus = new Vue();
  36. // 确定名字组件
  37. let x1 = Vue.extend({
  38. template: "#x1",
  39. data() {
  40. return {
  41. username: ""
  42. }
  43. },
  44. methods: {
  45. getName() {
  46. bus.$emit("editName", this.username)
  47. }
  48. }
  49. });
  50. //确定名字后的表白组件
  51. let x2 = Vue.extend({
  52. template: "#x2",
  53. data() {
  54. return {
  55. name: "小丽",
  56. }
  57. },
  58. methods: {
  59. myfn() {
  60. //editName是一个方法的名字,但是这个方法并不存在,on和emit两个方法名字一致即可
  61. bus.$on("editName", (name) => {
  62. this.name = name;
  63. });
  64. }
  65. },
  66. created() {
  67. this.myfn();
  68. }
  69. });
  70. let vm = new Vue({
  71. el: '#app',
  72. components: {
  73. 'likename': x1,
  74. 'likewords': x2
  75. }
  76. })
  77. </script>
  78. </html>

通过例6-1中,我们通过一个空的Vue实例,来做中间容器,组件1输入名字,点击按钮触发Vue实例中的$on监听的方法 ,注意,$on和$emit后面的方法名字要一致,有同学会问,但是这个方法并不存在,这个方法的作用是用来保证on和emit监听和发送双方的一致,所以该方法可以不存在但是必须保持一致。

6.1.2 第二种方式props和event

兄弟传值也可以借助于同一个父组件,把两个自组建的内容都传给父组件,通过父组件来实现数据交流。

接下来我们把上面的例子进行改造,实现使用props和event方法兄弟传值。

例6-2 Demo0602.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>兄弟组件传值</title>
  8. <!-- 在线引入vue.js,必须引入否则无法使用vue-->
  9. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  10. </head>
  11. <body>
  12. <div id="app">
  13. <h2>表白墙</h2>
  14. <likename @getx1="getx1"></likename>
  15. <hr>
  16. <likewords :myname="myname"></likewords>
  17. </div>
  18. <!-- 子组件模板 -->
  19. <template id="x1">
  20. <div>
  21. <label>请输入您喜欢人的名字:</label>
  22. <input type="text" v-model="username" />
  23. <input type="button" value="确定" @click="getName" />
  24. </div>
  25. </template>
  26. <template id="x2">
  27. <div>
  28. <input type="button" value="表白" />
  29. <h2>{{myname}}</h2>
  30. <h4>你在我心中最美!</h4>
  31. </div>
  32. </template>
  33. </body>
  34. <script>
  35. // 确定名字组件
  36. let x1 = Vue.extend({
  37. template: "#x1",
  38. data() {
  39. return {
  40. username: ""
  41. }
  42. },
  43. methods: {
  44. getName() {
  45. this.$emit("getx1", this.username)
  46. }
  47. }
  48. });
  49. //确定名字后的表白组件
  50. let x2 = Vue.extend({
  51. props: ['myname'],
  52. template: "#x2",
  53. methods: {
  54. },
  55. });
  56. let vm = new Vue({
  57. el: '#app',
  58. data: {
  59. myname: ""//用来接受组件1传过来的值,然后再传给组件2
  60. },
  61. components: {
  62. 'likename': x1,
  63. 'likewords': x2
  64. },
  65. methods: {
  66. //把这个方法传给组件1,在组件1中就可以触发这个方法
  67. getx1(name) {
  68. this.myname = name;
  69. }
  70. }
  71. })
  72. </script>
  73. </html>

程序的运行结果如下:

图 6- 2 使用父组件作为容器进行兄弟传值

通过例6-2中,我们先通过自定义事件,把组件1的值传给父组件,然后父组件使用props把获取的值传给组件2,这样也能够实现兄弟传值。

6.1.3 第三种方式使用parent、children和refs

除了使用Vue空实例,以及父子传值我们还可以使用Vue实例的三个属性,分别是parent,children和refs,接下来我们来看这三种属性如何使用:

This.$parent //获取父组件

This.$children //获取子组件

This.$refs.子组件ref的名字 //获取子组件

1、ref为子组件指定一个索引名称,通过索引来操作子组件;
2、this.$parent 可以直接访问该组件的父实例或组件;
3、父组件也可以通过this.$children 访问它所有的子组件; 需要注意 $children 并不保证顺序,也不是响应式的。

接下来我们来看案例中具体使用:

例6-3 Demo0603.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>兄弟传值</title>
  8. <!-- 在线引入vue.js,必须引入否则无法使用vue-->
  9. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  10. </head>
  11. <body>
  12. <div id="app">
  13. <h2>表白墙</h2>
  14. <likename></likename>
  15. <hr>
  16. <likewords ref="szj"></likewords>
  17. </div>
  18. <!-- 子组件模板 -->
  19. <template id="x1">
  20. <div>
  21. <label>请输入您喜欢人的名字:</label>
  22. <input type="text" v-model="username" />
  23. <input type="button" value="确定" @click="getName" />
  24. </div>
  25. </template>
  26. <template id="x2">
  27. <div>
  28. <h2>{{name}}</h2>
  29. <h4>你在我心中最美!</h4>
  30. </div>
  31. </template>
  32. </body>
  33. <script>
  34. // 确定名字组件
  35. let x1 = Vue.extend({
  36. template: "#x1",
  37. data() {
  38. return {
  39. username: ""
  40. }
  41. },
  42. methods: {
  43. getName() {
  44. this.$parent.getx1(this.username);
  45. }
  46. }
  47. });
  48. //确定名字后的表白组件
  49. let x2 = Vue.extend({
  50. template: "#x2",
  51. data() {
  52. return {
  53. name: '夏利'
  54. }
  55. },
  56. methods: {
  57. sonName(val) {
  58. this.name = val;
  59. },
  60. },
  61. });
  62. let vm = new Vue({
  63. el: '#app',
  64. components: {
  65. 'likename': x1,
  66. 'likewords': x2
  67. },
  68. data: {
  69. },
  70. methods: {
  71. //该方法调用组件2的方法给name赋值
  72. //但是该方法会在组件1中调用,这样就把组件1中username->sanme->组件2
  73. getx1(sname) {
  74. this.$refs.szj.sonName(sname);
  75. },
  76. }
  77. })
  78. </script>
  79. </html>

程序的运行结果与上面两个案例效果相同。

下面我们来看代码解析:

组件2使用ref加入索引:

<likewords ref="szj"></likewords>

父组件中提供宫组件1调用的方法:

methods: {

//该方法调用组件2的方法给name赋值

//但是该方法会在组件1中调用,这样就把组件1中username->sanme->组件2

getx1(sname) {

this.$refs.szj.sonName(sname);

},

}

组件1中点击确定按钮,则把组件1中的名字传值给父组件中的方法:

methods: {

getName() {

this.$parent.getx1(this.username);

}

}

例6-3中,我们也可以使用$children替换$refs,但是父组件有多个子组件,使用的时候要使用下标说明调用的是哪个子组件。

methods: {

//该方法调用组件2的方法给name赋值

//但是该方法会在组件1中调用,这样就把组件1中username->sanme->组件2

getx1(sname) {

console.log(this);

this.$children[1].sonName(sname);

},

}

6.2 跨级传值

在Vue中,有时需要实现通信的两个组件不是直接的父子组件,而是祖父和孙子,或者是跨越了更多层级的父子组件,这种时候就不可能由子组件一级一级的向上传递参数,特别是在组件层级比较深,嵌套比较多的情况下,需要传递的事件和属性较多,会导致代码很混乱。这时就需要用到 vue 提供的更高阶的方法:provide/inject。

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

provide/inject:简单来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量,不管组件层级有多深,在父组件生效的生命周期内,这个变量就一直有效。

先看使用步骤:

<script>

provide() {

// 它的作用就是将 **name** 这个变量提供给它的所有子组件。

name: 'Jack'

},

methods: {

changeName() {

this.name = 'Lily'

}

}

</script>

子组件:

inject: ['name'], // 注入了从父组件中提供的name变量

mounted () {

console.log(this.name); // Jack

}

接下来演示完整案例:

例6-4 Demo0604.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>兄弟传值</title>
  8. <!-- 在线引入vue.js,必须引入否则无法使用vue-->
  9. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  10. </head>
  11. <body>
  12. <div id="app">
  13. <h2>父组件</h2>
  14. <input type="text" v-model="username" />
  15. <hr>
  16. <ez></ez>
  17. </div>
  18. <!-- 子组件模板 -->
  19. <template id="ez">
  20. <div>
  21. <h4>儿子</h4>
  22. 儿子接受的值====><span>{{username}}</span>
  23. <sz></sz>
  24. </div>
  25. </template>
  26. <template id="sz">
  27. <div>
  28. 孙子接受的值===><span>{{username}}</span>
  29. </div>
  30. </template>
  31. </body>
  32. <script>
  33. //孙子组件
  34. let sz = Vue.extend({
  35. template: "#sz",
  36. inject: ['username']
  37. });
  38. // 儿子组件
  39. let ez = Vue.extend({
  40. template: "#ez",
  41. inject: ['username'],
  42. components: { sz }
  43. });
  44. let vm = new Vue({
  45. el: '#app',
  46. components: {
  47. ez
  48. },
  49. data: {
  50. username: "你好世界"
  51. },
  52. provide() {
  53. return {
  54. username: "你好世界"
  55. }
  56. },
  57. })
  58. </script>
  59. </html>

程序的运行结果如下:

图 6- 3 把父组件的值传给儿子和孙子


例6-4中,该案例中的值不是相应式的,也就是说如果父组件中的文本框内的数据发生变化,儿子和孙子接受的值不是不会跟着变化的,如果想要响应式变化我们可以使用如下代码:

父组件容器:

// 父组件 

<div>

<button @click="changeName">修改姓名</button>

<child-b />

</div>

<script>

......

data() {

return {

name: "Jack"

};

},

provide() {

return {

parentObj: this //提供祖先组件的实例

};

},

methods: {

changeName() {

this.name = 'Lily'

}

}

</script>

孙子容器:

<template>

  <div class="border2">

    <P>姓名:{{parentObj.name}}</P>

  </div>

</template>

<script>

  export default {

    inject:['parentObj']

</script>

例6-5 Demo0605.html

  1. p<!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>兄弟传值</title>
  8. <!-- 在线引入vue.js,必须引入否则无法使用vue-->
  9. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  10. </head>
  11. <body>
  12. <div id="app">
  13. <h2>父组件</h2>
  14. <input type="text" v-model="username" />
  15. <hr>
  16. <ez></ez>
  17. </div>
  18. <!-- 子组件模板 -->
  19. <template id="ez">
  20. <div>
  21. <h4>儿子</h4>
  22. 儿子接受的值====><span>{{parentObj.username}}</span>
  23. <sz></sz>
  24. </div>
  25. </template>
  26. <template id="sz">
  27. <div>
  28. 孙子接受的值===><span>{{parentObj.username}}</span>
  29. </div>
  30. </template>
  31. </body>
  32. <script>
  33. //孙子组件
  34. let sz = Vue.extend({
  35. template: "#sz",
  36. inject: ['parentObj']
  37. });
  38. // 儿子组件
  39. let ez = Vue.extend({
  40. template: "#ez",
  41. inject: ['parentObj'],
  42. components: { sz }
  43. });
  44. let vm = new Vue({
  45. el: '#app',
  46. components: {
  47. ez
  48. },
  49. data: {
  50. username: "你好世界"
  51. },
  52. provide() {
  53. return {
  54. parentObj: this
  55. }
  56. },
  57. })
  58. </script>
  59. </html>

程序的运行结果如下:

图 6- 4 子孙组件的值会跟着父组件的变化而变化


以上是使用provide把父组件实例共享给子孙,子孙通过inject注入,就可以使用父组件的值。

6.3 综合案例

有关组件的知识,通过第五章和第六章我们已经学习掌握,本节同通过实际操作来掌握有关父子传值的知识。

6.3.1 效果和需求

效果

图 6- 5 综合案例效果图

该案例实现了评论的增删改查,以及模糊查询和分页查询;

需求

  • 实现所有评论的分页查询
  • 实现模糊查询
  • 实现添加功能
  • 实现修改功能
  • 实现删除功能

该案例综合使用动态组件、axios、以及组件传值,希望大家通过练习该案例能够掌握vue组件的基础知识。

6.3.2 实际步骤

第一步:建立数据源

建立data.json,因为我们的数据最初是从json文件里面拿出来的,拿出来后存储到本地中

  1. {
  2. "comments": [
  3. {
  4. "commentId": 1,
  5. "content": "平凡的世界",
  6. "comDate": "1999-09-09"
  7. },
  8. {
  9. "commentId": 2,
  10. "content": "白天与黑夜",
  11. "comDate": "1999-09-09"
  12. },
  13. {
  14. "commentId": 3,
  15. "content": "这个很不错额",
  16. "comDate": "1999-09-09"
  17. },
  18. {
  19. "commentId": 4,
  20. "content": "平凡的世界",
  21. "comDate": "1999-09-09"
  22. },
  23. {
  24. "commentId": 5,
  25. "content": "平凡的世界",
  26. "comDate": "1999-09-09"
  27. }
  28. ]
  29. }

第二步:查询建立页面

在页面上引入vue.js,bootstrap.csc、axios.js以及moment.js

其中moment.js 是用来时间格式化的。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>评论案例</title>
  8. <!-- 在线引入vue.js,必须引入否则无法使用vue-->
  9. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  10. <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  11. <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  12. <script src="http://cdn.staticfile.org/moment.js/2.24.0/moment.min.js"></script>
  13. </head>

第三步:实现查询

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>评论案例</title>
  8. <!-- 在线引入vue.js,必须引入否则无法使用vue-->
  9. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  10. <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  11. <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  12. </head>
  13. <body>
  14. <div id="app">
  15. <table class="table">
  16. <tr>
  17. <th>编号</th>
  18. <th>内容</th>
  19. <th>日期</th>
  20. <th>操作</th>
  21. </tr>
  22. <tr v-for="(item,index) in coms" :key="index">
  23. <td>
  24. {{item.commentId}}
  25. </td>
  26. <td>
  27. {{item.content}}
  28. </td>
  29. <td>
  30. {{item.comDate}}
  31. </td>
  32. <td>
  33. <button type="button" class="btn btn-primary">修改</button>
  34. <button type="button" class="btn btn-danger">删除</button>
  35. </td>
  36. </tr>
  37. </table>
  38. </div>
  39. </body>
  40. <script>
  41. let vm = new Vue({
  42. el: '#app',
  43. data: {
  44. coms: [],
  45. },
  46. methods: {
  47. //获取所有的评论
  48. //先从本地获取存储中获取评论,如果没有则去data.json中获取
  49. allComs() {
  50. let comments = JSON.parse(localStorage.getItem("comments") || "[]");
  51. this.coms = comments;
  52. if (comments.length === 0) {
  53. axios.get("./data.json").then(resp => {
  54. console.log(resp.data);
  55. this.coms = resp.data.comments;
  56. localStorage.setItem("comments", JSON.stringify(this.coms));
  57. }, resp => {
  58. console.log("error");
  59. });
  60. }
  61. },
  62. },
  63. created() {
  64. this.allComs();
  65. },
  66. })
  67. </script>
  68. </html>

程序运行结果如下:

图 6- 6 查询所有评论

第四步:实现添加

添加的时候实现评论id自增,通过计算属性查询出数组的最大值,在最大值的基础上加1,在添加模板中,点击添加按钮,把最新添加的评论内容添加到数组中和本地中。点击添加按钮的时候使用$parent调用父组件的方法。

写添加模板:

  1. <!-- 添加模板 -->
  2. <script type="text/x-template" id="add">
  3. <div class="add">
  4. <form>
  5. <div class="form-group">
  6. <label for="inputfile">评论内容</label>
  7. <textarea class="form-control" rows="3" v-model="content"></textarea>
  8. </div>

  9. <button type="reset" class="btn btn-danger">重置</button>
  10. <button type="button" class="btn btn-default" @click="addItem">提交</button>
  11. </form>
  12. </div>
  13. </script>

</head>

添加的样式:

  1. <style>
  2. .add {
  3. width: 700px;
  4. height: auto;
  5. margin: 10px auto;
  6. }
  7. </style>

添加的时候,我们的编号是通过查找最大的编号,然后在最大的编号的基础上加1:

  1. computed: {
  2. num() {
  3. let index = 1;
  4. this.coms.forEach(item => {
  5. let id = parseInt(item.commentId);
  6. if (id > index) {
  7. index = id;
  8. }
  9. });
  10. return index;
  11. }
  12. }

添加模板中的方法:

  1. let addCom = Vue.extend({
  2. template: '#add',
  3. data() {
  4. return {
  5. content: ""
  6. }
  7. },
  8. methods: {
  9. addItem() {
  10. let obj = { content: this.content }
  11. this.$parent.addCom(obj);
  12. }
  13. }
  14. });

父组件中的添加方法:

  1. //添加方法
  2. addCom(item) {
  3. item.commentId = this.num + 1;
  4. let date = new Date();
  5. let commentDate = moment(date).format('YYYY-MM-DD')
  6. item.comDate = commentDate;
  7. this.coms.push(item);
  8. localStorage.setItem("comments", JSON.stringify(this.coms));
  9. }

程序运行如下:

图 6- 7 实现添加评论

来看本地存储中也添加了最新的数据:

图 6- 8 本地存储添加最新数据

第五步:实现修改业务;

点击修改按钮的时候,我们希望在添加的位置,显示修改的模板,这个时候我们可以使用动态组件component的is属性,默认显示add组件。

  1. <component :is="addOrupdate"></component>
  2. .
  3. .
  4. .
  5. data: {
  6. coms: [],
  7. addOrupdate: "add"
  8. },

设置修改的模板,设置完毕模板后要在父组件中注册:

  1. <!-- 修改模板 -->
  2. <script type="text/x-template" id="update">
  3. <div class="add">
  4. <form>
  5. <div class="form-group">
  6. <label for="inputfile">评论id</label>
  7. <input class="form-control" type="text" v-model="id" readonly/>
  8. </div>
  9. <div class="form-group">
  10. <label for="inputfile">评论内容</label>
  11. <textarea class="form-control" rows="3" v-model="content"></textarea>
  12. </div>

  13. <button type="reset" class="btn btn-danger">重置</button>
  14. <button type="button" class="btn btn-default" @click="upfn">提交</button>
  15. </form>
  16. </div>
  17. </script>

点击每一行的修改按钮的时候要做以下事情:

  • 表格上方改为修改模板
  • 根据要修改的id找到这个数据
  • 把该对象原来的数据传到修改组件中
  1. //点击修改按钮去修改
  2. //1、在添加的位置显示修改模板
  3. //2、要修改的信息进行回显
  4. toUpdate(id) {
  5. this.addOrupdate = "update";
  6. //根据id找到信息进行回显
  7. let obj = {};
  8. this.coms.some(c => {
  9. if (c.commentId === id) {
  10. obj = c;
  11. return true;
  12. }
  13. });
  14. this.upobj = obj;
  15. },

现在把upobj传给修改组件

  1. <keep-alive>
  2. <component :is="addOrupdate" :upobj="upobj"></component>
  3. </keep-alive>
  4. //修改模板去接受
  5. let updateCom = Vue.extend({
  6. template: "#update",
  7. props: ['upobj'],
  8. });

由于使用props是单向向下的,只能父组件传给子组件,但是子组件不能修改,所以我们要把props中的数据给data中的id和content,同时要监视props数据的变化,一旦变化随时传值给data:

  1. let updateCom = Vue.extend({
  2. template: "#update",
  3. props: ['upobj'],
  4. data() {
  5. return {
  6. id: this.upobj.commentId,
  7. content: this.upobj.content
  8. }
  9. },
  10. watch: {
  11. //监听父组件传过来的值,只要传过来的值发生变化,子组件的data中的数据也会跟着变化
  12. upobj(val) {
  13. this.id = val.commentId;
  14. this.content = val.content;
  15. }
  16. }
  17. });

程序运行如下:

成功回显数据

修改该条数据

图 6- 9 点击修改按钮数据回显成功

点击提交实现修改:

点击提交,根据最新的数据生成一个对象,然后在子组件中调用父组件的修改方法,实现修改业务:

  1. //父组件中的修改的方法
  2. update(obj) {
  3. console.log(obj);
  4. obj.comDate = moment(new Date()).format("YYYY-MM-DD");
  5. this.coms.some(c => {
  6. if (c.commentId === obj.commentId) {
  7. c.content = obj.content;
  8. c.comDate = obj.comDate;
  9. return true;
  10. }
  11. });
  12. //本地存储做出修改
  13. localStorage.setItem("comments", JSON.stringify(this.coms));
  14. //修改页面恢复为添加
  15. this.addOrupdate = "add";
  16. }
  17. //修改子组件的方法
  18. methods: {
  19. //修改的方法
  20. //把修改的数据覆盖掉之前的数据
  21. //在子组件中调用父组件的修改方法
  22. upfn() {
  23. let obj = {
  24. commentId: this.id,
  25. content: this.content
  26. }
  27. this.$parent.update(obj);
  28. }
  29. },

第六步:实现删除

点击删除按钮的时候,实现从data中的coms中删除,同时跟localStorage保持一致:

  1. //执行删除功能
  2. //根据id查找,找到后删除
  3. del(id) {
  4. this.coms.some((c, index) => {
  5. if (c.commentId === id) {
  6. this.coms.splice(index, 1);
  7. return true;
  8. }
  9. });
  10. localStorage.setItem("comments", JSON.stringify(this.coms));
  11. }

第七步:实现搜索

定义搜索的模板,然后在父组件中注册:

  1. <!-- 搜索模板 -->
  2. <script type="text/x-template" id="search">
  3. <div class="input-group col-md-3" style="margin-top:0px;positive:relative">
  4. <input type="text" class="form-control" placeholder="请输入搜索内容" v-model="keywords"/>
  5. <span class="input-group-btn">
  6. <button class="btn btn-info btn-search" @click="searfn">查找</button>
  7. </span>
  8. </div>
  9. </script>

点击查找按钮,进行调用父组件中的查找方法:

  1. //搜索
  2. let searchCom = Vue.extend({
  3. template: "#search",
  4. data() {
  5. return {
  6. keywords: ""
  7. }
  8. },
  9. methods: {
  10. searfn() {
  11. this.$parent.search(this.keywords);
  12. }
  13. }
  14. });
  15. //父组件的搜索功能,scoms是在父组件中定义的专门用来存储搜索后结果的数组,同时table遍历改成使用scoms
  16. search(kw) {
  17. this.scoms = this.coms.filter(item => {
  18. return item.content.includes(kw);
  19. });
  20. }
  21. //最开是的查询所有变成如下代码
  22. allComs() {
  23. let comments = JSON.parse(localStorage.getItem("comments") || "[]");
  24. this.coms = comments;
  25. if (comments.length === 0) {
  26. axios.get("./data.json").then(resp => {
  27. console.log(resp.data);
  28. this.coms = resp.data.comments;
  29. localStorage.setItem("comments", JSON.stringify(this.coms));
  30. }, resp => {
  31. console.log("error");
  32. });
  33. }
  34. this.scoms = this.coms;//把原来的数组给搜索的结果
  35. },

程序运行结果如下:

图 6- 10 模糊查询的功能

第八步:实现分页

定义分页模板:

  1. <!-- 分页组件 -->
  2. <script type="text/x-template" id="page">
  3. <div class="page">
  4. <ul class="pagination">
  5. <li><a href="#">«</a></li>
  6. <li><a href="#">1</a></li>
  7. <li><a href="#">2</a></li>
  8. <li><a href="#">3</a></li>
  9. <li><a href="#">4</a></li>
  10. <li><a href="#">5</a></li>
  11. <li><a href="#">»</a></li>
  12. </ul>
  13. </div>

思考:分页组件中的总页数是从父组件中传过来的;

父组件定义计算属性:

  1. //计算总页数
  2. pagenum() {
  3. return Math.ceil(this.scoms.length / this.pageSize);
  4. }

把这个属性传值给分页组件:

  1. <page :pagenum="pagenum"></page>
  2. let page = Vue.extend({
  3. template: "#page",
  4. props: ['pagenum'],
  5. data() {
  6. return {
  7. num: this.pagenum
  8. }
  9. },
  10. watch: {
  11. pagenum(val) {
  12. this.num = val;
  13. }
  14. }
  15. });

模板中循环页码:


  1. <!-- 分页组件 -->
  2. <script type="text/x-template" id="page">
  3. <div class="page">
  4. <ul class="pagination">
  5. <li><a href="#">«</a></li>
  6. <li v-for="n in num"><a href="#">{{n}}</a></li>
  7. <li><a href="#">»</a></li>
  8. </ul>
  9. </div>
  10. </script>

接下来实现默认显示第一页的内容:

在这我们使用的是数组的slice方法可以截取某一个段,所以我们需要计算开始和结束位置:

  1. <tr v-for="(item,index) in scoms.slice(start,end)" :key="index">
  2. //使用计算属性计算截取的开始位置
  3. start() {
  4. return this.pageIndex * this.pageSize - this.pageSize;
  5. },
  6. //截取的结束位置
  7. end() {
  8. return this.pageIndex * this.pageSize;
  9. }

模板变化:

  1. <!-- 分页组件 -->
  2. <script type="text/x-template" id="page">
  3. <div class="page">
  4. <ul class="pagination">
  5. <li @click="prev"><a href="#">«</a></li>
  6. <li v-for="n in num" @click="goto(n)"><a href="#">{{n}}</a></li>
  7. <li @click="next"><a href="#">»</a></li>
  8. </ul>
  9. </div>
  10. </script>

实现点击页码显示相应的内容:

  1. methods: {
  2. //分页组件的点击页码事件
  3. goto(n) {
  4. this.$parent.changePageIndex(n);
  5. }
  6. }
  7. //父组件的方法
  8. //执行显示相应的页码的内容,本质就是改变页码
  9. changePageIndex(val) {
  10. this.pageIndex = val;
  11. }

实现上一页下一页的功能:

  1. methods: {
  2. //分页组件的点击页码事件
  3. goto(n) {
  4. this.$parent.changePageIndex(n);
  5. },
  6. //子组件的上一页
  7. prev() {
  8. this.$parent.goUp();
  9. },
  10. //自组建的下一页
  11. next() {
  12. this.$parent.goDown();
  13. }
  14. }
  15. //父组件的上一页下一页
  16. //上一页
  17. goUp() {
  18. if (this.pageIndex === 1) {
  19. alert("已经是第一页了");
  20. } else {
  21. this.pageIndex = this.pageIndex - 1;
  22. }
  23. },
  24. //下一页
  25. goDown() {
  26. if (this.pageIndex === this.pagenum) {
  27. alert("已经是最后一页了");
  28. } else {
  29. this.pageIndex = this.pageIndex + 1;
  30. }
  31. }

6.3.3 完整案例

Data.json

  1. {
  2. "comments": [
  3. {
  4. "commentId": 1,
  5. "content": "平凡的世界",
  6. "comDate": "1999-09-09"
  7. },
  8. {
  9. "commentId": 2,
  10. "content": "白天与黑夜",
  11. "comDate": "1999-09-09"
  12. },
  13. {
  14. "commentId": 3,
  15. "content": "这个很不错额",
  16. "comDate": "1999-09-09"
  17. },
  18. {
  19. "commentId": 4,
  20. "content": "平凡的世界",
  21. "comDate": "1999-09-09"
  22. },
  23. {
  24. "commentId": 5,
  25. "content": "人间失格",
  26. "comDate": "1999-09-09"
  27. },
  28. {
  29. "commentId": 6,
  30. "content": "yyh",
  31. "comDate": "1999-09-09"
  32. },
  33. {
  34. "commentId": 7,
  35. "content": "yyh当当",
  36. "comDate": "1999-09-09"
  37. }
  38. ]
  39. }

综合案例.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>评论案例</title>
  8. <!-- 在线引入vue.js,必须引入否则无法使用vue-->
  9. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  10. <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  11. <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  12. <script src="http://cdn.staticfile.org/moment.js/2.24.0/moment.min.js"></script>
  13. <style>
  14. .add {
  15. width: 700px;
  16. height: auto;
  17. margin: 10px auto;
  18. }
  19. .search {
  20. width: 600px;
  21. height: auto;
  22. margin: auto auto;
  23. }
  24. .page {
  25. width: 300px;
  26. margin: 0px auto;
  27. }
  28. </style>
  29. </head>
  30. <body>
  31. <div id="app">
  32. <keep-alive>
  33. <component :is="addOrupdate" :upobj="upobj"></component>
  34. </keep-alive>
  35. <search></search>
  36. <br>
  37. <table class="table">
  38. <tr>
  39. <th>编号</th>
  40. <th>内容</th>
  41. <th>日期</th>
  42. <th>操作</th>
  43. </tr>
  44. <tr v-for="(item,index) in scoms.slice(start,end)" :key="index">
  45. <td>
  46. {{item.commentId}}
  47. </td>
  48. <td>
  49. {{item.content}}
  50. </td>
  51. <td>
  52. {{item.comDate}}
  53. </td>
  54. <td>
  55. <button type="button" class="btn btn-primary" @click="toUpdate(item.commentId)">修改</button>
  56. <button type="button" class="btn btn-danger" @click="del(item.commentId)">删除</button>
  57. </td>
  58. </tr>
  59. </table>
  60. <page :pagenum="pagenum"></page>
  61. </div>
  62. </body>
  63. <!-- 添加模板 -->
  64. <script type="text/x-template" id="add">
  65. <div class="add">
  66. <form>
  67. <div class="form-group">
  68. <label for="inputfile">评论内容</label>
  69. <textarea class="form-control" rows="3" v-model="content"></textarea>
  70. </div>

  71. <button type="reset" class="btn btn-danger">重置</button>
  72. <button type="button" class="btn btn-default" @click="addItem">提交</button>
  73. </form>
  74. </div>
  75. </script>
  76. <!-- 修改模板 -->
  77. <script type="text/x-template" id="update">
  78. <div class="add">
  79. <form>
  80. <div class="form-group">
  81. <label for="inputfile">评论id</label>
  82. <input class="form-control" type="text" v-model="id" readonly/>
  83. </div>
  84. <div class="form-group">
  85. <label for="inputfile">评论内容</label>
  86. <textarea class="form-control" rows="3" v-model="content"></textarea>
  87. </div>

  88. <button type="reset" class="btn btn-danger">重置</button>
  89. <button type="button" class="btn btn-default" @click="upfn">提交</button>
  90. </form>
  91. </div>
  92. </script>
  93. <!-- 搜索模板 -->
  94. <script type="text/x-template" id="search">
  95. <div class="input-group col-md-3" style="margin-top:0px;positive:relative">
  96. <input type="text" class="form-control" placeholder="请输入搜索内容" v-model="keywords"/>
  97. <span class="input-group-btn">
  98. <button class="btn btn-info btn-search" @click="searfn">查找</button>
  99. </span>
  100. </div>
  101. </script>
  102. <!-- 分页组件 -->
  103. <script type="text/x-template" id="page">
  104. <div class="page">
  105. <ul class="pagination">
  106. <li @click="prev"><a href="#">«</a></li>
  107. <li v-for="n in num" @click="goto(n)"><a href="#">{{n}}</a></li>
  108. <li @click="next"><a href="#">»</a></li>
  109. </ul>
  110. </div>
  111. </script>
  112. <script>
  113. //添加
  114. let addCom = Vue.extend({
  115. template: '#add',
  116. data() {
  117. return {
  118. content: ""
  119. }
  120. },
  121. methods: {
  122. addItem() {
  123. let obj = { content: this.content }
  124. this.$parent.addCom(obj);
  125. }
  126. },
  127. });
  128. //修改
  129. let updateCom = Vue.extend({
  130. template: "#update",
  131. props: ['upobj'],
  132. data() {
  133. return {
  134. id: this.upobj.commentId,
  135. content: this.upobj.content
  136. }
  137. },
  138. methods: {
  139. //修改的方法
  140. //把修改的数据覆盖掉之前的数据
  141. //在子组件中调用父组件的修改方法
  142. upfn() {
  143. let obj = {
  144. commentId: this.id,
  145. content: this.content
  146. }
  147. this.$parent.update(obj);
  148. }
  149. },
  150. watch: {
  151. //监听父组件传过来的值,只要传过来的值发生变化,子组件的data中的数据也会跟着变化
  152. upobj(val) {
  153. this.id = val.commentId;
  154. this.content = val.content;
  155. }
  156. }
  157. });
  158. //搜索
  159. let searchCom = Vue.extend({
  160. template: "#search",
  161. data() {
  162. return {
  163. keywords: ""
  164. }
  165. },
  166. methods: {
  167. searfn() {
  168. this.$parent.search(this.keywords);
  169. }
  170. }
  171. });
  172. //分页
  173. let page = Vue.extend({
  174. template: "#page",
  175. props: ['pagenum'],
  176. data() {
  177. return {
  178. num: this.pagenum
  179. }
  180. },
  181. watch: {
  182. pagenum(val) {
  183. this.num = val;
  184. }
  185. },
  186. methods: {
  187. //分页组件的点击页码事件
  188. goto(n) {
  189. this.$parent.changePageIndex(n);
  190. },
  191. //子组件的上一页
  192. prev() {
  193. this.$parent.goUp();
  194. },
  195. //自组建的下一页
  196. next() {
  197. this.$parent.goDown();
  198. }
  199. }
  200. });
  201. let vm = new Vue({
  202. el: '#app',
  203. data: {
  204. coms: [],
  205. addOrupdate: "add",
  206. upobj: {},//保存被修改的对象
  207. scoms: [],
  208. pageSize: 3,//每页显示的条数
  209. pageIndex: 1,//当前页码
  210. },
  211. components: {
  212. "add": addCom,
  213. "update": updateCom,
  214. "search": searchCom,
  215. page
  216. },
  217. methods: {
  218. //获取所有的评论
  219. //先从本地获取存储中获取评论,如果没有则去data.json中获取
  220. allComs() {
  221. let comments = JSON.parse(localStorage.getItem("comments") || "[]");
  222. this.coms = comments;
  223. if (comments.length === 0) {
  224. axios.get("./data.json").then(resp => {
  225. console.log(resp.data);
  226. this.coms = resp.data.comments;
  227. localStorage.setItem("comments", JSON.stringify(this.coms));
  228. }, resp => {
  229. console.log("error");
  230. });
  231. }
  232. this.scoms = this.coms;
  233. },
  234. //添加方法
  235. addCom(item) {
  236. item.commentId = this.num + 1;
  237. let date = new Date();
  238. let commentDate = moment(date).format('YYYY-MM-DD')
  239. item.comDate = commentDate;
  240. this.coms.push(item);
  241. this.scoms = this.coms;
  242. localStorage.setItem("comments", JSON.stringify(this.coms));
  243. },
  244. //点击修改按钮去修改
  245. //1、在添加的位置显示修改模板
  246. //2、要修改的信息进行回显
  247. toUpdate(id) {
  248. this.addOrupdate = "update";
  249. //根据id找到信息进行回显
  250. let obj = {};
  251. this.coms.some(c => {
  252. if (c.commentId === id) {
  253. obj = c;
  254. return true;
  255. }
  256. });
  257. this.upobj = obj;
  258. },
  259. //父组件中的修改的方法
  260. update(obj) {
  261. console.log(obj);
  262. obj.comDate = moment(new Date()).format("YYYY-MM-DD");
  263. this.coms.some(c => {
  264. if (c.commentId === obj.commentId) {
  265. c.content = obj.content;
  266. c.comDate = obj.comDate;
  267. return true;
  268. }
  269. });
  270. this.scoms = this.coms;
  271. //本地存储做出修改
  272. localStorage.setItem("comments", JSON.stringify(this.coms));
  273. //修改页面恢复为添加
  274. this.addOrupdate = "add";
  275. },
  276. //执行删除功能
  277. //根据id查找,找到后删除
  278. del(id) {
  279. this.coms.some((c, index) => {
  280. if (c.commentId === id) {
  281. this.coms.splice(index, 1);
  282. return true;
  283. }
  284. });
  285. this.scoms = this.coms;
  286. localStorage.setItem("comments", JSON.stringify(this.coms));
  287. },
  288. //父组件的搜索功能
  289. search(kw) {
  290. this.scoms = this.coms.filter(item => {
  291. return item.content.includes(kw);
  292. });
  293. },
  294. //执行显示相应的页码的内容,本质就是改变页码
  295. changePageIndex(val) {
  296. this.pageIndex = val;
  297. },
  298. //上一页
  299. goUp() {
  300. if (this.pageIndex === 1) {
  301. alert("已经是第一页了");
  302. } else {
  303. this.pageIndex = this.pageIndex - 1;
  304. }
  305. },
  306. //下一页
  307. goDown() {
  308. if (this.pageIndex === this.pagenum) {
  309. alert("已经是最后一页了");
  310. } else {
  311. this.pageIndex = this.pageIndex + 1;
  312. }
  313. }
  314. },
  315. created() {
  316. this.allComs();
  317. },
  318. computed: {
  319. num() {
  320. let index = 1;
  321. this.coms.forEach(item => {
  322. let id = parseInt(item.commentId);
  323. if (id > index) {
  324. index = id;
  325. }
  326. });
  327. return index;
  328. },
  329. //计算总页数
  330. pagenum() {
  331. return Math.ceil(this.scoms.length / this.pageSize);
  332. },
  333. //截取的开始位置
  334. start() {
  335. return this.pageIndex * this.pageSize - this.pageSize;
  336. },
  337. //截取的结束位置
  338. end() {
  339. return this.pageIndex * this.pageSize;
  340. }
  341. }
  342. })
  343. </script>
  344. </html>


6.4 本章小结

  • 兄弟组件传值有三种方式:空的Vue实例、props和event结合以及parent/children/refs;
  • 跨级组件传值使用provider在父组件中申明,使用inject在孙子组件中注入;
  • 综合案例掌握不同组件之间的数据通讯,以及axios的初步使用以及动态组件的使用。

6.5 理论试题与实践练习

1.思考题

1.1 请简述如何使用空的Vue实例传值;

1.2 请简述如何使用props和event实现兄弟组件传值;

1.3 请简述parent/children/refs的使用;

1.4 请简述跨级provicer/inject的使用步骤;

1.5 请简述keep-alive的使用和以及钩子函数;

1.6 请简述动态组件如何传值;

2.编程题

实现图书管理的增删改查以及分页和模糊查询

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表