Vue(七) TodoList案例1.0

发布于:2024-09-05 ⋅ 阅读:(32) ⋅ 点赞:(0)

组件化编码流程(通用)

1、实现静态组件:抽取组件,使用组件实现静态页面效果

2、展示动态数据:

  • 数据的类型、名称是什么
  • 数据保存再哪个组件

3、交互–从绑定事件监听开始

如果组件名字不好起,想一下是不是组件拆分的不合理

1. 拆分静态组件

根据功能,可划分为以下几个组件:
在这里插入图片描述
将已写好页面的html,css根据组件进行拆分,得到的文件结构如下:
App组件里使用了MyHeader、MyList、MyFooter组件
在这里插入图片描述
MyList组件里使用了MyItem组件:
在这里插入图片描述

2. 初始化列表

展示动态数据

(1) 确定数据名称、数据类型
数据名称:这一堆的代办事项数据名称可以叫todos,
数据类型:最好用数组存储这一堆的待办事项,而每一个代办事项可以用对象存储。

(2) 数据保存在哪个组件:
谁用这些数据,就写在哪个组件里后续如果有变化,再改变位置。因此暂时写在MyList组件里。
MyList.vue

<template>
  <ul class="todo-main">
   <!-- 循环生成多个MyItem,且将具体的代办事项传递给MyItem,否则还是页面的列表里还是yyyy-->
    <MyItem v-for="toboObj in todos" :key="toboObj.id" :todo="toboObj" />
  </ul>
</template>
<script>
...
  data () {
    return {
      todos: [
        { id: '0001', title: '吃饭', done: true },
        { id: '0002', title: '睡觉', done: true },
        { id: '0003', title: '打豆豆', done: false }]
    }
  }
</script>

MyItem.vue

<template>
  <li>
    <label>
      <!-- 添加checked属性,初始化复选框的数据 -->
      <input type="checkbox" checked />
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" style="display: none">删除</button>
  </li>
</template>

<script>
export default {
  name: 'MyItem',
  // 接收MyList组件传递的对象
  props: ["todo"]
}
</script>

但是添加checked后,所有的复选框都被勾选了。

如何让一个标签动态的拥有某一个属性

答:v-bind动态绑定

  <input type="checkbox" :checked="todo.done" />

在这里插入图片描述

3. 按回车添加todo

输入框在Header组件中,因此Header组件中要添加点击事件

<template>
  <div class="todo-header">
    <!-- v-model获取表单元素的值 -->
    <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keydown.enter="add"/>
  </div>
</template>

add回调函数中要做以下几件事:

import { nanoid } from 'nanoid'
methods: {
    add () {
        // 1. 获取到表单元素的内容
        console.log(this.title); // 或者通过 e.target.value获取到输入框的内容
        // 2. 将用户的输入包装成一个todo对象
        const todoObj = { id: nanoid(), title: this.title, done: false }  
        // 3. 将todo对象添加到todo列表里,即todos数组中
        // 4. 输入框内容置空。
        this.title = '' // 或者 e.target.value = '' 
    }
}
  • nanoid
    包装的对象应该有一个唯一的id,uuid这个库可以生成全球唯一的编码,但是太大且生成的编码复杂。采用uuid的精简版:nanoid

    安装npm i nanoid
    引用 import {nanoid} from 'nanoid'
    使用: nanoid()(这是个函数,直接调用即可)

  • todoObj在MyHeader组件里,而数据todos在MyList组件里。两组件为兄弟组件,目前还不会兄弟组件之间通信。所以还是采用props属性。
    为了方便MyHeader通信,将todos改放在App组件里,而不是MyList组件里。此时:
    在这里插入图片描述

App可通过props将todos交给MyList组件进行展示。MyHeader该如何将todoObj交给父组件App呢?

子组件给父组件传值之props

父组件给子组件传递带参的函数

<!--App.vue-->
<template>
    ...
    <!-- 给子组件传函数 -->
    <MyHeader :addTodo="addTodo"></MyHeader>
    <!-- 给子组件传数据 -->
    <MyList :todos="todos"></MyList>
    <MyFooter></MyFooter>
    ...
</template>
<script>
  methods: {
    // 添加todoObj
    addTodo (todoObj) {
      this.todos.unshift(todoObj)
    }
  }
}
</script>

子组件接收函数

<!--MyHeader.vue-->
<script>
  props: ["addTodo"],
  methods: {
    add () {
      //完善一下:校验数据
      if (!this.title.trim()) return alert('输入不能为空')
      const todoObj = { id: nanoid(), title: this.title, done: false }
      // 调用函数,通过参数将todoObj传递给父组件
      this.addTodo(todoObj)
      this.title = ''
    }
  }
}
</script>

总结:子组件向父组件传值时,父组件需提前向子组件传递一个带参的函数,子组件通过函数的参数将数据传给父组件。

捋顺一下流程:
1、按下回车,执行MyHeader里的add函数,在add里调用了App组件里的addTodo函数。
2、addTodo函数修改了App组件中data里的todos数据。
3、Vue捕获到todos变了,于是重新解析App里的template模板,重新解析时,将变化后的todos交给了MyList组件。
4、MyList收到数据,重新解析自己组件内的模板,v-for,虚拟DOM对比,更新页面。

4. 勾选与取消勾选一个Todo

在这里插入图片描述

MyItem.vue添加change事件(@click也可以)

  <input  type="checkbox"  :checked="todo.done" @change="handleCheck(todo.id)" />

handleCheck事件里需要对todos数据进行修改。数据在App组件里, 数据在哪里,操作数据的方法就在哪里,所以这里需要把待修改的todo的id传递给App。子组件给父组件传值,父组件需要先传递一个带参函数。此处应该是App传给MyList,MyList再传递给MyItem。

App.vue

<template>
...
    <!-- 先将函数传递给MyList -->
    <MyList :todos="todos" :checkTodo="checkTodo"></MyList>
...
<script>
    // 勾选or取消勾选一个todo
    checkTodo (id) {
        this.todos.forEach((todo) => {
            if (todo.id === id) {
                todo.done = !todo.done
            }
        })
    }
</script>

MyList.vue

  <MyItem   v-for="toboObj in todos" :key="toboObj.id" :todo="toboObj"  :checkTodo="checkTodo" />
<script>
  props: ["todos", "checkTodo"]
</script>

MyItem.vue

// 接收MyList组件传递的对象
props: ["todo", "checkTodo"],
    methods: {
        handleCheck (id) {
            this.checkTodo(id)
    }
}

需要注意的是:
:checked="todo.done" ----初始化复选框
@change="handleCheck(todo.id) ----更新复选框
这两个操作可用v-model进行合并

<!-- 这行代码也能实现,但是有点儿违反原则,因为这样修改了props里数据的值,不报错是因为,todo是个对象,地址值没被修改, --> 
<input type="checkbox" v-model="todo.done" />

5. 删除

在这里插入图片描述
点击按钮,获取当前MyItem的id,根据id删除该条数据。同样的套路

添加监听事件,监听事件里调用父组件传递过来的函数。通过父组件里的这个函数对todos进行删除操作。
App.vue:

<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList>
<script>
    // 删除一个todo
    deleteTodo (id) {
        this.todos = this.todos.filter((todo) => {
            return todo.id !== id
        })
    }
</script>

MyList.vue:

<MyItem
v-for="toboObj in todos"
:key="toboObj.id"
:todo="toboObj"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
/>
<script>
    props: ["todos", "checkTodo", "deleteTodo"]
</script>

MyItem.vue:

<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
<script>
    handleDelete (id) {
    // 弹框
    if (confirm('确定要删除吗?')) {
          // 通知App,删除对应的todo
        this.deleteTodo(id)
      }
    }
</script>

在这里插入图片描述

6. footer底部统计

在这里插入图片描述

<!--Footer.vue--> 
<!--使用计算属性--> 
<span>已完成{{ doneTotal }}</span> / 全部{{ total }}
<script>
  props: ["todos"],
  computed: {
    total () {
      return this.todos.length
    },
    doneTotal () {
        // reduce记数
      return this.todos.reduce((prev, cur) => {
        return prev + (cur.done ? 1 : 0)
      }, 0)
      // 简写
      // return this.todos.reduce((prev, current) => prev + (current.done ? 1 : 0))
    }
</script>

7. footer底部交互

这里结合6底部统计一起看

7.1 全选框自动打勾

如果代办事项全都选上,则该框自动打勾,
在这里插入图片描述

<input type="checkbox" :checked="isAll" @change="checkAll" />
<script>
  computed: {
    isAll () {
      // 已完成的数量是否等于todos的总数
      return this.doneTotal === this.total
    }
  },
</script>  

但是这样有个问题:
在这里插入图片描述
改进:

isAll () {
    return this.doneTotal === this.total && this.total > 0
}

优化:当没有代办事项时,footer组件不应该显示
在这里插入图片描述

7.2 全选框取消勾选

方式一:添加点击事件
App组件中创建操作todos的函数并传递给子组件
在这里插入图片描述
子组件接收函数并调用
在这里插入图片描述
方式二:v-model
(注意此处与 第4节的勾选与取消勾选一个Todo的区别)

<!-- :check = "isAll"用于初始化复选框
@change="checkAll"用来更新复选框
-->
<input type="checkbox" :checked="isAll" @change="checkAll" />
<!-- 简写为 -->
<input type="checkbox" v-model="isAll" />

由于isAll是计算属性,且v-model是双向绑定,所以当操作复选框时,对isAll的值进行了修改;
在这里插入图片描述
所以此时isAll不能采用简写形式了

isAll: {
    get () {
        return this.doneTotal === this.total && this.total > 0
    },
    set (value) {
        this.checkAllTodo(value)
   }
}

8. 清除已完成任务

在这里插入图片描述

添加点击事件
MyFooter.vue

<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
<script>
props: ["todos", "checkAllTodo", "clearAllTodo"],
clearAll () {
    this.clearAllTodo()
}
</script>

App.vue

<MyFooter
    :todos="todos"
    :checkAllTodo="checkAllTodo"
    :clearAllTodo="clearAllTodo"
></MyFooter>
<script>
// 清除所有已完成的todo
    clearAllTodo () {
      this.todos = this.todos.filter((todo) => {
        return !todo.done
      })
    }
</script>

总结

  1. 组件化编码流程:
  • 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
  • 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用
    • 一个组件在用:放在组件自身即可。
    • 一些组件在用:放在他们共同的父组件上(状态提升
  • 实现交互:从绑定事件开始。数据在哪里,对数据进行处理的方法就在哪里
  1. props适用于:

    ​ (1).父组件 ==> 子组件 通信

    ​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数)

  2. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

  3. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

项目完整代码链接:https://gitee.com/LXHST/vue2–Todo-list-case-1.0/tree/master/


网站公告

今日签到

点亮在社区的每一天
去签到