基於 Vue3 和 TypeScript 項目大量實踐後的思考

概述

Vue3 出來已經有一段時間了,在團隊中,也進行了大量的業務實踐,也有了一些自己的思考。

總的來說,Vue3 無論是在底層的原理上,還是在業務的實際開發中,都有了長足的進步。

使用 proxy 代替之前的 Object.defineProperty 的 API,性能更加優異,也解決了之前 vue 在處理對象、數組上的缺陷;在 diff 算法上,使用了靜態標記的方式,大大提升了 Vue 的執行效率。

在使用的層面,我們從 options Api,變成了 composition Api,慢慢的在實際的業務中,我們拋棄了原本的 data、methods、computed 那種隔離式的寫法。compositon Api,它更加聚焦,它講究的是相關業務的聚合性。

同時,在 composition Api 中,爲了防止過於重的業務邏輯,它提供了一種關注點分離的方式,大大的提升了我們代碼的可讀性。

完全良好的支持了 TypeScript,類型校驗也成爲了以後 Vue3 進行大型項目開發的質量保障,同時這也是面向了趨勢 -- 前端的未來就是 TypeScript!

1、compositon Api

compositon Api 的本質,體現在代碼裏面,也就是一個 setup 函數,在這個 setup 函數中,返回的數據,會用到該組件的模板中。return 的這個對象,一定程度上,代表了之前 vue2 中的 data 屬性。

import { defineComponent, ref } from 'vue';
export default defineComponent({ 
    name: 'Gift',   
    setup() {     
        const counter = ref(0);    
        return {        
            counter     
        }  
    }
})

這時候,對於大多數初學者來說,可能存在的疑惑就是,那麼我能不能定義 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 依然那麼用。

1、ref 和 reactive 的區別?

在功能方面,ref 和 reactive,都是可以實現響應式數據!

在語法層面,兩個有差異。ref 定義的響應式數據需要用 [data].value 的方式進行更改數據;reactive 定義的數據需要[data].[prpoerty] 的方式更改數據。

const actTitle: Ref<string> = ref('活動名稱');

const actData = reactive({  
    list: [], 
    total: 0, 
    curentPage: 1,  
    pageSize: 10
});

actTitle.value = '活動名稱2';

actData.total = 100;

但是在應用的層面,還是有差異的,通常來說:單個的普通類型的數據,我們使用 ref 來定義響應式。表單場景中,描述一個表單的 key:value 這種對象的場景,使用 reactive;在一些場景下,某一個模塊的一組數據,通常也使用 reactive 的方式,定義數據。

那麼,對象是不是非要使用 reactive 來定義呢?其實不是的,都可以,根據自己的業務場景,具體問題具體分析!ref 他強調的是一個數據的 value 的更改,reactive 強調的是定義的對象的某一個屬性的更改。

2、週期函數

週期函數,在 Vue3 中,是被單獨使用的,使用方式如下:

import { defineComponent, ref, onMounted } from 'vue';
export default defineComponent({  
    name: 'Gift',  
    setup() {    
        const counter = ref(0);    
        onMounted(() ={     
            // 處理業務,一般進行數據請求      
        })  
        return {       
            counter     
        }  
    }
})

3、store 使用

在 Vue2 中,其實可以直接通過 this.$store 進行獲取,但是在 Vue3 中,其實沒有 this 這個概念,使用方式如下:

import { useStore } from "vuex";
import { defineComponent, ref, computed } from 'vue';
export default defineComponent({  
    name: 'Gift',  
    setup() {     
        const counter = ref(0);   
        const store = useStore();     
        const storeData = computed(() => store); // 配合computed,獲取store的值。      
        return {       
            counter,     
            storeData     
        }  
    }
})

4、router 的使用

在 Vue2 中,是通過 this.$router 的方式,進行路由的函數式編程,但是 Vue3 中,是這麼使用的:

import { useStore } from "vuex";
import { useRouter } from "vue-router";
import { defineComponent, ref, computed } from 'vue';
export default defineComponent({  
    name: 'Gift',  
    setup() {     
        const counter = ref(0);   
        const router = useRouter();     
        const onClick = () ={       
            router.push({ name: "AddGift" });      
        }   
        return {     
            counter,      
            onClick     
        }  
    }
})

2、關注點分離

關注點分離,應該分兩層意思:第一層意思就是,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();    
        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 
};
}

3、TypeScript 支持

這一部分內容,準確的來說,是 TS 的內容,不過它與 Vue3 項目開發,息息相關,所以真的想用 Vue3,我們還是得了解 TS 的使用。

不過這一部分,我不會介紹 TS 的基礎語法,主要是在業務場景中,如何組織 TS。

使用 TS 進行業務開發,一個核心的思維是,先關注數據結構,再根據數據結構進行頁面開發。以前的前端開發模式是,先寫頁面,後關注數據。

比如要寫一個禮品列表的頁面,我們可能要定義這麼一些 interface。總而言之,我們需要關注的是:頁面數據的 interface、接口返回的數據類型、接口的入參類型等等。

// 禮品創建、編輯、列表中的每一項,都會是這個數據類型。
interface IGiftItem { 
  id: string | number; 
  name: string; 
  desc: string; 
  [key: string]: any;
}

// 全局相應的類型定義
// 而且一般來說,我們不確認,接口返回的類型到底是什麼(可能是null、可能是對象、也可能是數組),所以使用範型來定義
interfaceinterface 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);
};

作者:mapbar_front

https://juejin.cn/post/7008063765585330207

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/5MYvwVikzwL5Bkm9eONSjQ