前幾天已經了解關於state的存取及更新方式,還有從父層把state透過props的傳遞給子層的方式,今天就讓我們把重點放在怎麼讓頁面能擁有實際的互動功能,也就是「觸發state更新有關的事件綁定,以及在單向資料流概念下,該如何在子層改動父層傳遞的state」的部分。
看完props的部分和元件顯示相關設計的部分後,想讓頁面有實際上的互動效果,還必須綁定事件。不論是Vue還是React都需要透過綁定事件,來實現互動功能,差異只在於寫法上的不同。
<template>
<div>
<h2>Vue Event Binding</h2>
<p>count: {{ count }}</p>
<!-- 透過v-on(縮寫為@)綁定click事件 -->
<button @click="handleAddCount">Add</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
const handleAddCount = () => {
count.value ++;
}
</script>
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
const handleAddCount = () => {
setCount(count + 1);
};
return (
<div className="App">
<h2>React Event Binding</h2>
<p>count: {count}</p>
{/* 用onClick綁定Click事件 */}
<button onClick={handleAddCount}>Add</button>
</div>
);
}
export default App;
可以看到上述兩個範例雖然Vue和React在寫法上有點不太一樣,不過其實都是on+事件名稱的概念下去綁定event handler,所以其實在轉換上還算容易。緊接著來看一下另一個Vue和React在使用上比較不同的事件綁定情境!
前面提到的事件綁定,都是在自己的元件中,去改動自己元件中的state或是做一些動作,但是還有一種情境是「當我們想要從子元件改動父元件的props時」。如同之前在了解單向資料流時有提到過的「當畫面要改動state,需要靠事件進行」,當想從子元件往回改動父元件的state時,一樣也需要透過事件,但是在這裡的做法上,Vue和React就有明顯的不同,雖然本質還是「事件」為基準。
這裡一樣用一個相同的使用情境,來看看Vue和React的差異。
在子元件中定義emit,並且使用emit(事件名稱, 參數)的方式通知父元件更新。
// 子元件
<template>
<li class="todo-item">
<p :class="{'completed': todoItem.complete}">{{ todoItem.content }}</p>
<input type="checkbox" :checked="todoItem.complete" @change="toggleComplete(todoItem.id)">
</li>
</template>
<script setup>
defineProps({
todoItem: {
type: Object,
required: true,
}
});
// define要使用的emit
const emit = defineEmits(['toggle']);
const toggleComplete = (id) => {
// emit的事件,以及事件中要帶的參數
emit('toggle', id);
};
</script>
父元件則是需要綁定一個對應的事件名稱,來去接子元件提醒要進行的事件。
<template>
<div class="container">
<ul>
<!-- 在父元件中透過v-bind去接會透過emit通知父元件進行的event -->
<TodoItem v-for="todoItem in list" :key="todoItem.id" :todoItem="todoItem" @toggle="handleToggle"/>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
import TodoItem from './components/TodoItem.vue';
const list = ref([
{
id: 1,
content: '打掃家裡',
complete: false,
},
{
id: 2,
content: '英文學習',
complete: false,
},
]);
const handleToggle = (id) => {
list.value = list.value.map((item) => {
if (item.id === id) {
return {
...item,
complete: !item.complete,
}
} else {
return item;
}
});
};
</script>
在Vue的進行方式中,需要多做定義emit的步驟,但整體的進行方式依然是以事件觸發
為中心概念下去更動父元件的state。
子元件的部分是使用props進來的函式下去更新父元件的state
const TodoItem = ({ todoItem, toggleComplete }) => {
return (
<li className="todo-item">
<p className={todoItem.complete ? 'completed' : ''}>{todoItem.content}</p>
<input
type="checkbox"
checked={todoItem.complete}
// 使用透過props傳來的函式,當事件觸發會是由父層的函式去改動子層的函式,函式還可以帶上參數
onChange={() => toggleComplete(todoItem.id)}
/>
</li>
);
};
export default TodoItem;
父元件則須先宣告更新state的函式,並透過props帶到子元件中。
import { useState } from 'react';
import TodoItem from './TodoItem';
const App = () => {
const [list, setList] = useState([
{
id: 1,
content: '打掃家裡',
complete: false,
},
{
id: 2,
content: '英文學習',
complete: false,
},
]);
const handleToggle = (id) => {
setList((previousList) =>
previousList.map((item) => {
if (item.id === id) {
return {
...item,
complete: !item.complete,
};
} else {
return item;
}
})
);
};
return (
<div className="container">
<ul>
{list.map((todoItem) => (
<TodoItem
key={todoItem.id}
todoItem={todoItem}
// 透過props把改動元件內state的函式帶進子元件
toggleComplete={handleToggle}
/>
))}
</ul>
</div>
);
};
export default App;
React的進行方式雖然感覺像是從子元件中更新的父元件,但實際上子元件只是做了觸發事件
的動作,因為實際上進行state改動動作的函式是父元件的函式,所以進行更新動作的人還是父元件。
雖然Vue和React綁定事件的寫法不太一樣,從子層更新父層的寫法也不同,不過都還是透過「事件觸發」來通知
父層進行自己state的更新。今天的內容個人覺得是初期從Vue轉換到React時,還算好懂的部分,因為只要掌握一個關鍵「透過事件觸發」,就不難理解。仔細對比一下的話,可能還會覺得React的寫法比較好記,因為就是在寫我們原本就懂的JavaScript,不需要額外再去記emit的用法。那今天的內容就先到這裡了,明天會來看與規劃共用元件有關的一個很有幫助的用法。