您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)Vue3中TypeScript 如何使用,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
根據(jù)設(shè)計,type 字段應(yīng)該是一個枚舉值,不應(yīng)該由調(diào)用方隨意設(shè)置。
下面是 Type 的枚舉聲明,共有 6 個字段。
enum Type { primary = "primary", success = "success", warning = "warning", warn = "warn", // warning alias danger = "danger", info = "info", }
TypeScript 中聲明類型的關(guān)鍵字有兩個,interface 和 type,在聲明 key 不確定類型的字段時稍有不同。
使用 type 進(jìn)行聲明:
type ColorConfig = { [key in Type]: Colors; };
使用 interface 卻只能像下面這樣:
interface ColorConfig { [key: string]: Colors; }
因為 interface 的索引只能是基礎(chǔ)類型,類型別名也不可以。而 type 的索引可以是復(fù)合類型。
在 vue3 中,組件的邏輯可以放在 setup 函數(shù)里面,但是 setup 中不再有 this,所以 vue2 中的 this.$refs 的用法在 vue3 中無法使用。
新的用法是:
給元素添加 ref 屬性。
在 setup 中聲明與元素 ref 同名的變量。
在 setup 的 return 對象中將 ref 變量作為同名屬性返回。
在 onMounted 生命周期中訪問 ref 變量,既是元素實例。
第一步:
<div class="point point-flicker" ref="point"></div>
第二步:
const point = ref<HTMLDivElement | null>(null);
注意類型要填寫 HTMLDivElement,這樣才能享受類型推斷。
第三步:
return { point };
這一步必不可少,如果返回對象中不包含這個同名屬性,onMounted 中訪問的 ref 對象會是 null。
第四步:
onMounted(() => { if (point?.value) { // logic } });
JavaScript 無法獲取到偽類元素,但是可以換一種思路。偽類樣式引用 css 變量,再通過 js 控制 css 變量來完成間接操作偽類的效果。
比如這是一個偽類:
.point-flicker:after { background-color: var(--afterBg); }
它依賴了 afterBg 變量。
如果需要修改它的內(nèi)容,只需要使用 js 操作 afterBg 的內(nèi)容即可。
point.value.style.setProperty("--bg", colorConfig[props.type].bg);
有一種不是很常見的情況,需要組件修改父組件傳遞給自己的 Props。
比如抽屜組件、擬態(tài)框組件等。
在 vue2 中常見的用法是 sync 和 v-model。
vue3 中只推薦使用 v-model:xxx="" 的方式。
比如父組件傳遞:
<ws-log v-model="wsLogVisible" />
子組件:
<template> <div v-model:visible="visible"> ... </div> </template> <script> // ... props: { visible: { type: Boolean, }, }, </script>
watch 變得更加簡單。
import { watch } from "vue"; watch(source, (currentValue, oldValue) => { // logic });
當(dāng) source 變化時自動執(zhí)行 watch 第二個參數(shù)所傳入的函數(shù)。
computed 也變得更加簡單。
import { computed } from "vue" const v = computed(() => { return x });
computed 返回的變量是一個響應(yīng)式對象。
這是一種開發(fā)組件的技巧。
假設(shè)你有一個不確定深度的樹狀結(jié)構(gòu)數(shù)據(jù)。
{ "label": "root", "children": [ { "label": "a", "children": [ { "label": "a1", "children": [] }, { "label": "a2", "children": [] } ] } ] }
它的類型定義如下:
export interface Menu { id: string; label: string; children: Menu | null; }
你需要實現(xiàn)一種樹狀組件來渲染它們。這時就需要用到這種技巧。
<template> <div>{{ menu.label }}</div> <Menu @select="select" v-for="item in menu.children" :key="item.id" :menu="item" /> </template> <script lang="ts"> import { defineComponent } from "vue"; export default defineComponent({ name: "Menu", props: { menu: { type: Object, }, }, }); </script>
組件的 name 可以在自身中直接使用,而不需要在 component 中聲明。
在 Vuex 中,我設(shè)計了一個數(shù)據(jù)結(jié)構(gòu)用于存儲模塊(業(yè)務(wù)概念)不同的狀態(tài)。
type Code = number; export type ModuleState = Map<Code, StateProperty>;
但是我發(fā)現(xiàn)一個問題,當(dāng)我修改 Map 中某一個 value 中的屬性時,不會觸發(fā) Vuex 的監(jiān)聽。
所以我只好將數(shù)據(jù)結(jié)構(gòu)修改為對象的形式。
export type ModuleState = { [key in Code]: StateProperty };
ts 中索引不可以使用類型別名,但是可以寫成下面這樣:
type Code = number; export type ModuleState = { [key in Code]: StateProperty };
除此之外,Map 還存在另外一個問題。
當(dāng)一個 Map 類型的 Proxy 對象作為參數(shù)被傳遞時,是無法使用 get、set、clear 等 Map 方法的,但是 TypeScript 會提示這些方法可用。如果使用了這些方法,會得到一個 Uncaught TypeError。
如果使用 Object 則不會產(chǎn)生這個問題。
ws 的異常只能在 onerror 和 onclose 兩個事件中進(jìn)行處理,try catch 是無法捕獲的。
有些時候,onerror 和 onclose 會連續(xù)執(zhí)行,比如觸發(fā) onerror,導(dǎo)致連接關(guān)閉,就會緊接著觸發(fā) onclose。
vue devtools 目前無法支持 Vue3,但是 vue devtools 幾乎是開發(fā)中必不可少的工具,目前可以使用 vue devtools beta 版本,但存在一些 Bug。
下載地址:
https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbhhppjdbg?utm_source=chrome-ntp-icon
用法非常簡單,安裝后重啟瀏覽器就可以。不需要設(shè)置 vue.config.devtools = true,在 vue3 中 vue.config 實例不存在 devtools 屬性。
在使用 vite 啟動服務(wù)的同時安裝依賴,非常容易碰到一個錯誤。
Error: EBUSY: resource busy or locked, open 'E:\gxt\property-relay-fed\node_modules\esbuild\esbuild.exe'
這個問題的原因是 vite 依賴的編譯工具 esbuild.exe 被占用所導(dǎo)致的,解決方法很簡單,就是停掉 vite,安裝完依賴后再重新啟動 vite。
系統(tǒng)中有一些移動頁面,需要嵌入在 App 中使用。
常見的調(diào)試 WebView 的方法有兩種,一種簡單的方式是使用騰訊開源的 vcosnole,另一種麻煩一些的調(diào)試方式是使用 Chrome 的 DevTools。
但是 vconsole 并沒有想象中那么好用。
image.png
所以我選擇使用 Chrome 調(diào)試,chrome://inspect/#devices
但是在調(diào)試過程中我發(fā)現(xiàn) Chrome 調(diào)試工具里面竟然運行的是 TS 源碼,TS 的語法直接被認(rèn)為語法錯誤。(我是使用 Vite 啟動的開發(fā)服務(wù)。)
解決方案很簡單,但挺 Low。先使用 vite build 把 TS 代碼編譯成 JS,再使用 vite preview 啟動服務(wù)。
websocket 和 Vue3 沒什么關(guān)系,但是在這里簡單提一下。
設(shè)備管理系統(tǒng)的核心概念是設(shè)備,設(shè)備會有很多屬性,在硬件上也被稱作數(shù)據(jù)點。這些屬性會經(jīng)歷非常長的鏈路傳輸?shù)接脩艚缑嫔?。整體流程大概是:硬件通過 tcp 協(xié)議上傳到接入網(wǎng)關(guān),接入網(wǎng)關(guān)處理后再通過 mqtt 協(xié)議上傳到物聯(lián)網(wǎng)平臺,物聯(lián)網(wǎng)平臺再經(jīng)過規(guī)則引擎處理,通過 webhook restful 的形式發(fā)送到業(yè)務(wù)系統(tǒng),業(yè)務(wù)系統(tǒng)再通過 websocket 推送到前端。
雖然數(shù)據(jù)通過層層編解碼、不同的協(xié)議繞了非常遠(yuǎn)的距離呈現(xiàn)到用戶面前,但是前端只需要關(guān)心 websocket 就足夠了。
在做重連時,需要注意 onerror 和 onclose 連續(xù)執(zhí)行的問題,通常是使用類似防抖的方法來解決。
我的做法是增加一個變量來控制重連次數(shù)。
let connecting = false; // 斷開連接后,先觸發(fā) onerror,再觸發(fā) onclose,主要用于防止重復(fù)觸發(fā)
conn(); function conn() { connecting = false; if (ctx.state.stateWS.instance && ctx.state.stateWS.instance.close) { ctx.state.stateWS.instance.close(); } const url = ctx.state.stateWS.url + "?Authorization=" + getAuthtication(); ctx.state.stateWS.instance = new WebSocket(url); ctx.state.stateWS.instance.onopen = () => { ctx.commit(ActionType.SUCCESS); }; ctx.state.stateWS.instance.onclose = () => { if (connecting) return; ctx.commit(ActionType.CLOSE); setTimeout(() => { conn(); }, 10 * 1000); connecting = true; }; ctx.state.stateWS.instance.onerror = () => { if (connecting) return; ctx.commit(ActionType.ERROR); setTimeout(() => { conn(); }, 10 * 1000); connecting = true; }; ctx.state.stateWS.instance.onmessage = function ( this: WebSocket, ev: MessageEvent ) { // logic } catch (e) { console.log("e:", e); } }; }
系統(tǒng)是設(shè)計成 7*24 小時不間斷運行。所以 websocket 很容易受到一些網(wǎng)絡(luò)因素或者其它因素的影響發(fā)生斷開,重連是一項非常重要的功能,同時還應(yīng)該具備重連日志功能。
在用戶的不同環(huán)境中,排查 WebSocket 的連接狀態(tài)很麻煩,添加一個連接日志功能是比較不錯的方案,這樣可以很好的看到不同時間的連接情況。
image.png
需要注意,這些日志是存儲在用戶的瀏覽器內(nèi)存中的,需要設(shè)置上限,到達(dá)上限要自動清除早期日志。
websocket 的鑒權(quán)是很多人容易忽視的一個點。
我在系統(tǒng)設(shè)計中,restful API 的鑒權(quán)是通過在 request header 上附帶 Authorization 字段,設(shè)置生成的 JWT 來實現(xiàn)的。
websocket 無法設(shè)置 header,但是可以設(shè)置 query,實現(xiàn)思路類似 restful 的認(rèn)證設(shè)計。
關(guān)于 ws 鑒權(quán)的過期、續(xù)期、權(quán)限等問題,和 restful 保持一致即可。
script setup 至今仍是一個實驗性特性,但它確實非常清爽。
單文件組件的 setup 常規(guī)用法像下面這樣:
<script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({ setup () { return {} } }) </script>
使用 script setup 后,代碼變成了下面這樣:
<script setup lang="ts"> </script>
在 sciprt 標(biāo)簽中的頂層變量、函數(shù)都會 return 出去。
在這種模式下,減少了大量代碼,可以提高開發(fā)效率、降低心智負(fù)擔(dān)。
但這時也存在幾個問題,比如在 script setup 中怎么使用生命周期和 watch/computed 函數(shù)?怎么使用組件?怎么獲取 props 和 context?
直接導(dǎo)入組件后,vue 會自動識別,無需使用 component 掛載。
<script setup lang="ts"> import C from "component" </script>
和標(biāo)準(zhǔn)寫法基本無差異。
<script setup lang="ts"> import { watch, computed, onMounted } from "vue" </script>
由于 setup 被提升到 script 標(biāo)簽上了,自然也就沒辦法接收 props 和 context 這兩個參數(shù)。
所以 vue 提供了 defineProps、defineEmit、useContext 函數(shù)。
defineProps
defineProps 的用法和 OptionsAPI 中的 props 用法幾乎一致。
<script setup lang="ts"> import { defineProps } from "vue"; interface Props { moduleID: string; } const props = defineProps<Props>(["moduleID"]); console.log(props.moduleID); </script>
defineEmit
defineEmit 的用法和 OptionsAPI 中的 emit 用法也幾乎一致。
<script setup lang="ts"> import { defineEmit } from "vue"; const emit = defineEmit(["select"]); console.log(emit("select")); </script>
emit 的第一個參數(shù)是事件名稱,后面支持傳遞不定個數(shù)的參數(shù)。
useContext
useContext 是一個 hook 函數(shù),返回 context 對象。
const ctx = useContext()
原理相當(dāng)簡單。增加了一層編譯過程,將 script setup 編譯成標(biāo)準(zhǔn)模式的代碼。
但是實現(xiàn)上有非常多的細(xì)節(jié),所以導(dǎo)致至今仍未推出正式版。
這套技術(shù)棧帶給我最深的感受還是開發(fā)方式上的變化。
在 Vue2 的開發(fā)中,Options API 在面對業(yè)務(wù)邏輯復(fù)雜的頁面時非常吃力。當(dāng)邏輯長達(dá)千行時,追蹤一個變量的變化是一件非常頭痛的事情。
但是有了 Composition API 后,這將不再是問題,它帶來了一種全新的開發(fā)方式,雖然有種 React 的感覺,但這相比之前已經(jīng)非常棒了!
這項目中所有的頁面,我都使用 hooks 的方式開發(fā)。
在設(shè)備模塊中,我的 js 代碼是這樣的。
<script lang="ts"> import { defineComponent, toRefs } from "vue"; import { useDeviceCreate } from "./create"; import { useDeviceQuery } from "./query"; import { useDeviceDelete } from "./delete"; import { useUnbind } from "./unbind"; import { useBind } from "./bind"; import { useDeviceEdit } from "./edit"; import { useState } from "./state"; import { useAssign } from "./assign"; export default defineComponent({ setup() { const queryObj = useDeviceQuery(); const { query, devices } = queryObj; const reload = query; return { ...toRefs(useDeviceCreate(reload)), ...toRefs(queryObj), ...toRefs(useDeviceDelete(reload)), ...toRefs(useUnbind(reload)), ...toRefs(useBind(reload)), ...toRefs(useDeviceEdit(reload)), ...toRefs(useState(devices)), ...toRefs(useAssign()), }; }, }); </script>
每個模塊各司其職,各自有自己的內(nèi)部數(shù)據(jù),各個模塊如果需要共享數(shù)據(jù),可以通過 Vuex,或者在頂層組件的 setup 中傳遞,比如上面的 reload 函數(shù)。
我的目錄結(jié)構(gòu)是這樣的。
看完上述內(nèi)容,你們對Vue3中TypeScript 如何使用有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。