一、composition Api
compositon Api的本質體現在代碼里面,也就是一個setup函數,在這個setup函數中,返回的數據,會用到該組件的模板中。return的這個對象,一定程度上,代表了之前vue2中的data屬性。
這時候,對於大多數初學者來說,可能存在的疑惑就是,那么我能不能定義options Api的寫法,比如data、computed、watch、methods等等。這里我需要明確的是,Vue3是完全兼容Vue2的這種options Api的寫法,但是從理念上來說,更加推薦setup的方式,來寫我們的組件。
原因如下:Vue3的存在,本身是為了解決Vue2的問題的,Vue2的問題就是在於,聚合性不足,會導致代碼越來越臃腫!setup的方式,能夠讓data、方法邏輯、依賴關系等聚合在一塊,更方便維護。
也就是說,以后我們盡量不要寫單獨的data、computed、watch、methods等等,不是Vue3不支持,而是和Vue3的理念違背。
components屬性,也就是一個組件的子組件,這個配置在Vue2和3的差異不大,Vue2怎么用,Vue3依然那么用。
缺點:從options api
切換到composition api
最大的問題就是沒有強制的代碼分區,如果書寫的人沒有很好的代碼習慣,那么后續的人將會看的十分難受。所以在寫代碼時需要注意做到很好的“關注點分離”:
1、自我代碼分區並且盡量抽離方法(寫好注釋),分區如下:
(1)相關引入
(2)響應式數據、props、emit 定義
(3)生命周期以及 watch 書寫
(4)方法定義
(5)方法、屬性暴露
2、組件抽離:將頁面拆成兩個文件夾,一個為 views
,一個為 components
。
views 和 components 文件夾下有各自的文件。views 文件夾中為頁面入口,掌管數據,而 components 則為頁面中一些組件抽離。如果是公共組件,再抽離到 components 文件夾下其他位置。
3、hook 抽離:盡可能將邏輯抽離,並不一定要進行復用。
二、關注點分離
關注點分離,應該分兩層意思:第一層意思就是,Vue3的setup,本身就把相關的數據,處理邏輯放到一起,這就是一種關注點的聚合,更方便我們看業務代碼。
第二層意思,就是當setup變的更大的時候,我們可以在setup內部,提取相關的一塊業務,做到第二層的關注點分離。
import { useStore } from "vuex"; import { useRouter } from "vue-router"; import { defineComponent, ref, computed } from 'vue'; import useMerchantList from './merchant.js'; export default defineComponent({ name: 'Gift', setup() { const counter = ref(0); const router = useRouter(); // router 的使用 const onClick = () => { router.push({ name: "AddGift" }); } // 在該示例中,我們把獲取商家列表的相關業務分離出去。也就是下面的merchant.ts
const {merchantList} = useMerchantList(); return { counter, onClick, merchantList } } }) // merchant.ts import { getMerchantlist } from "@/api/rights/gift"; import { ref, onMounted } from "vue"; export default function useMerchantList(): Record<string, any> { const merchantList = ref([]); const fetchMerchantList = async () => { let res = await getMerchantlist({}); merchantList.value = res?.data?.child; }; onMounted(fetchMerchantList); // 周期函數的使用 return { merchantList }; }
三、TypeScript支持
TS與Vue3項目開發息息相關,所以真的想用Vue3,我們還是得了解TS的使用。下面我們主要了解在業務場景中如何組織TS。
使用TS進行業務開發,一個核心的思維是,先關注數據結構,再根據數據結構進行頁面開發。以前的前端開發模式是,先寫頁面,后關注數據。
比如要寫一個禮品列表的頁面,我們可能要定義這么一些interface。總而言之,我們需要關注的是:頁面數據的interface、接口返回的數據類型、接口的入參類型等等。
// 禮品創建、編輯、列表中的每一項,都會是這個數據類型。
interface IGiftItem { id: string | number; name: string; desc: string; [key: string]: any; } // 全局相應的類型定義 // 而且一般來說,我們不確認,接口返回的類型到底是什么(可能是null、可能是對象、也可能是數組)
所以使用范型來定義interface
interface IRes<T> { code: number; msg: string; data: T } // 接口返回數據類型定義
interface IGiftInfo { list: Array<IGiftItem>; pageNum: number; pageSize: number; total: number; }
在一個常見的接口請求中,我們一般使用TS這么定義一個數據請求,數據請求的req類型,數據請求的res類型
export const getGiftlist = ( params: Record<string, any> ): Promise<IRes<IGiftInfo>> => { return Http.get("/apis/gift/list", params); };
目錄結構:在你編寫大型前端項目時,推薦使用聲明文件(Declaration Files)來管理接口或其他自定義類型。聲明文件一般是 <module_name>.d.ts
的形式,在這類文件中只定義模塊中的類型,沒有任何實際的實現邏輯。聲明文件可以單獨放在一個目錄里,我喜歡命名為 interfaces
,意思就是接口。這樣,就可以充分將抽象類型、方法、屬性等與實際內容分開。以下例子是一個集成了 TS 的 Vue 項目目錄。
. ├── babel.config.js // Babel 編譯配置文件
├── jest.config.ts // 單元測試配置文件
├── package.json // 項目配置文件
├── public // 公共資源
├── src // 源代碼目錄
│ ├── App.vue // 主應用
│ ├── assets // 靜態資源
│ ├── components // 組件
│ ├── constants // 常量
│ ├── i18n // 國際化
│ ├── interfaces // 聲明文件目錄
│ │ ├── components // 組件聲明
│ │ ├── index.d.ts // 主聲明
│ │ ├── layout // 布局聲明
│ │ ├── store // 狀態管理聲明
│ │ └── views // 頁面聲明
│ ├── layouts // 布局
│ ├── main.ts // 主入口
│ ├── router // 路由
│ ├── shims-vue.d.ts // 兼容 Vue 聲明文件
│ ├── store // 狀態管理
│ ├── styles // CSS/SCSS 樣式
│ ├── test // 測試
│ ├── utils // 公共方法
│ └── views // 頁面
└── tsconfig.json // TS 配置文件
其中可以看到,interfaces
這個目錄跟其他模塊在同一級,而其子目錄則是其他模塊所對應的類型聲明。編寫代碼前,盡量先創建並設計聲明文件內容,設計好之后再到實際的模塊下完成實現。當然,這個 “定義 -> 實現“ 是一個不斷迭代的過程,可能實現過程中發現有類型設計問題,可以回到聲明文件中完善定義,再到實現代碼里進行優化。