在这里插入图片描述

TodoList案例

(省略样式部分)

分析

分为四个组件 Header List Item Footer

App.vue

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader :addTodo="addTodo"></MyHeader>
        <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList>
        <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></MyFooter>
      </div>
    </div>
  </div>
</template>

<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";

export default {
  name: "App",
  components: { MyHeader, MyList, MyFooter },
  data() {
			return {
				todos:[
					{id:"001",title:"抽烟",done:true},
					{id:"002",title:"喝酒",done:false},
					{id:"003",title:"开车",done:true}
				]
			}
	},
  methods:{
    //添加todo
    addTodo(todoObj){
      this.todos.unshift(todoObj)
    },
    //勾选或取消勾选一个todo
    checkTodo(id){
      //遍历todo
      this.todos.forEach((todo)=>{
        if(todo.id===id) todo.done=!todo.done
      })
    },
    deleteTodo(id){
      this.todos = this.todos.filter((todo)=>{
        return todo.id !== id
      })
    },
    //全选或全不选
    checkAllTodo(done){
      this.todos.forEach((todo)=>{
        todo.done = done
      })
    },
    //清除所有已完成todo
    clearAllTodo(){
      this.todos = this.todos.filter((todo)=>{
        return !todo.done
      })
    }
  }
};
</script>

<style></style>

功能实现:

1.Header部分

按回车键后添加内容todoObj,内容应添加进List部分,兄弟间不可传数据,将todos中内容放置在父组件App中,通过子组件Header向父组件App中传递todoObj来实现新内容的添加。

此处涉及子向父传数据,该案例使用最基础的方法,父先向子传入一个函数(addTodo),子组件通过props数组接收。子组件再调用这个函数实现子向父传递。

Header.vue
<template>
	<div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
    </div>
</template>

<script>
  	import {nanoid} from 'nanoid'
	export default {
		name:'MyHeader',
    data() {
      return {
        title:''
      }
    },
    props:['addTodo'],
		//接收从App传递过来的addTodo
    methods: {
      add(){
        //校验数据
        if(!this.title.trim()) return alert('输入不能为空')
        //将用户输入生成一个对象
        const todoObj={id:nanoid(),title:this.title,done:false}
        //将新生成的对象传给App
        this.addTodo(todoObj)
        this.title=''
      }
    },
    
	}
</script>

<style scoped></style>

注: 在为新添加的内容生成唯一id时使用nanoid方法,需要先引入。

2.List部分

将App中todos传给List进行渲染,将todoObj传给Item。

List.vue
<template>
	<ul class="todo-main">
        <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyItem>
      </ul>
</template>

<script>
	import MyItem from './MyItem'

	export default {
		name:'MyList',
		components:{MyItem},
		props:['todos','checkTodo','deleteTodo']	//接收
	}
</script>

<style scoped></style>
3.Item部分

标记勾选或取消勾选一个todo,改变done的值(通过id:向App组件传id => 子向父传数据),在子组件中勾选或取消勾选 可以通过@change@click调用函数。

需要App组件向该组件传入相应函数(checkTodo <=> 在App组件中编写的改变done值的函数),即需要App先向List组件传函数,再由List组件向Item组件传函数。

点击删除按钮实现删除某一todo,通过id值确定删除的内容。实现过程与上述类似。

Item.vue
<template>
  <li>
    <label>
      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
      <span>{{todo.title}}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
  </li>
</template>

<script>
export default {
  name: "MyItem",
  //声明接收todo对象
  props:['todo','checkTodo','deleteTodo'],
  methods: {
    //通知App
    handleCheck(id){
      this.checkTodo(id)
    },
    handleDelete(id){
      if(confirm('确定删除吗?')){
        this.deleteTodo(id)
      }
    }
  },
};
</script>

<style scoped></style>
4.Footer部分

全部todo数,采用计算属性(即todos数组的长度)

已完成todo的个数,采用计算属性,使用数组方法reduce => 迭代数组,把它累积到一个值中

reduce(回调函数(pre,current){},初始值)

注: 数组长度是多少 函数调用多少次

初始pre为统计数0,current为正在处理项

例如:

this.todos.reduce((pre,current)=>{   012
	console.log('@',pre)             012
	return pre+1                     123
},0)

在回调函数中:

  • 上一次的返回值为下一次的pre值

  • 控制台上输出为012

  • 最后一次返回值为3

最后一次返回值3为reduce的返回值

当所有todo被选中时,最下方的复选框也应被勾选。

给该复选框绑定checked属性,通过计算属性isAll(布尔型)决定checked值。当 全部todo数和已完成todo个数相等 且 总数>0 为真,被勾选。

当勾选或取消最下方复选框时,上述每个todo的复选框都被勾选或都不选。

将最下方复选框勾选状态即checked值传给App组件,在App组件中编写 使每个todo的done值与其checked值相等 的函数。上述需Footer组件向App组件传数据,实现过程与Item部分中相同(子 => 父)。

清除已完成任务按钮的实现。

删除已完成todo项,需在App组件中编写相应函数。与子=>父过程相似。

Footer.vue
<template>
  <div class="todo-footer" v-show="total!=0">
    <label>
      <input type="checkbox" :checked="isAll" @change="checkAll"/>
    </label>
    <span>
      <span>已完成{{doneTotal}}</span> / 全部{{total}}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "MyFooter",
  props: ["todos", "checkAllTodo","clearAllTodo"],
  computed: {
    total() {
      return this.todos.length;
    },
    doneTotal() {
      return this.todos.reduce((pre, todo) => {
        return pre + (todo.done ? 1 : 0);
      }, 0);
    },
    isAll() {
      return this.total === this.doneTotal && this.total > 0;
    },
  },
  methods: {
    checkAll(e) {
      this.checkAllTodo(e.target.checked);
    },
	clearAll(){
		this.clearAllTodo();
	}
  },
};
</script>

<style scoped></style>

注: 使用v-model时,绑定的值不能是props传过来的值,因为props不可修改

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐