<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從實際項目出發(fā),告訴你 vue3 到底香不香

          共 13239字,需瀏覽 27分鐘

           ·

          2021-06-13 12:27

          微信搜索逆鋒起筆關注后回復編程pdf
          領取編程大佬們所推薦的 23 種編程資料!

          來源:代碼與野獸

          https://juejin.cn/post/6950487211368251399

          背景

          近期在研發(fā)一套物聯(lián)網(wǎng)設備管理系統(tǒng),其主要用途是將公司旗下所負責智能園區(qū)中的硬件設備通過物聯(lián)網(wǎng)云平臺來進行綜合管控。

          由于這個產(chǎn)品是實驗性項目,沒有合同,沒有明確收益。所以能夠拿到的資源非常少。

          產(chǎn)品具體的負責人,只有 1.5 人,幾乎只有我自己。所以既要擔任產(chǎn)品經(jīng)理,又要擔任開發(fā)者,還要擔任運維。不過從技術角度而言,選型可以更加自由。

          整個系統(tǒng)在架構上設計分為 4 層。自底向上分別是設備硬件、設備接入網(wǎng)關、物聯(lián)網(wǎng)平臺、設備管理系統(tǒng)。除去設備硬件,其它 3 層都屬于軟件范疇。

          這篇文章主要記錄一下我在開發(fā)最后一層-設備管理系統(tǒng)的前端開發(fā)過程中的一些總結。

          前端采用 Vite2.x、Vue3.x、Vuex4.x、VueRouter4.x、TypeScript、Element-Plus 進行開發(fā)??梢钥吹?,這些框架和庫所采用的版本是比較激進的,大部分都是最新版本,以及 rc 和 beta 版本。不過從項目開始到寫這篇總結,其中的一些庫的版本已經(jīng)不是最新的了,不得不感慨前端技術變化之快。

          一個組件的思考

          首先來看一個組件。

          水波紋.gif

          這是一個具有波紋效果、用來表示當前 websocket 連接狀態(tài)的小圓點。是一個非常簡單的純展示組件。樣式效果使用 css3 變量、動畫、和 before、after 偽類實現(xiàn)。

          props 設計非常簡單,只有一個 type 字段。根據(jù) type 字段的不同,波紋的顏色也不同。

          思路有了,下面是實現(xiàn)上的一些細節(jié)性問題。

          如何聲明字段名為枚舉的類型?

          根據(jù)設計,type 字段應該是一個枚舉值,不應該由調(diào)用方隨意設置。

          下面是 Type 的枚舉聲明,共有 6 個字段。

          enum Type {
            primary = "primary",
            success = "success",
            warning = "warning",
            warn = "warn"// warning alias
            danger = "danger",
            info = "info",
          }
          復制代碼

          TypeScript 中聲明類型的關鍵字有兩個,interface 和 type,在聲明 key 不確定類型的字段時稍有不同。

          使用 type 進行聲明:

          type ColorConfig = {
            [key in Type]: Colors;
          };
          復制代碼

          使用 interface 卻只能像下面這樣:

          interface ColorConfig {
            [key: string]: Colors;
          }
          復制代碼

          因為 interface 的索引只能是基礎類型,類型別名也不可以。而 type 的索引可以是復合類型。

          Vue 3 如何獲取元素實例?

          在 vue3 中,組件的邏輯可以放在 setup 函數(shù)里面,但是 setup 中不再有 this,所以 vue2 中的 this.$refs 的用法在 vue3 中無法使用。

          新的用法是:

          1. 給元素添加 ref 屬性。
          2. 在 setup 中聲明與元素 ref 同名的變量。
          3. 在 setup 的 return 對象中將 ref 變量作為同名屬性返回。
          4. 在 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-colorvar(--afterBg);
          }
          復制代碼

          它依賴了 afterBg 變量。

          如果需要修改它的內(nèi)容,只需要使用 js 操作 afterBg 的內(nèi)容即可。

          point.value.style.setProperty("--bg", colorConfig[props.type].bg);
          復制代碼

          API 的變化

          Vue3 中組件如何修改自身的 props?

          有一種不是很常見的情況,需要組件修改父組件傳遞給自己的 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>
          復制代碼

          Vue3 中 watch 用法的變化

          watch 變得更加簡單。

          import { watch } from "vue";

          watch(source, (currentValue, oldValue) => {
              // logic
          });
          復制代碼

          當 source 變化時自動執(zhí)行 watch 第二個參數(shù)所傳入的函數(shù)。

          Vue3 中 computed 用法的變化

          computed 也變得更加簡單。

          import { computed } from "vue"

          const v = computed(() => {
              return x
          });
          復制代碼

          computed 返回的變量是一個響應式對象。

          Vue3 中組件循環(huán)自身的技巧

          這是一種開發(fā)組件的技巧。

          假設你有一個不確定深度的樹狀結構數(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:慎用 Map

          在 Vuex 中,我設計了一個數(shù)據(jù)結構用于存儲模塊(業(yè)務概念)不同的狀態(tài)。

          type Code = number;
          export type ModuleState = Map<Code, StateProperty>;
          復制代碼

          但是我發(fā)現(xiàn)一個問題,當我修改 Map 中某一個 value 中的屬性時,不會觸發(fā) Vuex 的監(jiān)聽。

          所以我只好將數(shù)據(jù)結構修改為對象的形式。

          export type ModuleState = { [key in Code]: StateProperty };
          復制代碼

          ts 中索引不可以使用類型別名,但是可以寫成下面這樣:

          type Code = number;
          export type ModuleState = { [key in Code]: StateProperty };
          復制代碼

          除此之外,Map 還存在另外一個問題。

          當一個 Map 類型的 Proxy 對象作為參數(shù)被傳遞時,是無法使用 get、set、clear 等 Map 方法的,但是 TypeScript 會提示這些方法可用。如果使用了這些方法,會得到一個 Uncaught TypeError。

          如果使用 Object 則不會產(chǎn)生這個問題。

          WebSocket 發(fā)生異常無法被 try catch 監(jiān)聽

          ws 的異常只能在 onerror 和 onclose 兩個事件中進行處理,try catch 是無法捕獲的。

          有些時候,onerror 和 onclose 會連續(xù)執(zhí)行,比如觸發(fā) onerror,導致連接關閉,就會緊接著觸發(fā) onclose。

          Vue Devtools

          vue devtools 目前無法支持 Vue3,但是 vue devtools 幾乎是開發(fā)中必不可少的工具,目前可以使用 vue devtools beta 版本,但存在一些 Bug。

          下載地址

          用法非常簡單,安裝后重啟瀏覽器就可以。不需要設置 vue.config.devtools = true,在 vue3 中 vue.config 實例不存在 devtools 屬性。

          ESbuild 安裝依賴

          在使用 vite 啟動服務的同時安裝依賴,非常容易碰到一個錯誤。

          Error: EBUSY: resource busy or locked, open 'E:\gxt\property-relay-fed\node_modules\esbuild\esbuild.exe'
          復制代碼

          這個問題的原因是 vite 依賴的編譯工具 esbuild.exe 被占用所導致的,解決方法很簡單,就是停掉 vite,安裝完依賴后再重新啟動 vite。

          Vite 在 Chrome 中調(diào)試的問題

          系統(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 的語法直接被認為語法錯誤。(我是使用 Vite 啟動的開發(fā)服務。)

          解決方案很簡單,但挺 Low。先使用 vite build 把 TS 代碼編譯成 JS,再使用 vite preview 啟動服務。

          WebSocket

          websocket 和 Vue3 沒什么關系,但是在這里簡單提一下。

          設備管理系統(tǒng)的核心概念是設備,設備會有很多屬性,在硬件上也被稱作數(shù)據(jù)點。這些屬性會經(jīng)歷非常長的鏈路傳輸?shù)接脩艚缑嫔?。整體流程大概是:硬件通過 tcp 協(xié)議上傳到接入網(wǎng)關,接入網(wǎng)關處理后再通過 mqtt 協(xié)議上傳到物聯(lián)網(wǎng)平臺,物聯(lián)網(wǎng)平臺再經(jīng)過規(guī)則引擎處理,通過 webhook restful 的形式發(fā)送到業(yè)務系統(tǒng),業(yè)務系統(tǒng)再通過 websocket 推送到前端。

          雖然數(shù)據(jù)通過層層編解碼、不同的協(xié)議繞了非常遠的距離呈現(xiàn)到用戶面前,但是前端只需要關心 websocket 就足夠了。

          WebSocket 重連

          在做重連時,需要注意 onerror 和 onclose 連續(xù)執(zhí)行的問題,通常是使用類似防抖的方法來解決。

          我的做法是增加一個變量來控制重連次數(shù)。

          let connecting = false// 斷開連接后,先觸發(fā) onerror,再觸發(fā) onclose,主要用于防止重復觸發(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);
                }
              };
            }
          復制代碼

          WebSocket 連接活動日志

          系統(tǒng)是設計成 7*24 小時不間斷運行。所以 websocket 很容易受到一些網(wǎng)絡因素或者其它因素的影響發(fā)生斷開,重連是一項非常重要的功能,同時還應該具備重連日志功能。

          在用戶的不同環(huán)境中,排查 WebSocket 的連接狀態(tài)很麻煩,添加一個連接日志功能是比較不錯的方案,這樣可以很好的看到不同時間的連接情況。

          image.png

          需要注意,這些日志是存儲在用戶的瀏覽器內(nèi)存中的,需要設置上限,到達上限要自動清除早期日志。

          WebSocket 鑒權

          websocket 的鑒權是很多人容易忽視的一個點。

          我在系統(tǒng)設計中,restful API 的鑒權是通過在 request header 上附帶 Authorization 字段,設置生成的 JWT 來實現(xiàn)的。

          websocket 無法設置 header,但是可以設置 query,實現(xiàn)思路類似 restful 的認證設計。

          關于 ws 鑒權的過期、續(xù)期、權限等問題,和 restful 保持一致即可。

          script setup:更加清爽的 API

          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 標簽中的頂層變量、函數(shù)都會 return 出去。

          在這種模式下,減少了大量代碼,可以提高開發(fā)效率、降低心智負擔。

          但這時也存在幾個問題,比如在 script setup 中怎么使用生命周期和 watch/computed 函數(shù)?怎么使用組件?怎么獲取 props 和 context?

          使用組件

          直接導入組件后,vue 會自動識別,無需使用 component 掛載。

          <script setup lang="ts">
          import C from "component"
          </script>
          復制代碼

          使用生命周期和監(jiān)聽計算函數(shù)

          和標準寫法基本無差異。

          <script setup lang="ts">
          import { watch, computed, onMounted } from "vue"
          </script>
          復制代碼

          使用 props 和 context

          由于 setup 被提升到 script 標簽上了,自然也就沒辦法接收 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()
          復制代碼

          原理

          原理相當簡單。增加了一層編譯過程,將 script setup 編譯成標準模式的代碼。

          但是實現(xiàn)上有非常多的細節(jié),所以導致至今仍未推出正式版。

          Vue3 Composition 所帶來的模塊化開發(fā)方式

          這套技術棧帶給我最深的感受還是開發(fā)方式上的變化。

          在 Vue2 的開發(fā)中,Options API 在面對業(yè)務邏輯復雜的頁面時非常吃力。當邏輯長達千行時,追蹤一個變量的變化是一件非常頭痛的事情。

          但是有了 Composition API 后,這將不再是問題,它帶來了一種全新的開發(fā)方式,雖然有種 React 的感覺,但這相比之前已經(jīng)非常棒了!

          這項目中所有的頁面,我都使用 hooks 的方式開發(fā)。

          在設備模塊中,我的 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ù)。

          我的目錄結構是這樣的。

          image.png

          整體上非常清爽,工程化的感覺越來越強。

          前端架構不同于后端架構。

          后端考慮的更多是高可用、高性能、可擴展。前端考慮的問題更多是如何實現(xiàn)高內(nèi)聚低耦合的分層設計,架構即設計。

          良好的架構設計能夠極大的開發(fā)效率,降低開發(fā)人員的心智負擔。

          這也是我們一直以來所關注的問題。

          逆鋒起筆是一個專注于程序員圈子的技術平臺,你可以收獲最新技術動態(tài)最新內(nèi)測資格、BAT等大廠大佬的經(jīng)驗增長自身、學習資料職業(yè)路線、賺錢思維,微信搜索逆鋒起筆關注!

          推薦幾款好用的文本編輯器

          Vue 實戰(zhàn)中的一些小魔法

          Vuex 4 正式發(fā)布:打包現(xiàn)在與 Vue 3 一致

          Vue 開發(fā)移動端項目,這個工具對你一定有幫助

          Vue 超清晰思維導圖,帶你詳細梳理知識點


          支持下 
          瀏覽 61
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费的av网站 | 日本操操操BBB | 日本www在线中文字幕 | 超碰在线9| 天天插天天日天天干 |