上一篇:飲冰三年-人工智慧-Vue-66 Vue元件化
很久以前我對Vue2的元件間資料互動做過學習,兜兜轉轉再用Vue已經是Vue3版本。
Vue3元件間資料互動
1、準備工作
環境準備
功能介紹
- 頭部元件(MyHeader)
主要是一個input框,用於收集使用者輸入內容,
當使用者輸入完成,並按下enter鍵後,將資料新增到 MyList 列表元件中
- 列表元件(MyList)
- 任務元件(MyItem)
- 底部元件(MyFooter)
2、props emit 版本
<template> <div class="todo-header"> <input type="text" placeholder="請輸入你的任務名稱,按Enter鍵確認" @keyup.enter="add" /> </div> </template> <script setup> // 引入nanoid import { nanoid } from "nanoid"; // 定義 emits const emit = defineEmits(['addTodo']) const add = (e) => { // 判斷使用者是否輸入了內容 if (e.target.value.trim().length === 0) { alert("輸入的內容不能為空"); return; } // 將使用者的輸入,包裝成為一個todo物件 const todoObj = { id: nanoid(), title: e.target.value, done: false, }; // 將todo物件傳遞給App元件 emit('addTodo', todoObj) // 清空使用者的輸入 e.target.value = ""; } </script> <style scoped> /*header*/ .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
原始碼解析
emit 是一個用於觸發事件的方法,允許子元件向父元件傳送訊息。這是 Vue 的一種元件通訊機制,通常用於子元件向父元件傳遞資料或通知父元件發生了某個事件。
作用和用法
-
事件傳遞: 當子元件需要通知父元件某個事件發生時,可以使用 emit 方法。例如,當使用者在輸入框中輸入任務並按下Enter鍵時,你想要將這個任務傳遞給父元件。
-
自定義事件: emit 允許你建立自定義事件,父元件可以透過監聽這些事件來響應子元件的行為。例如,在示例中,子元件使用 emit('addTodo', todoObj) 來觸發名為 addTodo 的事件,並將 todoObj 作為引數傳遞給父元件。
如何在父元件中監聽事件
-
在父元件中,你可以透過 @ 符號來監聽子元件發出的事件
-
<MyHeader @addTodo="addTodo"></MyHeader>
<template> <ul class="todo-main"> <!-- v-for 指令用於遍歷 todos 陣列,生成多個 MyItem 元件。 :todo="todoObj": 將當前的 todoObj 傳遞給 MyItem 元件的 todo prop。 :checkTodo="checkTodo" 和 :deleteTodo="deleteTodo": 將父元件的方法 checkTodo 和 deleteTodo 作為 prop 傳遞給 MyItem 元件,允許子元件在適當的時候呼叫這些方法 --> <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"> </MyItem> </ul> </template> <script setup> import MyItem from './MyItem.vue'; // defineProps 是一個 Vue 3 的編譯宏,用於在 <script setup> 中定義元件接收的 props。 const props = defineProps({ todos: Array, // todos: 一個陣列,用於儲存待辦事項列表。 checkTodo: Function, // checkTodo: 一個函式,用於處理勾選或取消勾選待辦事項的操作。 deleteTodo: Function // deleteTodo: 一個函式,用於處理刪除待辦事項的操作。 }); </script> <style scoped> /*main*/ .todo-main { 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; } </style>
原始碼解析
-
父元件透過 props 向子元件傳遞資料和方法。
作用和用法
- 父元件傳:父元件 APPVue 透過 : 符號繫結 todos、checkTodo 和 deleteTodo到子元件 MyList 元件。 Vue 會將這些資料和方法作為 props 傳遞給 MyList 元件。
<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList>
-
子元件接:在 MyList.vue 元件中,使用 defineProps 來定義和接收這些 props:
<template> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" /> <span>{{ todo.title }}</span> </label> <button class="btn btn-danger" @click="handleTodo(todo.id)">刪除</button> </li> </template> <script setup> // 接收 props const props = defineProps({ todo: Object, checkTodo: Function, deleteTodo: Function }) // 處理勾選事件 const handleCheck = (id) => { // 通知App元件,修改todo的done狀態 props.checkTodo(id) } // 處理刪除事件 const handleTodo = (id) => { if (window.confirm('確定刪除嗎?')) { props.deleteTodo(id) } } </script> <style scoped> /*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; } li:hover { background-color: #ddd; } li:hover button { display: block; } </style>
原始碼解析
有了上面的基礎,這個比較好理解,父元件透過 props 向子元件傳遞資料和方法。不過不同的是,子元件在這裡呼叫了props傳的方法。
// 處理勾選事件 const handleCheck = (id) => { // 通知App元件,修改todo的done狀態 props.checkTodo(id) } // 處理刪除事件 const handleTodo = (id) => { if (window.confirm('確定刪除嗎?')) { props.deleteTodo(id) } }
<template> <div class="todo-footer" v-show="total"> <label> <input type="checkbox" :checked="isAll" @change="checkAll" /> </label> <span> <span>已完成{{ doneTotal }}</span> / 全部{{ todos.length }} </span> <button class="btn btn-danger" @click="clearAll">清除已完成任務</button> </div> </template> <script setup> import { computed} from 'vue' // 接收 props const props = defineProps({ todos: { type: Array, required: true } }); // 定義 emits const emit = defineEmits(['checkAllTodo', 'clearAllTodo']); // 計算屬性 const total = computed(() => props.todos.length) const doneTotal = computed(() => { return props.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0); }) const isAll = computed(() => doneTotal.value === total.value && total.value > 0) const checkAll = (e) => { emit('checkAllTodo', e.target.checked); } const clearAll = () => { emit('clearAllTodo') } </script> <style scoped> /*footer*/ .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>
原始碼解析
-
定義 props:
-
props 接收一個 todos 陣列,表示所有的待辦事項。
-
defineProps的時候可以為屬性設定必填項
-
- 定義 emits:
-
-
emit 用於向父元件傳送事件。定義了兩個事件:checkAllTodo 和 clearAllTodo。
-
checkAll: 當核取方塊狀態改變時,觸發 checkAllTodo 事件,並傳遞核取方塊的勾選狀態。
-
clearAll: 當點選清除按鈕時,觸發 clearAllTodo 事件。
-
<script setup> import { ref, watch } from 'vue' import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList .vue' import MyFooter from './components/MyFooter.vue' // 初始化 todos const todos = ref(JSON.parse(localStorage.getItem('todos')) || []) // 新增todo const addTodo = (todo) => { todos.value.unshift(todo); }; // 勾選或者取消勾選一個todo const checkTodo = (id) => { todos.value.forEach((todo) => { if (todo.id === id) { todo.done = !todo.done; } }); }; // 刪除一個todo const deleteTodo = (id) => { todos.value = todos.value.filter((todo) => todo.id !== id); }; // 全選或者全不選 const checkAllTodo = (done) => { todos.value.forEach(todo => todo.done = done) }; // 清除所有已經完成的todo const clearAllTodo = () => { todos.value = todos.value.filter((todo) => !todo.done) } // 監聽 todos 的變化並同步到 localStorage watch( todos, (newValue) => { localStorage.setItem('todos', JSON.stringify(newValue)); }, { deep: true } ); </script> <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> <style scoped> .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; } .logo:hover { filter: drop-shadow(0 0 2em #646cffaa); } .logo.vue:hover { filter: drop-shadow(0 0 2em #42b883aa); } </style>
總結
2、全域性事件匯流排版本
安裝
npm install mitt
新增時間匯流排eventBus.js
import mitt from 'mitt'; const eventBus = mitt(); export default eventBus;
MyHeader、MyFooter 這兩個原始碼保持不變
<template> <ul class="todo-main"> <!-- eventBus02 <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"> --> <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"> </MyItem> </ul> </template> <script setup> import MyItem from './MyItem.vue'; // defineProps 是一個 Vue 3 的編譯宏,用於在 <script setup> 中定義元件接收的 props。 const props = defineProps({ todos: Array, // todos: 一個陣列,用於儲存待辦事項列表。 // checkTodo: Function, // eventBus02 註釋掉一些 // deleteTodo: Function // eventBus02 註釋掉一些 }); </script> <style scoped> /*main*/ .todo-main { 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; } </style>
原始碼解析
-
去掉props傳遞方式
-
不做中間商
<template> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" /> <span>{{ todo.title }}</span> </label> <button class="btn btn-danger" @click="handleTodo(todo.id)">刪除</button> </li> </template> <script setup> import eventBus from '../eventBus'; // eventBus01 匯入事件匯流排 // 接收 props const props = defineProps({ todo: Object, // checkTodo: Function, eventBus02 註釋掉一些 // deleteTodo: Function }) // 處理勾選事件 const handleCheck = (id) => { // eventBus03 通知父元件,修改 todo 的 done 狀態 eventBus.emit('checkTodo', id); } // 處理刪除事件 const handleTodo = (id) => { if (window.confirm('確定刪除嗎??')) { // eventBus04 通知父元件, eventBus.emit('deleteTodo', id); } } </script> <style scoped> /*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; } li:hover { background-color: #ddd; } li:hover button { display: block; } </style>
原始碼解析
-
// eventBus01 匯入事件匯流排
-
import eventBus from '../eventBus';
-
-
// eventBus02 註釋掉一些
-
// 接收 props const props = defineProps({ todo: Object, // checkTodo: Function, // deleteTodo: Function })
-
-
// eventBus 通知父元件
-
// 處理勾選事件 const handleCheck = (id) => { // eventBus03 通知父元件,修改 todo 的 done 狀態 eventBus.emit('checkTodo', id); } // 處理刪除事件 const handleTodo = (id) => { if (window.confirm('確定刪除嗎??')) { eventBus.emit('deleteTodo', id); } }
-
<script setup> import { ref, watch } from 'vue' import eventBus from './eventBus'; // eventBus01 匯入事件匯流排 import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList .vue' import MyFooter from './components/MyFooter.vue' // 初始化 todos const todos = ref(JSON.parse(localStorage.getItem('todos')) || []) // 新增todo const addTodo = (todo) => { todos.value.unshift(todo); }; // 勾選或者取消勾選一個todo const checkTodo = (id) => { todos.value.forEach((todo) => { if (todo.id === id) { todo.done = !todo.done; } }); }; // 刪除一個todo const deleteTodo = (id) => { todos.value = todos.value.filter((todo) => todo.id !== id); }; // 全選或者全不選 const checkAllTodo = (done) => { todos.value.forEach(todo => todo.done = done) }; // 清除所有已經完成的todo const clearAllTodo = () => { todos.value = todos.value.filter((todo) => !todo.done) } // 監聽 todos 的變化並同步到 localStorage watch( todos, (newValue) => { localStorage.setItem('todos', JSON.stringify(newValue)); }, { deep: true } ); // eventBus03 將方法註冊到事件匯流排上 eventBus.on('checkTodo', checkTodo); eventBus.on('deleteTodo', deleteTodo); </script> <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!-- 在父元件中,你可以透過 @ 符號來監聽子元件發出的事件 --> <MyHeader @addTodo="addTodo"></MyHeader> <!-- eventBus02 <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList> --> <MyList :todos="todos"></MyList> <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"></MyFooter> </div> </div> </div> </template> <style scoped> .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; } .logo:hover { filter: drop-shadow(0 0 2em #646cffaa); } .logo.vue:hover { filter: drop-shadow(0 0 2em #42b883aa); } </style>
原始碼解析
-
// eventBus01 匯入事件匯流排
-
import eventBus from './eventBus';
-
-
// eventBus02 修改原來props傳遞方式
-
<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList>
-
<MyList :todos="todos"></MyList>
-
-
// eventBus03 將方法註冊到事件匯流排上
-
eventBus.on('checkTodo', checkTodo); eventBus.on('deleteTodo', deleteTodo);
-