「后端小伙伴来学前端了」Vue中Props 实现组件通信TodoList案例
自己拍的小云彩
源碼在文末。
前言
上篇文章寫了個V利用Props進(jìn)行組件之間的通信,這不立馬就安排上這個案例拉丫。光學(xué)不敲等于沒學(xué)哈(資深大佬除外哈)
目標(biāo)就是實現(xiàn)如下的樣子:
能夠進(jìn)行增刪改查,并且是在各個組件之間。
一、環(huán)境準(zhǔn)備
針對這個頁面,我們將他們劃分為下面四個組件哈。其實也不是固定的,只是為了更好的展示組件之間的通信。
項目結(jié)構(gòu):
準(zhǔn)備靜態(tài)頁面
MyTodoHeader頭部組件:
<template><div class="todo-header-box"><input type="text" placeholder="請輸入你的任務(wù)名稱,按回車鍵確認(rèn)" 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組件,另外組件內(nèi)還包含著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元素來判斷有沒有進(jìn)行勾選 不是最佳方式 --><!-- <input type="checkbox" :checked="isAllCheck" @click="checkAll" /> --><!-- 第二種方式: 通過綁定計算屬性來進(jìn)行展示 --><input type="checkbox" v-model="isAllCheck" /></label><span>已完成{{ doneTotal }}<span> / 全部{{ todos.length }} </span></span><buttonclass="btn btn-danger">清除已完成任務(wù)</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"/><!-- 主要的內(nèi)容模塊 --><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>二、在頭部組件中實現(xiàn)增加方法
首先說說我們的需求:
就是在頭部組件中的輸入框中進(jìn)行輸入,然后按下回車鍵就將數(shù)據(jù)增加到todos數(shù)組中,并在下面的列表中展示出來。
思路大致如下:
App組件中修改:
<template><div class="todo-container"><!-- header模塊:addTodo 定義的是子組件接收的名稱"addTodo" 指向的是此組件中所定義的方法 --><TodoHeader :addTodo="addTodo" /><!-- main 模塊 --><TodoList :todos="todos" /><!-- 主要的內(nèi)容模塊 --><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="請輸入你的任務(wù)名稱,按回車鍵確認(rèn)" 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('請輸入內(nèi)容')return}const id = uuidv4()// 2. 根據(jù)輸入生成一個todo對象const todo = { id, title, done: false }// 3. 這里的this.addTodo 調(diào)用的實際上就是執(zhí)行父組件中的addTodo函數(shù) //添加到todosthis.addTodo(todo)// 4. 清除輸入this.title = ''}} } </script>這里我使用到了uuid生成全局唯一id。
安裝方式:
npm install uuid --save # 引用的話直接 import { v4 as uuidv4 } from 'uuid'; # 用法: 直接調(diào)用這個函數(shù)即可 uuidv4(); // ? '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'三、完善 MyTodoList 組件 | 根據(jù)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}},// 修改勾選狀態(tài)handlerCheck (id) {console.log(id)this.checkTodo(id)},// 根據(jù)id刪除handlerDeleteItem (id) {if (window.confirm('確定刪除嗎')) {this.deleteTodo(id)}}} } </script>App組件見下文哈
四、完善尾部組件 | 判斷是否全部勾選及清除全部已完成任務(wù)
照常先談?wù)勎覀兊男枨?#xff1a;
1、判斷是否全部勾選,修改數(shù)據(jù)狀態(tài)。
2、清除選中的任務(wù)
3、當(dāng)沒有任何數(shù)據(jù)時,底部欄不展示
先講講第一個的思路:判斷有沒有全選,其實就是判斷todos數(shù)組的長度是否等于已經(jīng)選中的數(shù)量(另外就是注意就是數(shù)組長度必須要大于零)
第二個:清除選中的任務(wù),其實就是根據(jù)id 刪除掉 App父組件中 todos中我們選中的數(shù)據(jù)。
第三個:使用v-show指令即可,直接用todos數(shù)組的長度即可,當(dāng)數(shù)組長度為0時,v-show自然為”false“,反之為true
理清楚,直接看代碼哈
<template><div class="todo-footer" v-show="total"><label><!-- // 第一種方式:通過dom元素來判斷有沒有進(jìn)行勾選 不是最佳方式 --><!-- <input type="checkbox" :checked="isAllCheck" @click="checkAll" /> --><!-- 第二種方式: 通過綁定計算屬性來進(jìn)行展示 --><input type="checkbox" v-model="isAllCheck" /></label><span>已完成{{ doneTotal }}<span> / 全部{{ todos.length }} </span></span><buttonclass="btn btn-danger"@click="deleteDoneAll">清除已完成任務(wù)</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元素來判斷有沒有進(jìn)行勾選 不是最佳方式// checkAll (e) {// console.log(e.target.checked)// this.checkAllTodos(e.target.checked)// }} } </script>為什么不選擇通過dom元素來判斷有沒有進(jìn)行勾選呢?
Vue框架中并不建議我們直接操作Dom元素,更多的是希望我們通過vue框架自帶的方式來實現(xiàn).
App組件:
<template><div class="todo-container"><!-- :message 對應(yīng)的是子組件 prop 中接收變量的名稱"message" 對應(yīng)的父組件中data中定義的數(shù)據(jù)--><!-- header模塊 --><TodoHeader :addTodo="addTodo" /><!-- main 模塊 --><TodoList :todos="todos" :deleteTodo="deleteTodo" :checkTodo="checkTodo" /><!-- 主要的內(nèi)容模塊 --><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 })},// 清除所有已完成的任務(wù)clearDoneTodos () {this.todos = this.todos.filter(todo => !todo.done)}}} </script>五 小結(jié)
vue中組件通信的方式其實有很多種,就像我已經(jīng)學(xué)過的就有props | emit | 全局事件主線 | 發(fā)布訂閱模式
之后還有Vuex,另外我們還可以自己定制.還有蠻多我沒學(xué)到的 捂臉
其實本質(zhì)都是去做數(shù)據(jù)的共享,大都數(shù)情況都是根據(jù)實際情況來選擇的,并非那一樣就是最好的。
六 源碼
gitee
github
后語
大家一起加油!!!如若文章中有不足之處,請大家及時指出,在此鄭重感謝。
紙上得來終覺淺,絕知此事要躬行。
大家好,我是博主寧在春:主頁
一名喜歡文藝卻踏上編程這條道路的小青年。
希望:我們,待別日相見時,都已有所成。L
總結(jié)
以上是生活随笔為你收集整理的「后端小伙伴来学前端了」Vue中Props 实现组件通信TodoList案例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 「后端小伙伴来学前端了」Vue中Prop
- 下一篇: 「后端小伙伴来学前端了」Vue中利用全局