基於SqlSugar的開發框架循序漸進介紹(11)-- 使用TypeScript和Vue3的Setup語法糖編寫頁面和元件的總結

2022-07-08 12:06:38

隨著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 lang="ts">  是在單檔案元件 (SFC) 中使用組合式 API 的編譯時語法糖。script-setup 弱化了vue模板式程式設計體驗,也使得程式碼更簡潔。

1、定義元件或者頁面名稱

由於組合式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="民族" />

2、data屬性定義

不管是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的屬性,才能設定或者存取它。

 

3、表單或者元件的ref參照

有時候,需要通過在頁面的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>(); //檢視表單參照

這樣我們就可以在程式碼中檢視它的對外公佈的方法資訊了。

  

4、元件prop屬性定義

在我們開發自定義元件的時候,我們往往需要定義很多父傳子的屬性,也叫作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>

 

5、Emits事件宣告

在元件裡面,我們丟擲事件,通過在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>

 

6、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>

 

7、Watch函數的使用

有時候,子元件需要監控自身某個值的變化,然後進行相關的處理,那麼對值進行監控就需要用到了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);
    });
  }
);

 

8、onMounted函數的使用

我們一般在 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;
});

9、自定義元件的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 + "");
  }
);

 

10、自定義引入Vue的API和元件

上面所有的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">語法中經常涉及到的一些常用的知識和程式碼案例了。