---
# System prepended metadata

title: "\U0001F3C5 Day 18 - props、emit"
tags: [TypeScript 直播班-2025]

---

# 🏅 Day 18 - props、emit

繼昨日的任務中完成和使用者驗證相關的功能後，接下來就要進入到待辦事項操作的功能實現啦~
關於實作方面，將以待辦事項新增、讀取和勾選三項為例來進行 TypeScript 結合 Vue 的練習，若同學有興趣的話也可以嘗試完成其它的功能來完善這個小應用！
這邊一樣附上 API 相關的連結：

1. API 網址：https://todolist-api.hexschool.io/
2. API 文件：https://todolist-api.hexschool.io/doc/

## props
`props` 是 Vue 裡面用於父元件向子元件傳遞資料的機制，在建立元件時可透過 `defineProps` 定義其資料結構。父元件可以在 template 中使用語法 `:prop="value"` 來傳入資料，而子元件只負責接收與呈現資料，而不能直接修改 `props` 的內容。
至於加上 TypeScript 的型別支援，可以利用類似 Generics 的方式來將型別給帶入進去，例如：

```html
<script>
const { name } = defineProps<{ name: string }>()
</script>
    
<template>
    <h2>{{ name }}</h2>
</template>
```

## emit
`emit` 在 Vue 當中則常用於子元件向父元件發送事件，讓父元件來執行的溝通機制。在沒有 TypeScript 的語法中一般會透過 `defineEmit` 和 `@事件名稱` 來進行事件的傳遞、觸發：

```javascript
// 子元件：Button
<script>
const emit = defineEmits(["event"])
</script>
    
<template>
    <button @click="emit('event', <資料>)">向上傳遞</button>
</template>

// 父元件
<script>
function handleEvent() {
    console.log("function by emit!")
}
</script>
    
<template>
    <Button @event="handleEvent" />
</template>
```

而加入 TypeScript 的語法支援後，則可以將 `emit` 的部分用下面的方式來定義其資料型別：

```tsx
const emit = defineEmits<{ event: [<payload>: <type>] }>();
```

或是參考[官方文件](https://zh-hk.vuejs.org/guide/typescript/composition-api.html#typing-component-emits)中也有不同形式的寫法~

## 實作時間！
在待辦事項功能的實現當中，我們會將元件拆分成 `TodoList.Vue` 和 `TodoItem.Vue`，請嘗試運用前面提到的 Vue Composition API 並結合 TypeScript 來完成新增、讀取和勾選事項的功能實作！

以下是兩個元件的部分範例，同學也可以依照自己的喜好自行設計~ 另外範例中有將一些和函式觸發相關的屬性挖掉了，需要根據設計的方法來正確的將所需要的屬性補上以觸發函式的互動~

大致上會需要完成的練習是：
    **1. 設計新增、讀取、勾選事項的 TodoList API 函式 (結合 [Day 16](https://hackmd.io/noBU7kqBQt2-oj_2BlnSxg) 的通用 apiFetch 方法)**
    **2. 運用 Vue Composition API 在 `TodlList.Vue` 和 `TodoItem.Vue` 中加入所需的 script 內容**
    **3. 在 template 的標籤中加入對應的屬性來完成元件之間的互動、資訊傳遞以及函式觸發**

### TodoList.Vue

```html
<script>
type Todo = {}
const todos = ref<Todo[]>([]);
    
</script>

<template>
    <section>
        <h2>待辦清單</h2>
        <button>讀取待辦</button>
        <ul>
            <TodoItem v-for="todo in todos" :key="todo.id" :todo="todo" />
        </ul>
        <div class="add-todo">
            <input type="text" placeholder="輸入待辦事項..." />
            <button>新增待辦</button>
        </div>
    </section>
</template>
```

### TodoItem.Vue

```html
<template>
    <li :data-id="todo.id">
        {{ todo.status ? '[O]' : '[ ]' }}
        {{ todo.content }}
    </li>
</template>
```

<!-- 參考範例：
type Todo = {
    id: string;
    createTime: number;
    content: string;
    status: boolean;
}

// TodoList.Vue
<script setup lang="ts">
const todos = ref<Todo[]>([]);
const newTodoContent = ref<HTMLInputElement | null>(null);

const getTodosData = async () => {
    const response = await getTodos();
    todos.value = response.data;
};

const handleAddTodo = async () => {
    const content = newTodoContent.value?.value.trim();
    if (!content) {
        alert("待辦事項不可為空");
        return;
    }

    const response = await addTodo({ content });
    newTodoContent.value!.value = "";
    if (response.status) {
        todos.value.push(response.newTodo);
    }
}

const handleToggleTodo = async (id: string) => {
    const response = await toggleTodo({ id });
    if (response.status) {
        const todo = todos.value.find(t => t.id === id);
        if (todo) {
            todo.status = !todo.status;
        }
    }
};
</script>

<template>
    <section>
        <h2>待辦清單</h2>
        <button @click="getTodosData">獲取待辦</button>
        <ul class="todos">
            <TodoItem v-for="todo in todos" :key="todo.id" :todo="todo" @toggle="handleToggleTodo" />
        </ul>
        <div class="add-todo">
            <input type="text" ref="newTodoContent" placeholder="輸入待辦事項..." />
            <button @click="handleAddTodo">新增</button>
        </div>
    </section>
</template>


// TodoItem.Vue
<script setup lang="ts">
defineProps<{ todo: Todo }>()
const emit = defineEmits<{
    toggle: [id: string]
}>();
</script>

<template>
    <li :data-id="todo.id" @click="emit('toggle', todo.id)">
        {{ todo.status ? '[O]' : '[ ]' }}
        {{ todo.content }}
    </li>
</template>
-->