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

          使用 Vue3 重構 Vue2 項目(長文)

          共 24029字,需瀏覽 49分鐘

           ·

          2020-11-03 22:51

          前言

          2020年9月18日,vue3正式版發(fā)布了,前幾天把文檔整體讀了一遍,感觸很深,可以解決我項目中的一些痛點,于是就決定重構之前那個vue2的開源項目。

          本篇文章就記錄下重構vue2項目的過程,歡迎各位感興趣的開發(fā)者閱讀本文。

          環(huán)境搭建

          本來打算使用vite + vue3 + VueRouter + vuex + typescript來構架項目的,但是經過一番折騰后發(fā)現vite目前只對vue支持,對于vue周邊的一些庫還沒做到支持,沒法在項目中使用。

          最后,還是決定使用Vue Cli 4.5來構建了。

          雖然vite目前還無法正常在項目中使用,但是我也折騰了一回,就記錄下在折騰時的過程以及一些報錯。

          使用vite構建項目

          本文采用的包管理工具為yarn,將其升級至最新版本就可以正常創(chuàng)建vite項目了。

          初始化項目

          接下來,我們來看看具體步驟。

          • 打開終端,進入你的項目目錄,運行命令:yarn crete vite-app vite-project,該命令用于創(chuàng)建一個名為vite-project的項目。
          • 創(chuàng)建完成后,會得到如下所示的文件。
          • 進入創(chuàng)建好的項目,運行命令:yarn install,該命令會安裝package.json中聲明的依賴。
          • 我們使用IDE打開剛才創(chuàng)建的項目,整體項目如下所示,vite官方為我們提供了一個簡單的demo。
          • 打開package.json查看啟動命令在終端運行命令:yarn run dev或者點擊ide的運行圖標來啟動項目。
          • 大功告成,瀏覽器訪問 http://localhost:3000/,如下所示。

          集成Vue周邊庫

          我們將Vue CLI初始化的項目文件替換到用vite初始化的項目中去,然后修改packge.json中的相關依賴,然后重新安裝依賴即可。

          具體過程如下:

          • 替換文件,替換后的項目目錄如下所示。
          • package.json中提取我們需要的依賴,提取后的文件下。
          {
          ??"name":?"vite-project",
          ??"version":?"0.1.0",
          ??"scripts":?{
          ????"dev":?"vite",
          ????"build":?"vite?build"
          ??},
          ??"dependencies":?{
          ????"core-js":?"^3.6.5",
          ????"vue":?"^3.0.0-0",
          ????"vue-class-component":?"^8.0.0-0",
          ????"vue-router":?"^4.0.0-0",
          ????"vuex":?"^4.0.0-0"
          ??},
          ??"devDependencies":?{
          ????"vite":?"^1.0.0-rc.1",
          ????"@typescript-eslint/eslint-plugin":?"^2.33.0",
          ????"@typescript-eslint/parser":?"^2.33.0",
          ????"@vue/compiler-sfc":?"^3.0.0-0",
          ????"@vue/eslint-config-prettier":?"^6.0.0",
          ????"@vue/eslint-config-typescript":?"^5.0.2",
          ????"eslint":?"^6.7.2",
          ????"eslint-plugin-prettier":?"^3.1.3",
          ????"eslint-plugin-vue":?"^7.0.0-0",
          ????"node-sass":?"^4.12.0",
          ????"prettier":?"^1.19.1",
          ????"sass-loader":?"^8.0.2",
          ????"typescript":?"~3.9.3"
          ??},
          ??"license":?"MIT"
          }

          8abcc9f5b934568e54c0229c6663866c
          • 啟動項目,沒報錯,嘴角瘋狂上揚。
          • 瀏覽器訪問后,空白頁面,打開console后,發(fā)現main.js 404

          難搞,找不到main.js,那我把main.ts后綴改一下試試。將后綴改成js后,文件是不報錯404了,但是又有了新的錯誤。

          vite服務500和@別名無法識別,于是我打開ide的控制臺看了錯誤,大概是scss的錯,vite還沒支持scss。

          scss不支持,別名不識別,網上找了一圈也沒找到解決方案,這些最基礎的東西都無法被vite支持,那它就不能用在項目中了,于是我放棄了。

          綜合上述,vite要走的路還有很多,等它在社區(qū)成熟了,再將它應用到項目中吧。

          使用Vue Cli構建項目

          由于vite的不合適,我們還是繼續(xù)選擇用webpack,此處我們選擇用Vue CLI 4.5來創(chuàng)建項目。

          初始化項目

          • 在終端進入項目目錄,執(zhí)行命令:vue create chat-system-vue3該命令用于創(chuàng)建一個名為chat-system-vue3的項目。

          • 創(chuàng)建完成后,如下所示。

          • IDE打開項目,打開package.json文件,查看項目啟動命令或者直接點編譯器的運行按鈕。

          • OK,大功告成,打開瀏覽器,訪問終端的內網地址。

          解決報錯問題

          在瀏覽CLI默認創(chuàng)建的demo時,打開main.js文件發(fā)現其中App.vue文件報類型錯誤,無法推導出具體的類型。

          一開始,我也懵逼,想起了Vue文檔所說的,啟用TypeScript必須要讓 TypeScript 正確推斷 Vue 組件選項中的類型,需要使用 defineComponent

          App.vue文件代碼如下:





          觀察代碼后我們發(fā)現CLI生成的代碼沒有包含文檔中所描述的代碼,因此我們將其補充上,然后導出即可。

          import?{?defineComponent?}?from?"vue";
          const?Component?=?defineComponent({
          ??//?已啟用類型推斷
          });
          export?default?Component;

          加入上述代碼后,我們的代碼就不報錯了。

          根據官網描述,我們可以在defineComponent的包裹中寫組件的邏輯代碼,但是我看了CIL提供的demo的Home組件后發(fā)現,他的寫法如下。

          export?default?class?Home?extends?Vue?{}

          在項目的src目錄下有一個名為shims-vue.d.ts的文件,它聲明了所有vue文件的返回類型,因此我們可以按照上述方法來寫。該聲明文件代碼如下。

          declare?module?"*.vue"?{
          ??import?{?defineComponent?}?from?"vue";
          ??const?component:?ReturnType<typeof?defineComponent>;
          ??export?default?component;
          }

          這樣的寫法看起來更符合TypeScript,不過這種寫法寫法只支持部分屬性,同樣的我們組件的邏輯代碼寫在類內部即可,那么將剛才App.vue文件中做的更改也應用到此處,如下所示。


          class寫法支持的屬性如下圖所示:

          image-20201009210815033

          配置IDE

          此處內容僅適用于webstorm,如果編輯器是其他的可跳過本部分。

          我們在項目中集成了eslintprettier,默認情況下webstorm是沒有啟用這兩個東西的,需要我們自己手動開啟。

          • 打開webstorm的配置菜單,如下所示

            image-20201006153458084
          • 搜索eslint,按照下圖所示進行配置,配置完成后點APPLYOK即可。

            image-20201006153031544
          • 搜索prettier,按照下圖所示進行配置,配置完成后點APPLYOK即可。

            image-20201006153654226

          配置完上面的內容后,還有一個問題,在組件上用v-if v-for等vue指令時沒有提示,這是因為webstorm沒法正確讀取node_modules包,按照下述操作即可解決這一問題。

          image-20201006154114315

          執(zhí)行上述操作后,等待時間根據cpu性能而定,屆時電腦會發(fā)熱。這都是正常現象

          image-20201006154306682

          成功后,我們發(fā)現編輯器已經可以正常識別v-指令了,并且給了相應的提示。

          image-20201006154454592

          項目目錄對比

          按照上述步驟,即可創(chuàng)建一個vue3的項目,接下來我們將需要重構的vue2項目的目錄與上面創(chuàng)建的項目進行下目錄對比。

          • 如下所示,為vue2.0項目的目錄

            image-20201006162826706
          • 如下所示,為vue3.0項目的目錄

            image-20201006162936370

          仔細觀察后,我們發(fā)現在目錄上并沒有什么大的區(qū)別,只是多了typescript的配置文件和項目內使用ts的時輔助文件。

          項目重構

          接下來,我們來一步步把vue2項目的文件遷移到vue3項目中,修改不合適的地方,讓其適配vue3.0。

          適配路由配置

          我們先從路由配置文件開始適配,打開vue3項目的router/index.ts文件,發(fā)現有一個報錯,報錯如下。

          image-20201006215331894

          錯誤信息是類型沒被推導出來,我看了下面路由的寫法后,盲猜它需要用函數返回,于是試了下,還真就是這樣,正確的路由寫法如下。

          ??{
          ????path:?"/",
          ????name:?"Home",
          ????component:?()?=>?Home
          ??}

          整體的路由配置文件代碼如下:

          import?{?createRouter,?createWebHashHistory,?RouteRecordRaw?}?from?"vue-router";
          import?Home?from?"../views/Home.vue";

          const?routes:?Array?=?[
          ??{
          ????path:?"/",
          ????name:?"Home",
          ????component:?()?=>?Home
          ??},
          ??{
          ????path:?"/about",
          ????name:?"About",
          ????//?route?level?code-splitting
          ????//?this?generates?a?separate?chunk?(about.[hash].js)?for?this?route
          ????//?which?is?lazy-loaded?when?the?route?is?visited.
          ????component:?()?=>
          ??????import(/*?webpackChunkName:?"about"?*/?"../views/About.vue")
          ??}
          ];

          const?router?=?createRouter({
          ??history:?createWebHashHistory(),
          ??routes
          });

          export?default?router;

          我們再來看看vue2項目中的路由配置,為了簡單起見我摘抄了部分代碼過來,如下所示。

          import?Vue?from?'vue'
          import?VueRouter?from?'vue-router'
          import?MsgList?from?'../views/msg-list'
          import?Login?from?"../views/login"
          import?MainBody?from?'../components/main-body'
          Vue.use(VueRouter);

          const?routes?=?[
          ????{
          ????????path:?'/',
          ????????redirect:?'/contents/message/message',
          ????},
          ????{
          ????????name:?'contents',
          ????????path:?'/contents/:thisStatus',
          ????????//?重定向到嵌套路由
          ????????redirect:?'/contents/:thisStatus/:thisStatus/',
          ????????components:?{
          ????????????mainArea:?MainBody
          ????????},
          ????????props:?{
          ????????????mainArea:?true
          ????????},
          ????????children:?[
          ????????????{
          ????????????????path:?'message',
          ????????????????components:?{
          ????????????????????msgList:?MsgList
          ????????????????}
          ????????????}
          ????????],
          ????},
          ????{
          ????????name:?'login',
          ????????path:?"/login",
          ????????components:?{
          ????????????login:Login
          ????????}
          ????}
          ];

          const?router?=?new?VueRouter({
          ????//?mode:?'history',
          ????routes,
          });

          export?default?router

          經過觀察后,它們的不同點如下:

          • Vue.use(VueRouter)這種寫法被移除
          • new VueRouter({})寫法改為了createRouter({})
          • hash模式和history模式聲明由原先的mode選項變更為了createWebHashHistory()createWebHistory()更加語義化了
          • 聲明路由時多了ts的類型注解Array

          知道它們的區(qū)別后,我們就可以對路由進行適配和遷移了,遷移完成的路由配置文件:router/index.ts

          這里有個小坑,路由懶加載的時候必須給他返回一個函數。例如:component: () => import("../views/msg-list.vue")。不然就會報黃色警告。

          image-20201015223425458
          image-20201015223525227

          適配Vuex配置

          接下來我們來看看兩個版本在vuex使用上的區(qū)別,如下所示為vue3的vuex配置。

          import?{?createStore?}?from?"vuex";

          export?default?createStore({
          ??state:?{},
          ??mutations:?{},
          ??actions:?{},
          ??modules:?{}
          });

          我們再來看看vue2項目中的vuex配置,為了簡潔起見,我只列出了大體代碼。

          import?Vue?from?'vue'
          import?Vuex?from?'vuex'

          Vue.use(Vuex);

          export?default?new?Vuex.Store({
          ??state:?{
          ??},
          ??mutations:?{
          ??},
          ??actions:?{
          ??},
          ??modules:?{
          ??}
          })

          經過對比后,我們發(fā)現的不同點如下所示:

          • 按需導入import { createStore } from "vuex",移除了之前的整個導入import Vuex from 'vuex'
          • 移除了Vue.use(Vuex)的寫法
          • 導出時丟棄之前的new Vuex.Store寫法,改用了createStore寫法。

          知道上述不同點后,我們就可以對代碼進行適配和遷移了,遷移完成的vuex配置文件:store/index.ts

          如果需要在vue的原型上掛載東西,就不能使用以前的原型掛載方法,需要使用新方法config.globalProperties,詳細用法請查閱官方文檔。

          我的項目中用到了一個websocket的插件,他需要在vuex中往Vue原型上掛載方法,下面是我的做法。

          • main.ts中的createApp方法導出。

            import?{?createApp?}?from?"vue";

            const?app?=?createApp(App);

            export?default?app;

          • store/index.ts中導入main.ts,然后調用方法掛載即可。

            ??mutations:?{
            ????//?連接打開
            ????SOCKET_ONOPEN(state,?event)?{
            ??????main.config.globalProperties.$socket?=?event.currentTarget;
            ??????state.socket.isConnected?=?true;
            ??????//?連接成功時啟動定時發(fā)送心跳消息,避免被服務器斷開連接
            ??????state.socket.heartBeatTimer?=?setInterval(()?=>?{
            ????????const?message?=?"心跳消息";
            ????????state.socket.isConnected?&&
            ??????????main.config.globalProperties.$socket.sendObj({
            ????????????code:?200,
            ????????????msg:?message
            ??????????});
            ??????},?state.socket.heartBeatInterval);
            ????}
            ??}

          適配axios

          axios在封裝成插件時與之前的差別對比如下:

          • 暴露install方法由原來的Plugin.install改為了install
          • 增加了ts的類型聲明
          • Object.defineProperties舍棄了,現在直接使用app.config.globalProperties掛載即可

          適配完成的代碼如下:

          import?{?App?}?from?"vue";
          import?axiosObj,?{?AxiosInstance,?AxiosRequestConfig?}?from?"axios";
          import?store?from?"../store/index";

          const?defaultConfig?=?{
          ??//?baseURL在此處省略配置,考慮到項目可能由多人協作完成開發(fā),域名也各不相同,此處通過對api的抽離,域名單獨配置在base.js中

          ??//?請求超時時間
          ??timeout:?60?*?1000,
          ??//?跨域請求時是否需要憑證
          ??//?withCredentials:?true,?//?Check?cross-site?Access-Control
          ??heards:?{
          ????get:?{
          ??????"Content-Type":?"application/x-www-form-urlencoded;charset=utf-8"
          ??????//?將普適性的請求頭作為基礎配置。當需要特殊請求頭時,將特殊請求頭作為參數傳入,覆蓋基礎配置
          ????},
          ????post:?{
          ??????"Content-Type":?"application/json;charset=utf-8"
          ??????//?將普適性的請求頭作為基礎配置。當需要特殊請求頭時,將特殊請求頭作為參數傳入,覆蓋基礎配置
          ????}
          ??}
          };

          /**
          ?*?請求失敗后的錯誤統(tǒng)一處理,當然還有更多狀態(tài)碼判斷,根據自己業(yè)務需求去擴展即可
          ?*?@param?status?請求失敗的狀態(tài)碼
          ?*?@param?msg?錯誤信息
          ?*/

          const?errorHandle?=?(status:?number,?msg:?string)?=>?{
          ??//?狀態(tài)碼判斷
          ??switch?(status)?{
          ????//?401:?未登錄狀態(tài),跳轉登錄頁
          ????case?401:
          ??????//?跳轉登錄頁
          ??????break;
          ????//?403?token過期
          ????case?403:
          ??????//?如果不需要自動刷新token,可以在這里移除本地存儲中的token,跳轉登錄頁

          ??????break;
          ????//?404請求不存在
          ????case?404:
          ??????//?提示資源不存在
          ??????break;
          ????default:
          ??????console.log(msg);
          ??}
          };

          export?default?{
          ??//?暴露安裝方法
          ??install(app:?App,?config:?AxiosRequestConfig?=?defaultConfig)?{
          ????let?_axios:?AxiosInstance;

          ????//?創(chuàng)建實例
          ????_axios?=?axiosObj.create(config);
          ????//?請求攔截器
          ????_axios.interceptors.request.use(
          ??????function(config)?{
          ????????//?從vuex里獲取token
          ????????const?token?=?store.state.token;
          ????????//?如果token存在就在請求頭里添加
          ????????token?&&?(config.headers.token?=?token);
          ????????return?config;
          ??????},
          ??????function(error)?{
          ????????//?Do?something?with?request?error
          ????????error.data?=?{};
          ????????error.data.msg?=?"服務器異常";
          ????????return?Promise.reject(error);
          ??????}
          ????);
          ????//?響應攔截器
          ????_axios.interceptors.response.use(
          ??????function(response)?{
          ????????//?清除本地存儲中的token,如果需要刷新token,在這里通過舊的token跟服務器換新token,將新的token設置的vuex中
          ????????if?(response.data.code?===?401)?{
          ??????????localStorage.removeItem("token");
          ??????????//?頁面刷新
          ??????????parent.location.reload();
          ????????}
          ????????//?只返回response中的data數據
          ????????return?response.data;
          ??????},
          ??????function(error)?{
          ????????if?(error)?{
          ??????????//?請求已發(fā)出,但不在2xx范圍內
          ??????????errorHandle(error.status,?error.data.msg);
          ??????????return?Promise.reject(error);
          ????????}?else?{
          ??????????//?斷網
          ??????????return?Promise.reject(error);
          ????????}
          ??????}
          ????);
          ????//?將axios掛載到vue的全局屬性中
          ????app.config.globalProperties.$axios?=?_axios;
          ??}
          };

          然后將其在main.js中use,就可以在代碼中通過this.$axios.xx來使用了。

          不過上述將axios掛載到vue上是多此一舉的,因為我已經將api進行了抽離,在每個單獨的api文件中都是通過導入我們封裝好的axios的配置文件,然后用導入進來的axios實例來進行的接口封裝。(ps: 之前由于自己太菜沒注意到這個,傻傻的將其封裝成了插件?)

          那么,不需要將其封裝成插件的話,那它就屬于對axios進行配置封裝了,我們將它放在config目錄下,將上述代碼稍作修改即可,修改好的代碼地址:config/axios.ts。

          最后在main.ts中將api掛載到全局屬性。

          import?{?createApp?}?from?"vue";
          import?api?from?"./api/index";
          const?app?=?createApp(App);
          app.config.globalProperties.$api?=?api;

          隨后就就可以在業(yè)務代碼中通過this.$api.xx按模塊來調用我們拋出來的接口了。

          shims-vue.d.ts類型聲明文件

          shims-vue.d.ts是一個Typescript的聲明文件,當項目啟用ts后,有些文件是我們自己封裝的,類型較為復雜,ts不能推導出其具體類型,此時就需要我們進行手動聲明。

          例如上面我們掛載到原型上的$api,它導出了一個類文件,此時類型就較為復雜了,ts沒法推導出其類型,我們在使用時就會報錯。

          image-20201010100416381

          要解決這個錯誤,我們就需要在shims-vue.d.ts中聲明api的的類型

          //?聲明全局屬性類型
          declare?module?"@vue/runtime-core"?{
          ??interface?ComponentCustomProperties?{
          ????$api:?T;
          ??}
          }

          注意:在shims-vue.d.ts文件中,類型聲明超過1個時,組件內需要import包就不能在其內部進行,需要將其寫在最外層,否則會報錯。

          image-20201010101906448

          適配入口文件

          由于啟用了typescript,入口文件由main.js變成了main.ts,文件中的寫法與之前相比其不同點如下:

          • 初始化掛載vue由原先的new Vue(App)改為了按需導入寫法的createApp(App)
          • 使用插件時,也由原先的Vue.use()改成了,createApp(App).use()

          在我的項目中引用了幾個插件,需要在入口文件中做一些初始化的操作,插件還是2.x版本,沒有ts的類型聲明文件,因此導入時ts沒法推導出它的類型,就得用// @ts-ignore讓ts忽略它。

          完整的入口文件地址:main.ts

          適配組件

          基礎設施完善后,接下來我們來適配組件,我們先來試試把2.x項目的所有組件搬過來看看,能不能直接啟動。

          結果可想而知,無法運行。因為我用了2.x的插件,vue3.0有關插件的封裝,一些寫法變了。我項目中總共引用了2個插件v-viewervue-native-websocketv-viewer這個插件無解,他底層使用用到的2.x語法太多了,所以我選擇放棄這個插件。vue-native-websocket這個插件就是使用的Vue.prototype.xx寫法被舍棄了,用新的寫法Vue.config.globalProperties.xx將其替換即可。

          image-20201009174402912

          替換完成后,重新編譯即可,隨后啟動項目,如下所示,錯誤解決,項目成功啟動。

          image-20201009175415170

          正如上圖中所看到的,控制臺有黃色警告,因為我們組件的代碼還是使用的vue2.x的語法,我們要重新整理組件中的方法從而適配vue3.0

          注意:組件script標簽聲明lang="ts"后,就必須按照Vue官方文檔所說使用defineComponent全局方法來定義組件。

          組件優(yōu)化

          接下來,我們從login.vue組件開始重構,看看都做了哪些優(yōu)化。

          1. 創(chuàng)建type文件夾,文件夾內創(chuàng)建ComponentDataType.ts,將組件中用到的類型指定放在其中。
          2. 創(chuàng)建enum文件夾,將組件中用到的枚舉放在其中。

          我們先來看看第一點,將組件內用到的類型進行統(tǒng)一管理,我們以登錄組件為例,我們需要為data返回的對象指定其每個屬性的類型,因此我們ComponentDataType.ts中創(chuàng)建一個名為loginDataType的類型,其代碼如下。

          export?type?loginDataType?=?{
          ??loginUndo:?T;?//?禁止登錄時的圖標
          ??loginBtnNormal:?T;?//?登錄時的按鈕圖標
          ??loginBtnHover:?T;?//?鼠標懸浮時的登錄圖標
          ??loginBtnDown:?T;?//?鼠標按下時的登錄圖標
          ??userName:?string;?//?用戶名
          ??password:?string;?//?密碼
          ??confirmPassword:?string;?//?注冊時的確認登錄密碼
          ??isLoginStatus:?number;?//?登錄狀態(tài):0.未登錄 1.登錄中 2.注冊
          ??loginStatusEnum:?Object;?//?登錄狀態(tài)枚舉
          ??isDefaultAvatar:?boolean;?//?頭像是否為默認頭像
          ??avatarSrc:?T;?//?頭像地址
          ??loadText:?string;?//?加載層的文字
          };

          聲明好類型后,就可以在組件中使用了,代碼如下:

          import?{?loginDataType?}?from?"@/type/ComponentDataType";
          export?default?defineComponent({
          ??data():?loginDataType?{
          ????return?{
          ??????loginUndo:?require("../assets/img/login/[email protected]"),
          ??????loginBtnNormal:?require("../assets/img/login/[email protected]"),
          ??????loginBtnHover:?require("../assets/img/login/[email protected]"),
          ??????loginBtnDown:?require("../assets/img/login/[email protected]"),
          ??????userName:?"",
          ??????password:?"",
          ??????confirmPassword:?"",
          ??????isLoginStatus:?0,
          ??????loginStatusEnum:?loginStatusEnum,
          ??????isDefaultAvatar:?true,
          ??????avatarSrc:?require("../assets/img/login/[email protected]"),
          ??????loadText:?"上傳中"
          ????};
          ??}
          })

          上述代碼完整地址:

          • type/ComponentDataType.ts

          • login.vue

          再然后,我們看看第二點,使用enum來優(yōu)化組件內部的條件判斷,例如上面data中的isLoginStatus就有3種狀態(tài),我們要根據這三種狀態(tài)來做不同的事情,如果直接用數字來代表三種狀態(tài)直接賦值數字,后期維護時將是一件很痛苦的事情,如果用enum來定義的話,根據語意一眼就能看出它的狀態(tài)是什么。

          我們在enum文件夾中創(chuàng)建ComponentEnum.ts文件,組件內用到的所有枚舉都會在此文件內定義,接下來在組件內創(chuàng)建loginStatusEnum,代碼如下:

          export?enum?loginStatusEnum?{
          ??NOT_LOGGED_IN?=?0,?//?未登錄
          ??LOGGING_IN?=?1,?//?登錄中
          ??REGISTERED?=?2?//?注冊
          }

          聲明好后,我們就可以在組件中使用了,代碼如下:

          import?{?loginStatusEnum?}?from?"@/enum/ComponentEnum";

          export?default?defineComponent({
          ??methods:?{
          ????stateSwitching:?function(status)?{
          ??????case?"條件1":
          ???????this.isLoginStatus?=?loginStatusEnum.LOGGING_IN;
          ???????break;
          ??????case?"條件2":
          ???????this.isLoginStatus?=?loginStatusEnum.NOT_LOGGED_IN;
          ???????break;
          ????}
          ??}
          })

          上述代碼完整地址:

          • enum/ComponentEnum.ts

          • login.vue

          this指向

          在適配組件過程中,方法內部的this不能很好的識別,無奈就用了很笨的方法解決。

          如下所示:

          const?_img?=?new?Image();
          _img.src?=?base64;
          _img.onload?=?function()?{
          ????const?_canvas?=?document.createElement("canvas");
          ????const?w?=?this.width?/?scale;
          ????const?h?=?this.height?/?scale;
          ????_canvas.setAttribute("width",?w?+?"");
          ????_canvas.setAttribute("height",?h?+?"");
          ????_canvas.getContext("2d")?.drawImage(this,?0,?0,?w,?h);
          ????const?base64?=?_canvas.toDataURL("image/jpeg");
          }

          onload方法內部的this應該是指向_img的,但是ts并不這么認為,報錯如下所示。

          image-20201013171520088

          this對象中不包含width屬性,解決方案就是講this換成_img,問題解決。

          image-20201013171712449

          Dom對象類型定義

          當操作dom對象時,層級過時ts就無法推斷出具體類型了,如下所示:

          sendMessage:?function(event:?KeyboardEvent)?{
          ??????if?(event.key?===?"Enter")?{
          ????????//?阻止編輯框默認生成div事件
          ????????event.preventDefault();
          ????????let?msgText?=?"";
          ????????//?獲取輸入框下的所有子元素
          ????????const?allNodes?=?event.target.childNodes;
          ????????for?(const?item?of?allNodes)?{
          ??????????//?判斷當前元素是否為img元素
          ??????????if?(item.nodeName?===?"IMG")?{
          ????????????if?(item.alt?===?"")?{
          ??????????????//?是圖片
          ??????????????let?base64Img?=?item.src;
          ??????????????//?刪除base64圖片的前綴
          ??????????????base64Img?=?base64Img.replace(/^data:image\/\w+;base64,/,?"");
          ??????????????//隨機文件名
          ??????????????const?fileName?=?new?Date().getTime()?+?"chatImg"?+?".jpeg";
          ??????????????//將base64轉換成file
          ??????????????const?imgFile?=?this.convertBase64UrlToImgFile(
          ????????????????base64Img,
          ????????????????fileName,
          ????????????????"image/jpeg"
          ??????????????);
          ????????????}
          ??????????}
          ????????}
          ??????}
          }

          上面為一個發(fā)送消息的函數的部分代碼,消息框中包含圖片和文字,要對圖片進行單獨處理,我們需要要從target中拿到所有節(jié)點childNodes,然后遍歷每個節(jié)點獲取其類型,childNodes的類型為NodeList,那么他的每一個元素就是Node類型,如果當前遍歷到的元素的nodeName屬性是IMG時,它就是一個圖片,我們就獲取它的alt屬性進一步判斷,再獲取src屬性。

          然而,ts會報錯altsrc屬性不存在,報錯如下:

          image-20201013172815950

          此時,我們就需要把item斷言成HTMLImageElement類型。

          image-20201019110053258

          復雜類型定義

          在適配組件過程中,遇到一個比較復雜的數據類型定義,數據如下:

          ?data(){
          ????return?{
          ??????friendsList:?[
          ????????{
          ??????????groupName:?"我",
          ??????????totalPeople:?2,
          ??????????onlineUsers:?2,
          ??????????friendsData:?[
          ????????????{
          ??????????????username:?"神奇的程序員",
          ??????????????avatarSrc:
          ????????????????"https://www.kaisir.cn/uploads/1ece3749801d4d45933ba8b31403c685touxiang.jpeg",
          ??????????????signature:?"今天的努力只為未來",
          ??????????????onlineStatus:?true,
          ??????????????userId:?"c04618bab36146e3a9d3b411e7f9eb8f"
          ????????????},
          ????????????{
          ??????????????username:?"admin",
          ??????????????avatarSrc:
          ????????????????"https://www.kaisir.cn/uploads/40ba319f75964c25a7370e3909d347c5admin.jpg",
          ??????????????signature:?"",
          ??????????????onlineStatus:?true,
          ??????????????userId:?"32ee06c8380e479b9cd4097e170a6193"
          ????????????}
          ??????????]
          ????????},
          ????????{
          ??????????groupName:?"我的朋友",
          ??????????totalPeople:?0,
          ??????????onlineUsers:?0,
          ??????????friendsData:?[]
          ????????},
          ????????{
          ??????????groupName:?"我的家人",
          ??????????totalPeople:?0,
          ??????????onlineUsers:?0,
          ??????????friendsData:?[]
          ????????},
          ????????{
          ??????????groupName:?"我的同事",
          ??????????totalPeople:?0,
          ??????????onlineUsers:?0,
          ??????????friendsData:?[]
          ????????}
          ??????]
          ????};
          ??},

          一開始我是這樣定義的。

          image-20201014214430066

          嵌套到一起,自認為沒問題,放進代碼后,報錯長度不匹配,這樣寫知識給第一個對象定義了類型。

          image-20201014214529652

          經過一番求助后,他們說應該分開寫,不能這樣嵌套定義,正確寫法如下:

          • 類型分開定義

            //?聯系人面板Data屬性定義
            export?type?contactListDataType?=?{
            ??friendsList:?Array;
            };

            //?聯系人列表類型定義
            export?type?friendsListType?=?{
            ??groupName:?string;?//?分組名稱
            ??totalPeople:?number;?//?總人數
            ??onlineUsers:?number;?//?在線人數
            ??friendsData:?Array;?//?好友列表
            };

            //?聯系人類型定義
            export?type?friendsDataType?=?{
            ??username:?string;?//?昵稱
            ??avatarSrc:?string;?//?頭像地址
            ??signature:?string;?//?個性簽名
            ??onlineStatus:?boolean;?//?在線狀態(tài)
            ??userId:?string;?//?用戶id
            };

          • 組件中使用

            import?{
            ??contactListDataType,
            ??friendsListType,
            ??friendsDataType
            }?from?"@/type/ComponentDataType";

            data():?contactListDataType>?{
            ????return?{
            ??????friendsList:?[
            ????????{
            ??????????groupName:?"我",
            ??????????totalPeople:?2,
            ??????????onlineUsers:?2,
            ??????????friendsData:?[
            ????????????{
            ??????????????username:?"神奇的程序員",
            ??????????????avatarSrc:
            ????????????????"https://www.kaisir.cn/uploads/1ece3749801d4d45933ba8b31403c685touxiang.jpeg",
            ??????????????signature:?"今天的努力只為未來",
            ??????????????onlineStatus:?true,
            ??????????????userId:?"c04618bab36146e3a9d3b411e7f9eb8f"
            ????????????},
            ????????????{
            ??????????????username:?"admin",
            ??????????????avatarSrc:
            ????????????????"https://www.kaisir.cn/uploads/40ba319f75964c25a7370e3909d347c5admin.jpg",
            ??????????????signature:?"",
            ??????????????onlineStatus:?true,
            ??????????????userId:?"32ee06c8380e479b9cd4097e170a6193"
            ????????????}
            ??????????]
            ????????},
            ????????{
            ??????????groupName:?"我的朋友",
            ??????????totalPeople:?0,
            ??????????onlineUsers:?0,
            ??????????friendsData:?[]
            ????????},
            ????????{
            ??????????groupName:?"我的家人",
            ??????????totalPeople:?0,
            ??????????onlineUsers:?0,
            ??????????friendsData:?[]
            ????????},
            ????????{
            ??????????groupName:?"我的同事",
            ??????????totalPeople:?0,
            ??????????onlineUsers:?0,
            ??????????friendsData:?[]
            ????????}
            ??????]
            ????};
            ??}

          深刻的理解到了typescript泛型的使用,經驗++?

          tag屬性被移除

          我們在使用router-link時,它默認會渲染成a標簽,如果想讓他渲染成其它自定義標簽,可以通過tag屬性來修改,如下所示:


          然而,在vue-router的新版本中,官方將event和tag屬性移除了,因此我們就不能這么使用了,當然官方文檔中也給了解決方案使用v-solt來作為替代方案,上述代碼中我們希望將其渲染成div,用v-solt的寫法如下所示:


          @click="navigate"
          @keypress.enter="navigate"
          role="link"
          >


          有關這一塊的更多講解,請移步官方文檔:removal-of-event-and-tag-props-in-router-link

          組件無法外鏈文件

          當我把頁面當組件進行引入聲明時,發(fā)現vue3不支持將邏輯代碼外鏈,像下面這樣,通過src外鏈。


          在組件中引用。




          然后,他就報錯了,類型無法推斷。

          image-20201018224619607

          嘗試了很多方法,最后發(fā)現是不能通過src外鏈的問題,于是我把ts文件中的代碼寫在vue模版中報錯就沒了。

          必須使用as進行斷言

          當我把代碼搬到vue模版中后,它報了一些很奇怪的錯誤,如下所示imgContent變量可能存在多個類型,ts無法推斷出具體類型,此時就需要我們自己進行斷言給他指定類型,我用了尖括號的寫法,他報錯了,webstorm可能對vue3的適配不是很好,他的報錯很奇怪,如下所示

          image-20201018225114933

          一開始,我看到這個錯誤我是一臉懵逼的,一個朋友告訴我用排除法,注釋下距離它最近的代碼,看看是否會報錯,于是找到了問題根源,就是上面的類型斷言的鍋,將它修改后,問題解決。

          image-20201018225618020

          問題是解決了,但是我很是想不通為何一定要用as,尖括號跟他是同等的才對,于是我翻了官方文檔。

          image-20201018225919664

          正如官方文檔所說,啟用jsx后就只能使用as語法了。可能vue3的模版語法默認是啟用jsx的吧。

          ref數組不會自動創(chuàng)建數組

          在vue2中,在v-for里使用ref屬性時會用ref數組填充相應的$refs屬性,如下所示為好友列表的部分代碼,它通過循環(huán)friendsList,將groupArrowbuddyList放進ref數組中。


          我們通過$refs可以訪問到相應的節(jié)點,如下所示。

          import?lodash?from?'lodash';
          export?default?{
          ???name:?"contact-list",
          ???methods:{
          ????????//?分組狀態(tài)切換
          ????????groupingStatus:function?(index)?{
          ????????????if(lodash.isEmpty(this.$route.params.userId)===false){
          ????????????????this.$router.push({name:?"list"}).then();
          ????????????}
          ????????????//?獲取transform的值
          ????????????let?transformVal?=?this.$refs.groupArrow[index].style.transform;
          ????????????if(lodash.isEmpty(transformVal)===false){
          ????????????????//?截取rotate的值
          ????????????????transformVal?=?transformVal.substring(7,9);
          ????????????????//?判斷是否展開
          ????????????????if?(parseInt(transformVal)===90){
          ????????????????????this.$refs.groupArrow[index].style.transform?=?"rotate(0deg)";
          ????????????????????this.$refs.buddyList[index].style.display?=?"none";
          ????????????????}else{
          ????????????????????this.$refs.groupArrow[index].style.transform?=?"rotate(90deg)";
          ????????????????????this.$refs.buddyList[index].style.display?=?"block";
          ????????????????}
          ????????????}else{
          ????????????????//?第一次點擊添加transform屬性,旋轉90度
          ????????????????this.$refs.groupArrow[index].style.transform?=?"rotate(90deg)";
          ????????????????this.$refs.buddyList[index].style.display?=?"block";
          ????????????}
          ????????},
          ????????//?獲取列表好友信息
          ????????getBuddyInfo:function?(userId)?{
          ????????????//?判斷當前路由params與當前點擊項的userId是否相等
          ????????????if(!lodash.isEqual(this.$route.params.userId,userId)){
          ????????????????this.$router.push({name:?"dataPanel",?params:?{userId:?userId}}).then();
          ????????????}
          ????????}
          ????}
          }

          上述寫法在vue2沒問題,但是在vue3中你得到的結果是報錯,官方認為這種行為會變得不明確且效率低下,采用了新的語法來解決這個問題,通過ref來綁定一個函數去處理,如下所示。



          <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>
                    欧美在线手机性免费 | 欧美特级黄片在线播放 | 野外无码 | 蜜芽精品。con | 啊啊啊操B视频在线观看 |