隨著Vue3和TypeScript的大浪潮不斷襲來,越來越多的Vue專案採用了TypeScript的語法來編寫程式碼,而Vue3的JS中的Setup語法糖也越來越廣泛的使用,給我們這些以前用弱型別的JS語法編寫Vue程式碼的人不少衝擊,不過隨著大量的學習和程式碼編寫,經歷過一段難熬的時間後,逐步適應了這種和之前差別不小的寫法和衝擊。本篇隨筆介紹總結了Vue3中一些常見的基於TypeScript的Setup語法與組合式 API的處理程式碼案例。
TypeScript(簡稱ts)是微軟推出的靜態型別的語言,相比於js,TypeScript擁有強型別、編譯器嚴謹的語法檢查、更加嚴苛的語法,TypeScript 是 JS型別的超集,並支援了泛型、型別、名稱空間、列舉等特性,彌補了 JS 在大型應用開發中的不足。TypeScript 是 JavaScript 的強型別版本,最終在瀏覽器中執行的仍然是 JavaScript,所以 TypeScript 並不依賴於瀏覽器的支援,也並不會帶來相容性問題。
基於TypeScript的Setup語法糖寫法越來越多,熟練使用的話,需要一個學習過程,另外ElementPlus控制元件也有了一些不同的變化,而且它的官方案例程式碼基本上採用了Setup語法糖的寫法來提供例子程式碼。
script-setup
弱化了vue模板式程式設計體驗,也使得程式碼更簡潔。由於組合式API的特殊性,元件裡面的各項內容可以分開進行定義,同時藉助一些輔助函數進行處理。如這裡定義元件或者頁面名稱,通過使用defineOptions進行宣告。
<script setup lang="ts"> import { reactive, ref, onMounted, watch, computed } from "vue"; defineOptions({ name: "MyDictdata" }); //定義元件或頁面名稱
如果是元件,通過這樣定義後,我們在頁面引入它的時候,就可以import這個名稱就可以了,如下程式碼所示。
// 自定義字典控制元件 import MyDictdata from "./src/my-dictdata.vue";
這樣我們在頁面中就可以和其他HTML標籤一樣使用這個元件了。
<my-dictdata v-model="editForm.nationality" type-name="民族" />
不管是Vue 頁面還是元件,我們都需要設定一些屬性資訊,並提供一些初始化值,以前這些在選項式程式碼中的時候,是在data塊中定義的,採用了<script setup lang="ts">語法後,任何在裡面定義的資訊,在當前頁面或者元件的模板裡面都是公開,可以存取的。
我們可以使用ref或者 reactive 來定義不同型別的,ref針對的是簡單型別,reactive 針對的是物件型別,它們底層的實現是一樣的,ref的引數增加了一個value的屬性。
let expandMore = ref(false); //是否展開更多條件 let list = ref([]); // 頁面列表資料 let listSelection = ref([]); // 選中記錄 let loading = ref(true); // 載入狀態 let sorting = ref(""); // 排序條件 // 分頁條件 let pageInfo = reactive({ pageIndex: 1, pageSize: 20, totalCount: 0 });
這些資訊可以在HTML頁面中直接參照使用即可。
<!--分頁部分 --> <div class="block" style="height: 70px"> <el-pagination background :current-page="pageInfo.pageIndex" :page-size="pageInfo.pageSize" :total="pageInfo.totalCount" :page-sizes="[10, 20, 30, 40]" layout="total, sizes, prev, pager, next,jumper" @size-change="sizeChange" @current-change="currentChange" /> </div>
不過記得,如果是在JS裡面參照物件,那麼記得加上.value的屬性,才能設定或者存取它。
有時候,需要通過在頁面的ref=「form」 來參照一些表單或者元件的名稱,那麼就需要初始化相關的型別的,如下程式碼所示。
const searchRef = ref<FormInstance>(); //表單參照
而這個需要引入對應的型別的。
import { FormInstance, FormRules } from "element-plus";
這樣我們在HTML模板中就可以使用它的名稱了。
而對於自定義元件的話,如果需要嚴謹型別的處理,一般也需要約束對應的型別,我們如果需要反射某個特定元件的型別,那麼也可以使用InstanceType的關鍵字來處理,如下程式碼所示。
<script lang="ts" setup> import { ref } from 'vue' import { ElTree } from 'element-plus' const treeRef = ref<InstanceType<typeof ElTree>>()
這樣在呼叫相關介面方法的時候,就有Typescript的只能提示,程式碼更加健壯了。
通過InstanceType這樣方式獲得的ref參照,會顯示元件很多公開的屬性和介面方法,如下圖所示。
我們也可以單獨定義一個型別,用來約束自定義元件的方法或者屬性,如下我們定義一個檢視型別元件,只有一個show方法。
我們在<script setup lang="ts">的頂部export一個介面定義,然後再在下面使用 defineExpose 暴露元件屬性和方法,這樣就可以在元件的參照的地方呼叫這些方法了。
<script setup lang="ts"> //元件的介面型別 export interface ExposeViewType { show(id?: string | number): Function; } //顯示視窗 const show = (id: string | number) => { if (!isNullOrUnDef(id)) { testuser.Get(id).then(data => { Object.assign(viewForm, data); isVisible.value = true; //顯示對話方塊 }); } }; //暴露元件屬性和方法 defineExpose({ show });
這樣我們在頁面中定義這個自定義元件的參照的時候,除了使用InstanceType之外,還可以使用自定義的型別宣告了。
<!--檢視詳細元件介面--> <view-data ref="viewRef" />
在<script setup lang="ts">裡面定義對應參照的型別。
const viewRef = ref<ExposeViewType | null>(); //檢視表單參照
這樣我們就可以在程式碼中檢視它的對外公佈的方法資訊了。
在我們開發自定義元件的時候,我們往往需要定義很多父傳子的屬性,也叫作prop屬性定義。
prop屬性定義,是通過defineProps函數進行處理的,這個defineProps()
宏函數支援從它的引數中推導型別,定義的程式碼如下所示。
<script setup lang="ts"> const props = defineProps<{ foo: string bar?: number }>() </script>
我們也可以將 prop 的型別移入一個單獨的介面中:
<script setup lang="ts"> interface Props { foo: string bar?: number } const props = defineProps<Props>() </script>
有時候,我們還需要給指定的prop屬性給定預設值,那麼也可以通過函數withDefaults一起進行處理即可。
如下面是我們指定模組定義的prop介面資訊和defineProps的處理程式碼。
<script setup lang="ts"> import { reactive, ref, onMounted, watch, computed} from vue"; //定義元件名稱 defineOptions({ name: "MyDictdata" }); //宣告Props的介面型別 interface Props { placeholder?: string; // 空白提示 typeName?: string; // 字典型別方式,從後端字典介面獲取資料 options?: Array<TreeNodeItem>; // 固定列表方式,直接繫結,專案包括id,label屬性 modelvalue?: string | number; // 接受外部v-model傳入的值 clearable?: boolean; // 是否可以清空 disabled?: boolean; // 是否禁用 multiple?: boolean; // 是否多選 } //使用預設值定義Props const props = withDefaults(defineProps<Props>(), { placeholder: "請選擇", typeName: "", options: () => { return []; }, clearable: true, disabled: false, multiple: false, modelValue: "" //對應自定義控制元件的v-model的值 });
這樣我們在使用的時候,就可以傳入給元件對應的prop名稱了。
<el-form-item label="民族" prop="nationality"> <my-dictdata v-model="editForm.nationality" type-name="民族" /> </el-form-item>
在元件裡面,我們丟擲事件,通過在Emits中進行宣告,再行使用。
宣告事件在setup語法裡面也是和其他宏函數一樣,如下程式碼所示。
// 宣告事件 const emit = defineEmits(['updateName'])
如果為了更強的指定事件的引數和返回值等資訊,我們也可以通過定義介面然後在宣告Emits的方式,如下程式碼所示。
//宣告控制元件事件 interface Emits { (e: "update:modelValue", value: string): void; (e: "change", value: string): void; } //定義控制元件事件 const emit = defineEmits<Emits>();
或者直接整合一起宣告。
// 基於型別的宣告 const emit = defineEmits<{ (e: 'change', id: number): void (e: 'update', value: string): void }>()
然後在元件的函數中觸發事件,通知父頁面即可。
function change(data) {
const obj = dictItems.value.find(item => {
return item.id + "" === data;
});
emit("change", obj);
}
這樣我們在頁面使用元件的時候,HTML模板中使用的元件程式碼裡面,可以獲得獲得對應的事件處理。
<el-form-item label="狀態" prop="state"> <my-dictdata v-model="searchForm.state" :options="Status" @change="change" /> </el-form-item>
Computed計算函數的使用
「computed」 是Vue中提供的一個計算屬性。它被混入到Vue範例中,所有的getter和setter的this上下文自動的繫結為Vue範例。
<script setup lang="ts"> import { computed, ref } from 'vue' const count = ref(1) // 通過computed獲得doubleCount const doubleCount = computed(() => { return count.value * 2 }) // 獲取 console.log(doubleCount.value) </script>
有時候,子元件需要監控自身某個值的變化,然後進行相關的處理,那麼對值進行監控就需要用到了watch函數。
// 監聽外部對props屬性變更,如通過ResetFields()方法重置值的時候 watch( () => props.modelValue, newValue => { console.log(newValue); emit("update:modelValue", newValue + ""); } ); watch( () => props.options, newValue => { newValue.forEach(item => { dictItems.value.push(item); }); } );
我們一般在 onMounted 的邏輯裡面準備好元件或者頁面顯示的內容,這裡面在頁面元件準備妥當後進行更新顯示。
//頁面初始化載入 onMounted(() => { getlist(); });
或者元件裡面
//掛載的時候初始化資料 onMounted(async () => { var typeName = props.typeName; var options = props.options; if (typeName && typeName !== "") { // 使用字典型別,從伺服器請求資料 await dictdata.GetTreeItemByDictType(typeName).then(list => { if (list) { list.forEach(item => { dictItems.value.push({ id: item.id, label: item.label }); }); } }); } else if (options && options.length > 0) { // 使用固定字典列表 options.map(item => { dictItems.value.push({ id: item.id, label: item.label }); }); } // 設定預設值 keyword.value = props.modelValue; });
一般元件在繫結值的時候,一般使用v-Model的屬性來設定它的值。
<el-form-item label="姓名" prop="name"> <el-input v-model="editForm.name" /> </el-form-item>
或者日期元件
<el-form-item label="出生日期" prop="birthDate"> <el-date-picker v-model="editForm.birthDate" align="right" type="date" placeholder="選擇日期" format="YYYY-MM-DD" /> </el-form-item>
因此我們自定義開發的元件,也應該採用這樣約定的屬性。這裡面的v-Model對應的prop屬性就是modelValue的,因此我們需要定義這個屬性,並處理Emits事件就可以了。
//宣告Props的介面型別 interface Props { modelvalue?: string | number; // 接受外部v-model傳入的值 } //使用預設值定義Props const props = withDefaults(defineProps<Props>(), { modelValue: "" //對應自定義控制元件的v-model的值 });
然後宣告元件的事件,在元件內部合適的地方觸發即可。
//宣告元件事件 interface Emits { (e: "update:modelValue", value: string): void; (e: "change", value: string): void; } //定義元件事件 const emit = defineEmits<Emits>();
並在Watch監控它的變化,觸發元件的自定義事件
watch( () => props.modelValue, newValue => { console.log(newValue); emit("update:modelValue", newValue + ""); } );
上面所有的setup語法糖程式碼裡面,我們在開始的時候,往往都需要引入ref,reactive等API,如下程式碼所示。
<script setup lang="ts"> import { reactive, ref, onMounted, watch, computed } from "vue";
那麼每次引入局的麻煩的話,可以通過使用https://github.com/antfu/unplugin-auto-import 這個外掛來實現自動引入這些設定資訊,這樣每次就可以省卻一些定義程式碼了。
這樣在使用ref,reactive的時候,不用引入就直接使用,如下程式碼所示。
const count = ref(0)
const doubled = computed(() => count.value * 2)
安裝元件,直接通過下面npm 或者pnmp進行安裝即可。
npm i -D unplugin-auto-import
它提供了Vite、WebPack等編譯器的整合,可以參考官網進行修改。
如Vite的設定處理如下所示。
// vite.config.ts import AutoImport from 'unplugin-auto-import/vite' export default defineConfig({ plugins: [ AutoImport({ /* options */ }), ], })
然後對Typescript和ESLint進行修改設定一下就可以一勞永逸了(具體參考官網的說明),希望下個版本的vue能自動不用引入這些API就好了。
以上就是我們在<script setup lang="ts">語法中經常涉及到的一些常用的知識和程式碼案例了。