在上一篇實戰演練中,已經將項目搭建好,並介紹了 Router、Vuex 的基本用法
接下來會以一個 Todo List 項目來介紹實戰中如何使用 Composition API
一、輸入框與列表(按鍵別名 + computed 類型聲明 )
首先是輸入框,由於需要支持回車鍵提交,所以需要監聽 keydown 事件

如果是傳統的按鍵處理,需要在事件對象中根據 keyCode 來判斷按鍵
Vue 提供了一些常用的按鍵修飾符,不用在事件處理函數中再做判斷
比如這里就使用了 enter 修飾符,直接監聽 enter 鍵的 keydown 事件
列表部分,需要判斷當前列表是否為空,如果為空則展示空狀態

這里使用了 v-if 和 v-else 來做條件判斷,而其判斷條件 showList 是一個計算屬性 computed
在 TypeScript 的項目中,如果像 JS 項目一樣添加計算屬性,無法進行類型推斷

需要加上類型聲明:

// 在 setup() 中通過 computed() 注冊的計算屬性不需要聲明類型
二、添加、刪除條目(在 setup 中使用 vuex)
創建的條目需要保存到 store 中,首先需要定義條目類型

然后在 state 中新增 todoList 字段,用於保存列表

這里還添加了一個 todoListMap 字段,是 todoList 的字典項,后面查找條目的時候會用到
同時在 mutations 中新增添加和刪除方法

Store 已經調整好了,接下來只要在組件中調用即可
可以像之前介紹的那樣,使用 mapState 和 mapMutations 來導出對應的字段和方法
不過如果想在 setup 中使用 vuex,就需要用到 vuex 4 提供的 useStore 方法
import { useStore } from 'vuex'; export default { //...
setup() { const store = useStore(); console.log('store--->', store); return { ...store.state } } }

從截圖可以看到,useStore() 的返回值其實就是 $store
接下來的事情就簡單了,手動導出需要用到的 state 和 mutations、actions 即可

這種方式導出 state 還行,但對於 mutation 和 action,需要一個一個手動創建函數並導出,就比較笨重
沒關系,我們還有 mapMutations 和 mapActions 可以使用

但需要注意,不要在 setup 使用 mapState!
因為 mapState 導出 state 是一個函數(computed),這個函數內部使用了 this.$store
而 setup 中 this 是一個空值,所以在 setup 中使用 mapState 會報錯

如果確實希望以 mapState 的形式在 setup 中導出 state,可以看一下 vuex-composition-helpers
import { useState, useActions } from 'vuex-composition-helpers'; export default { props: { articleId: String }, setup(props) { const { fetch } = useActions(['fetch']); const { article, comments } = useState(['article', 'comments']); fetch(props.articleId); // dispatch the "fetch" action
return { // both are computed compositions for to the store
article, comments } } }
三、查看條目詳情(在 setup 中使用 router)
在條目詳情頁,可以在 url 上攜帶條目 id,然后通過 id 在 store 中找到對應的數據
這就需要調整路由配置文件 src/router/index.ts,配置 vue-router 中的動態路由

路由配置好了,接下來需要在列表上添加“查看詳情”按鈕的處理函數
如果這個函數寫在 methods 里面,可以直接通過 this.$router.push() 來跳轉頁面
但是在 setup 中,就需要用到 vue-router 提供的 useRouter
import { useRouter } from 'vue-router'; export default { // ...
setup() { const router = useRouter(); const viewItem = (id: string) => { router.push(`/about/${id}`); }; return { viewItem }; } }
然后在詳情頁,通過 useRoute(注意不是 userRouter )獲取 params

四、完全使用 Composition API 開發組件
以上都是在 setup 中使用 Composition API,整個組件本身依然是使用 Options API 開發
想象一下,如果整個組件的 <script> 部分就是一個 setup 函數,會發生什么呢?
給 <script> 標簽加上 setup 修飾符試試!
<script lang="ts" setup>
// ...
</script>
然后把整個 <script> 當做 setup 函數,改寫上面的詳情頁

這樣就能完全使用 Composition API 來開發組件了
和 setup 的區別在於,setup 最終需要 return 一個對象,而現在需要使用 export 來導出變量
如果需要使用 setup 函數的參數 ( props 和 context ),可以這么寫:
<script lang="ts" setup="props, { emit }">
等效於
setup(props, { emit }) { // ...
}
最后再貼一份 script-setup 的組件示例:
<script lang="ts" setup="props, { emit }"> import { defineComponent, computed, onMounted, ref, toRefs, } from 'vue'; const { modelValue, disabled } = toRefs(props); /* data */ export const currentValue = ref(''); export const isComposing = ref(false); /* methods */ export function handleInput() { if (isComposing.value) return; emit('update:modelValue', currentValue.value); } /* lifecycle */ onMounted(() => { currentValue.value = modelValue.value; }); /* computed */ export const inputDisabled = computed(() => disabled.value); export default defineComponent({ name: 'test-input', props: { modelValue: [String, Number], disabled: Boolean, }, emits: { 'update:modelValue': null, }, }); </script>
在這份代碼中,我還是按照組件選項的方式,將變量分類放到一起
其實更合理的方式是按功能分類,這樣更利於抽取邏輯
五、小結
Vuex 和 vue-router 都提供了可以在 setup 中獲取實例的方法
這也側面體現了 Vue 3 的 setup 是一個獨立的鈎子函數
它不會依賴於 Vue 組件實例,如果需要用到函數外部的變量,都需要從外部獲取
同時也提醒我們在開發 Vue 3 的插件的時候,一定要提供相應的函數讓開發者能在 setup 中使用
