-->">
当前位置:   article > 正文

Vue-TodoList案例剖析_

目录

一、TodoList案例介绍:

二、案例全部完整代码:

2.1 Mylist.vue

2.2 Myitem.vue

2.3 MyHeader.vue

2.4 MyFooter.vue

2.5 App.vue

三、配置项props:

四、部分功能分析:

4.1 list列表显示

4.2 添加功能


一、TodoList案例介绍:

        TodoList案例有点类似于我们平时使用的“备忘录”的功能。在这次案例中主要是为了体验“组件化编程的流程”,组件化编程的流程有以下三个:

  1. 拆分静态组件;组件要按照功能点拆分,命名不要与html元素冲突

  2. 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用;

  3. 实现交互:从绑定事件开始

其中实现组件化又分为了两种:

  • 一个组件在用:放在组件自身即可.

  • 一些组件在用:放在他们共同的父组件上(状态提升)

项目拆分组件及功能如图所示: 

 二、案例全部完整代码:

        下面是项目实现的完整代码,可以参考学习

2.1 Mylist.vue

        Mylist.vue是列表显示组件,它有一个子组件item,也是每个“要做的事儿~”

  1. <template>
  2. <ul class="todo-main">
  3. <Item v-for="todoobj in todos"
  4. :key="todoobj.id"
  5. :todo="todoobj"
  6. :checkTodo='checkTodo'
  7. :deleteTodo='deleteTodo'/>
  8. </ul>
  9. </template>
  10. <script>
  11. import Item from "./Myitem.vue";
  12. export default {
  13. name:"List",
  14. components:{Item},
  15. props:[
  16. 'todos',
  17. 'checkTodo',
  18. 'deleteTodo'
  19. ]
  20. }
  21. </script>
  22. <style scoped>
  23. .todo-main {
  24. margin-left: 0px;
  25. border: 1px solid #ddd;
  26. border-radius: 2px;
  27. padding: 0px;
  28. }
  29. .todo-empty {
  30. height: 40px;
  31. line-height: 40px;
  32. border: 1px solid #ddd;
  33. border-radius: 2px;
  34. padding-left: 5px;
  35. margin-top: 10px;
  36. }
  37. </style>

2.2 Myitem.vue

        Myitem组件是每一个具体要做的事情`

  1. <template>
  2. <li>
  3. <label>
  4. <!-- checked = true表示勾选 :checked="todo.done动态绑定是否勾选-->
  5. <!-- 可以綁定@change事件或者是@click事件 -->
  6. <input type="checkbox" :checked="todo.done" @change="handlecheck(todo.id)" />
  7. <!-- 以下代碼也可以完成功能,但是不建議使用,只是转了v-model空子 -->
  8. <!-- <input type="checkbox" v-model="todo.done"/> -->
  9. <!-- 父传子,拿到title数据 -->
  10. <span>{{ todo.title }}</span>
  11. </label>
  12. <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
  13. </li>
  14. </template>
  15. <script>
  16. export default {
  17. name: "Item",
  18. data() {
  19. return {
  20. }
  21. },
  22. // 声明接收todo对象(父传子)
  23. props: ["todo", "checkTodo","deleteTodo"],
  24. methods: {
  25. handlecheck(id) {
  26. console.log(id)
  27. this.checkTodo(id);
  28. },
  29. handleDelete(id) {
  30. if (confirm("确定删除吗?")) {
  31. this.deleteTodo(id);
  32. }
  33. }
  34. },
  35. }
  36. </script>
  37. <style scoped>
  38. li {
  39. list-style: none;
  40. height: 36px;
  41. line-height: 36px;
  42. padding: 0 5px;
  43. border-bottom: 1px solid #ddd;
  44. }
  45. li label {
  46. float: left;
  47. cursor: pointer;
  48. }
  49. li label li input {
  50. vertical-align: middle;
  51. margin-right: 6px;
  52. position: relative;
  53. top: -1px;
  54. }
  55. li button {
  56. float: right;
  57. display: none;
  58. margin-top: 3px;
  59. }
  60. li:before {
  61. content: initial;
  62. }
  63. li:last-child {
  64. border-bottom: none;
  65. }
  66. li:hover {
  67. background-color: #ddd;
  68. }
  69. li:hover button {
  70. display: block;
  71. }
  72. </style>

 2.3 MyHeader.vue

         MyHeader组件是头部添加部分

  1. <template>
  2. <div class="todo-header">
  3. <input type="text" v-model="title" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"/>
  4. </div>
  5. </template>
  6. <script>
  7. import {nanoid} from "nanoid"
  8. export default {
  9. name:"MyHeader",
  10. data() {
  11. return {
  12. title:'',
  13. }
  14. },methods: {
  15. add(){
  16. // 校验输入的数据不能为空
  17. if(!this.title.trim()) return alert("输入不能为空")
  18. // event.target.value获取发生事件的元素的值,这里使用的是双向数据绑定,两种都可以
  19. // 将用户的输出包装成todo对象 uuid用于生成全球唯一字符编码id nanoid是在uuid的基础上做的精简
  20. const todoobj ={id:nanoid(),title:this.title,done:false}
  21. this.addTodo(todoobj)
  22. // 清空
  23. this.title=''
  24. }
  25. },props:[
  26. 'addTodo'
  27. ]
  28. }
  29. </script>
  30. <style scoped>
  31. .todo-header input {
  32. width: 560px;
  33. height: 28px;
  34. font-size: 14px;
  35. border: 1px solid #ccc;
  36. border-radius: 4px;
  37. padding: 4px 7px;
  38. }
  39. .todo-header input:focus {
  40. outline: none;
  41. border-color: rgba(82, 168, 236, 0.8);
  42. box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
  43. }
  44. </style>

2.4 MyFooter.vue

        MyFooter是底部“完成件儿数和事件的总数以及清空已完成部分”这一部分的内容

  1. <template>
  2. <div class="todo-footer" v-show="total">
  3. <label>
  4. <!-- <input type="checkbox" checked="isAll" @change="checkAll" /> -->
  5. <input type="checkbox" v-model="isAll" />
  6. </label>
  7. <span>
  8. <span>已完成{{ doneTotal }}</span> / 全部{{ total }}
  9. </span>
  10. <button class="btn btn-danger">清除已完成任务</button>
  11. </div>
  12. </template>
  13. <script>
  14. export default {
  15. name: "Footer",
  16. data() {
  17. return {
  18. }
  19. },
  20. props: [
  21. 'todos','checkAlltodo'
  22. ],
  23. methods: {
  24. checkAll(e) {
  25. this.checkAlltodo(e.target.checked)
  26. }
  27. },
  28. computed: {
  29. total() {
  30. return this.todos.length
  31. },
  32. doneTotal() {
  33. // reduce做条件查询的 第一个参数是函数(调用的次数是数组的长度),第二个参数是初始符合条件的个数
  34. // pre第一次调用是0,第二次pre是第一次pre的返回值
  35. // current是当前todo对象
  36. return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
  37. },
  38. isAll:{
  39. get(){
  40. return this.doneTotal === this.total && this.total > 0
  41. },
  42. set(value){
  43. this.checkAlltodo(value)
  44. }
  45. }
  46. }
  47. }
  48. </script>
  49. <style scoped>
  50. .todo-footer {
  51. height: 40px;
  52. line-height: 40px;
  53. padding-left: 6px;
  54. margin-top: 5px;
  55. }
  56. .todo-footer label {
  57. display: inline-block;
  58. margin-right: 20px;
  59. cursor: pointer;
  60. }
  61. .todo-footer label input {
  62. position: relative;
  63. top: -1px;
  64. vertical-align: middle;
  65. margin-right: 5px;
  66. }
  67. .todo-footer button {
  68. float: right;
  69. margin-top: 5px;
  70. }
  71. </style>

2.5 App.vue

        App组件是前面所有组件的父组件以及“爷爷”组件。

  1. <template>
  2. <div id="app">
  3. <div class="todo-container">
  4. <div class="todo-wrap">
  5. <Header :addTodo="addTodo" />
  6. <List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo" />
  7. <Footer :todos="todos" :checkAlltodo="checkAlltodo"/>
  8. </div>
  9. </div>
  10. </div>
  11. </template>
  12. <script>
  13. import Header from './components/MyHeader.vue';
  14. import List from "./components/Mylist.vue";
  15. import Footer from "./components/MyFooter.vue";
  16. export default {
  17. name: 'App',
  18. components: {
  19. Header,
  20. List,
  21. Footer
  22. },
  23. data() {
  24. return {
  25. todos: [
  26. // id:唯一标识
  27. // title:需要做的事
  28. //done:是否完成 true完成 FALSE失败
  29. {
  30. id: '001',
  31. title: '吃饭',
  32. done: true
  33. }, {
  34. id: '002',
  35. title: '喝酒',
  36. done: false
  37. }, {
  38. id: '003',
  39. title: '吃饭',
  40. done: true
  41. }
  42. ]
  43. }
  44. },
  45. methods: {
  46. // 添加
  47. addTodo(x) {
  48. console.log("我收到了数据", x);
  49. // 对数组进行修改
  50. this.todos.unshift(x)
  51. },
  52. // 勾選or或取消勾選
  53. checkTodo(id) {
  54. this.todos.forEach((todo) => {
  55. if (todo.id == id) todo.done = !todo.done;
  56. console.log(todo.done)
  57. })
  58. },
  59. // 删除
  60. deleteTodo(id) {
  61. this.todos= this.todos.filter((todo) => {
  62. return todo.id !== id
  63. })
  64. },
  65. // 全选、全不选
  66. checkAlltodo(done){
  67. this.todos.forEach((todo)=>{
  68. todo.done=done
  69. })
  70. }
  71. },
  72. }
  73. </script>
  74. <style>
  75. body {
  76. background: #fff;
  77. }
  78. .btn {
  79. display: inline-block;
  80. padding: 4px 12px;
  81. margin-bottom: 0;
  82. font-size: 14px;
  83. line-height: 20px;
  84. text-align: center;
  85. vertical-align: middle;
  86. cursor: pointer;
  87. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  88. border-radius: 4px;
  89. }
  90. .btn-danger {
  91. color: #fff;
  92. background-color: #da4f49;
  93. border: 1px solid #bd362f;
  94. }
  95. .btn-danger:hover {
  96. color: #fff;
  97. background-color: #bd362f;
  98. }
  99. .btn:focus {
  100. outline: none;
  101. }
  102. .todo-container {
  103. width: 600px;
  104. margin: 0 auto;
  105. }
  106. .todo-container .todo-wrap {
  107. padding: 10px;
  108. border: 1px solid #ddd;
  109. border-radius: 5px;
  110. }
  111. </style>

三、配置项props:

        在剖析todolist案例前呢,我们一定要先熟练地使用props(父传子传值),因为在这次案例中使用较多。

功能:让组件接收外部传过来的数据

使用 :

  (1)、父组件传递数据:

<Student name="李四" :age="11"></Student>

(2)、子组件使用props接收数据,接收的同时对数据类型进行限制+默认值的制定+必要性限制

  1. props:
  2. {
  3. name: {
  4. type: String,
  5. required: true
  6. },
  7. age: {
  8. type: Number,
  9. default:99
  10. }
  11. }

备注:props是只读的。vue底层会检监测你对props的修改,如果修改了,就会发出警告,

    若业务需求需要修改,那么复制props的内容到data中一份,然后去修改data中的数据

四:部分功能分析:

4.1 list列表显示

        由于todos是有很多个组件进行操作,所以在定义的时候为了方便操作,我们会把它方法App.vue(公用父组件)中,由于列表的显示是在item组件中,在我们没有学会消息订阅与发布以及全局事件总线前,我们只能先将todos通过props先传递给mylist组件、然后使用v-for进行数据的遍历这样就可以得到list列表显示的效果了

  1. <ul class="todo-main">
  2. <Item v-for="todoobj in todos"
  3. :key="todoobj.id"
  4. />
  5. </ul>
  6. </template>
  7. <script>
  8. import Item from "./Myitem.vue";
  9. export default {
  10. name:"List",
  11. components:{Item},
  12. props:[
  13. 'todos',
  14. ]
  15. }
  16. </script>

4.2 添加功能

        由于添加功能的时候,所处的组件关系不是父子关系,所以添加功能相对于其他的功能会复杂一些,在我们没有学过全局事件总线以及消息的订阅与发布之前,我们可以使用组件自定义事件进行myheader组件对vue.app组件 的传值(子传父),而再将拿到的数据通过父传子传给mylist子组件即可。子组件向父组件传值怎么传递呢?方法如下:

 子传父的方式:

        方式一:通过父组件给子组件擦魂帝函数类型的props实现;子给父传递函数

        方式二:通过父组件给子组件绑定一个自定义事件实现:子传父传递数据

        方式三:通过ref ,灵活性更强(方式三是方式二的另一种实现方式)

假定有两个组件,app.vue为父组件,school,student为子组件

(1)、通过父组件给子组件擦魂帝函数类型的props实现;子给父传递函数

  1. app组件: <School :getSchoolName="getSchoolName"></School>
  2. getSchoolName(name) {
  3. this.msg = name
  4. }
  1. School组件:<button @click="sendSchoolName">点我将学校名传给APP组件</button>
  2. methods: {
  3. sendSchoolName(){
  4. this.getSchoolName(this.name)
  5. }
  6. },
  7. props:[
  8. 'getSchoolName'
  9. ]

(2)、通过父组件给子组件绑定一个自定义事件实现:子传父传递数据

  1. app组件 : <Student v-on:atguigu="getStudentName"></Student>
  2. getStudentName(name) {
  3. console.log("收到了"+name)
  4. this.msg = name
  5. }
  1. Student组件:
  2. <button @click="sendStudentName">点我将学生名传给APP</button>
  3. methods: {
  4. sendStudentName(){
  5. // 触发Student组件实例身上的atguigu事件
  6. this.$emit('atguigu',this.name)
  7. }
  8. },

(3)、通过ref ,灵活性更强(方式三是方式二的另一种实现方式)

  1. APP组件:
  2. <Student ref="student"></Student>
  3. getStudentName(name) {
  4. console.log("收到了"+name)
  5. this.msg = name
  6. },
  7. mounted() {
  8. setTimeout(() => {
  9. // 绑定自定义事件
  10. // this.$refs.student.$on('atguigu', this.getStudentName)
  11. // 绑定自定义事件,只执行一次
  12. this.$refs.student.$once('atguigu', this.getStudentName)
  13. }, 5000)
  14. },
  1. Student组件:
  2. methods: {
  3. sendStudentName(){
  4. // 触发Student组件实例身上的atguigu事件
  5. this.$emit('atguigu',this.name)
  6. }
  7. },

按照这三种方式,也可以完成子组件向父组件间的传值功能,可以体会一下~后期学了“消息的订阅与发布”和“全局事件总线”后,会有更加便捷的方法~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/223885?site
推荐阅读
相关标签
  

闽ICP备14008679号