「后端小伙伴来学前端了」Vue中Props 实现组件通信TodoList案例
自己拍的小云彩
源码在文末。
前言
上篇文章写了个V利用Props进行组件之间的通信,这不立马就安排上这个案例拉丫。光学不敲等于没学哈(资深大佬除外哈)
目标就是实现如下的样子:
能够进行增删改查,并且是在各个组件之间。
一、环境准备
针对这个页面,我们将他们划分为下面四个组件哈。其实也不是固定的,只是为了更好的展示组件之间的通信。
项目结构:
准备静态页面
MyTodoHeader头部组件:
<template><div class="todo-header-box"><input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title"/></div> </template> <script> import { v4 as uuidv4 } from 'uuid' export default {props: {},data () {return {title: ''}},methods: {} } </script> <style scoped> .todo-header-box{width: 500px;height: 40px;margin-top:10px; } .todo-header-box input{width: 460px;height: 40px;margin-left: 10px;border: 1px solid #43B984;border-radius: 8px;padding-left: 10px; } :focus-visible{ outline:none; } </style>MyTodoList组件,另外组件内还包含着MyTodoItem组件
<template> <ul class="todo-main"><TodoItem v-for="(todo, index) in todos" :key="index" :todo="todo"/></ul> </template> <script> import TodoItem from './MyTodoItem' export default {components: {TodoItem},props: {},methods: {} } </script> <style scoped> /*main*/ .todo-main {margin-top: 10px;margin-left: 0px;border: 1px solid #ddd;border-radius: 2px;padding: 0px; }.todo-empty {height: 40px;line-height: 40px;border: 1px solid #ddd;border-radius: 2px;padding-left: 5px;margin-top: 10px; }/*item*/ li {list-style: none;height: 36px;line-height: 36px;padding: 0 5px;border-bottom: 1px solid #ddd; }li label {float: left;cursor: pointer; }li label li input {vertical-align: middle;margin-right: 6px;position: relative;top: -1px; }li button {float: right;display: none;margin-top: 3px; }li:before {content: initial; }li:last-child {border-bottom: none; } </style>MyTodoItem组件
<template><li :style="{background: bgColor}"><label><input type="checkbox" :checked="todo.done" /><span>{{todo.title}}</span></label><button class="btn btn-danger" style="display:none" v-show="isShow">删除</button></li> </template><script> export default {props: {todo: Object},data () {return {bgColor: 'white',isShow: false}},methods: {} } </script>MyTodoFooter组件
<template><div class="todo-footer" v-show="total"><label><!-- // 第一种方式:通过dom元素来判断有没有进行勾选 不是最佳方式 --><!-- <input type="checkbox" :checked="isAllCheck" @click="checkAll" /> --><!-- 第二种方式: 通过绑定计算属性来进行展示 --><input type="checkbox" v-model="isAllCheck" /></label><span>已完成{{ doneTotal }}<span> / 全部{{ todos.length }} </span></span><buttonclass="btn btn-danger">清除已完成任务</button></div> </template><script> export default {props: {todos: Array,clearDoneTodos: Function,checkAllTodos: Function},computed: {total () {return this.todos.length},doneTotal () {return this.todos.reduce((preTotal, todo) => preTotal + (todo.done ? 1 : 0), 0)},isAllCheck: {get () {return this.doneTotal === this.todos.length && this.doneTotal > 0}}},methods: {} } </script> /*footer*/ <style scoped> .todo-footer {height: 40px;line-height: 40px;padding-left: 6px;margin-top: 5px; }.todo-footer label {display: inline-block;margin-right: 20px;cursor: pointer; }.todo-footer label input {position: relative;top: -1px;vertical-align: middle;margin-right: 5px; }.todo-footer button {float: right;margin-top: 5px; } </style>App组件
<template><div class="todo-container"><!-- header模块 --><TodoHeader/><!-- main 模块 --><TodoList :todos="todos"/><!-- 主要的内容模块 --><TodoFooter :todos="todos"/><!-- footer模块 --></div> </template> <script> import TodoHeader from './components/MyTodoHeader' import TodoList from './components/MyTodoList' import TodoFooter from './components/MyTodoFooter'export default {components: {TodoHeader,TodoList,TodoFooter},data () {return {todos: [{ id: '001', title: '吃饭', done: true },{ id: '002', title: '睡觉', done: false },{ id: '003', title: '敲代码', done: true }]}},methods: {}} </script> <style> * {margin: 0 0;padding: 0 0; } .todo-container {margin: 0 auto;margin-top: 10px;width: 500px;height: 500px;background-color: #ccc;border: 1px solid #ddd;border-radius: 8px; } </style>二、在头部组件中实现增加方法
首先说说我们的需求:
就是在头部组件中的输入框中进行输入,然后按下回车键就将数据增加到todos数组中,并在下面的列表中展示出来。
思路大致如下:
App组件中修改:
<template><div class="todo-container"><!-- header模块:addTodo 定义的是子组件接收的名称"addTodo" 指向的是此组件中所定义的方法 --><TodoHeader :addTodo="addTodo" /><!-- main 模块 --><TodoList :todos="todos" /><!-- 主要的内容模块 --><TodoFooter :todos="todos"/><!-- footer模块 --></div> </template> <script> import TodoHeader from './components/MyTodoHeader' import TodoList from './components/MyTodoList' import TodoFooter from './components/MyTodoFooter'export default {components: {TodoHeader,TodoList,TodoFooter},data () {return {message: 'hello',todos: [{ id: '001', title: '吃饭', done: true },{ id: '002', title: '睡觉', done: false },{ id: '003', title: '敲代码', done: true }]}},methods: {// 回车增加一个todoaddTodo (todo) {this.todos.unshift(todo)}} } </script>我们通过:addTodo="addTodo"传递给子组件一个方法,然后在子组件中我们用props来接收。
<template><div class="todo-header-box"><input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/></div> </template> <script> import { v4 as uuidv4 } from 'uuid' export default {props: {// 通过props来接收addTodo: Function},data () {return {title: ''}},methods: {add () {// 1. 检查输入合法性const title = this.title.trim()if (!title) {alert('请输入内容')return}const id = uuidv4()// 2. 根据输入生成一个todo对象const todo = { id, title, done: false }// 3. 这里的this.addTodo 调用的实际上就是执行父组件中的addTodo函数 //添加到todosthis.addTodo(todo)// 4. 清除输入this.title = ''}} } </script>这里我使用到了uuid生成全局唯一id。
安装方式:
npm install uuid --save # 引用的话直接 import { v4 as uuidv4 } from 'uuid'; # 用法: 直接调用这个函数即可 uuidv4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'三、完善 MyTodoList 组件 | 根据id删除一条todo&判断是否选中
先说说需求:
其实还有第三个的哈,我没写了(懒了),第三个是编辑,大家可以试一试.
如下图:
思路其实蛮简单的哈:
MyTodoList组件
<template> <ul class="todo-main"><TodoItem v-for="(todo, index) in todos" :key="index" :todo="todo" :deleteTodo="deleteTodo" :checkTodo="checkTodo" /></ul> </template> <script> import TodoItem from './MyTodoItem' export default {components: {TodoItem},props: {todos: Array,deleteTodo: Function,checkTodo: Function},methods: {} } </script>MyTodoItem组件
<template><li @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)" :style="{background: bgColor}"><label><input type="checkbox" :checked="todo.done" @click="handlerCheck(todo.id)" /><span>{{todo.title}}</span></label><button class="btn btn-danger" style="display:none" v-show="isShow" @click="handlerDeleteItem(todo.id)">删除</button></li> </template><script> export default {props: {todo: Object,checkTodo: Function,deleteTodo: Function},data () {return {bgColor: 'white',isShow: false}},methods: {handleEnter (isEnter) {if (isEnter) {this.bgColor = '#aaa'this.isShow = true} else {this.bgColor = 'white'this.isShow = false}},// 修改勾选状态handlerCheck (id) {console.log(id)this.checkTodo(id)},// 根据id删除handlerDeleteItem (id) {if (window.confirm('确定删除吗')) {this.deleteTodo(id)}}} } </script>App组件见下文哈
四、完善尾部组件 | 判断是否全部勾选及清除全部已完成任务
照常先谈谈我们的需求:
1、判断是否全部勾选,修改数据状态。
2、清除选中的任务
3、当没有任何数据时,底部栏不展示
先讲讲第一个的思路:判断有没有全选,其实就是判断todos数组的长度是否等于已经选中的数量(另外就是注意就是数组长度必须要大于零)
第二个:清除选中的任务,其实就是根据id 删除掉 App父组件中 todos中我们选中的数据。
第三个:使用v-show指令即可,直接用todos数组的长度即可,当数组长度为0时,v-show自然为”false“,反之为true
理清楚,直接看代码哈
<template><div class="todo-footer" v-show="total"><label><!-- // 第一种方式:通过dom元素来判断有没有进行勾选 不是最佳方式 --><!-- <input type="checkbox" :checked="isAllCheck" @click="checkAll" /> --><!-- 第二种方式: 通过绑定计算属性来进行展示 --><input type="checkbox" v-model="isAllCheck" /></label><span>已完成{{ doneTotal }}<span> / 全部{{ todos.length }} </span></span><buttonclass="btn btn-danger"@click="deleteDoneAll">清除已完成任务</button></div> </template><script> export default {props: {todos: Array,clearDoneTodos: Function,checkAllTodos: Function},computed: {total () {return this.todos.length},doneTotal () {return this.todos.reduce((preTotal, todo) => preTotal + (todo.done ? 1 : 0), 0)},isAllCheck: {get () {return this.doneTotal === this.todos.length && this.doneTotal > 0},// 通过计算属性来判断是否全选或全不选set (checked) {this.checkAllTodos(checked)}}},methods: {deleteDoneAll () {this.clearDoneTodos()}// 通过dom元素来判断有没有进行勾选 不是最佳方式// checkAll (e) {// console.log(e.target.checked)// this.checkAllTodos(e.target.checked)// }} } </script>为什么不选择通过dom元素来判断有没有进行勾选呢?
Vue框架中并不建议我们直接操作Dom元素,更多的是希望我们通过vue框架自带的方式来实现.
App组件:
<template><div class="todo-container"><!-- :message 对应的是子组件 prop 中接收变量的名称"message" 对应的父组件中data中定义的数据--><!-- header模块 --><TodoHeader :addTodo="addTodo" /><!-- main 模块 --><TodoList :todos="todos" :deleteTodo="deleteTodo" :checkTodo="checkTodo" /><!-- 主要的内容模块 --><TodoFooter:todos="todos":clearDoneTodos="clearDoneTodos":checkAllTodos="checkAllTodos"/><!-- footer模块 --></div> </template> <script> import TodoHeader from './components/MyTodoHeader' import TodoList from './components/MyTodoList' import TodoFooter from './components/MyTodoFooter'export default {components: {TodoHeader,TodoList,TodoFooter},data () {return {message: 'hello',todos: [{ id: '001', title: '吃饭', done: true },{ id: '002', title: '睡觉', done: false },{ id: '003', title: '敲代码', done: true }]}},methods: {// 回车增加一个todoaddTodo (todo) {this.todos.unshift(todo)},// 判断勾选不勾选checkTodo (id) {this.todos.forEach((todo) => {if (todo.id === id) todo.done = !todo.done})},// 删除一个tododeleteTodo (id) {this.todos = this.todos.filter(todo => todo.id !== id)},// 全选全不选checkAllTodos (done) {this.todos.forEach((todo) => { todo.done = done })},// 清除所有已完成的任务clearDoneTodos () {this.todos = this.todos.filter(todo => !todo.done)}}} </script>五 小结
vue中组件通信的方式其实有很多种,就像我已经学过的就有props | emit | 全局事件主线 | 发布订阅模式
之后还有Vuex,另外我们还可以自己定制.还有蛮多我没学到的 捂脸
其实本质都是去做数据的共享,大都数情况都是根据实际情况来选择的,并非那一样就是最好的。
六 源码
gitee
github
后语
大家一起加油!!!如若文章中有不足之处,请大家及时指出,在此郑重感谢。
纸上得来终觉浅,绝知此事要躬行。
大家好,我是博主宁在春:主页
一名喜欢文艺却踏上编程这条道路的小青年。
希望:我们,待别日相见时,都已有所成。L
总结
以上是生活随笔为你收集整理的「后端小伙伴来学前端了」Vue中Props 实现组件通信TodoList案例的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 「后端小伙伴来学前端了」Vue中Prop
- 下一篇: 「后端小伙伴来学前端了」Vue中利用全局