1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        僅需十分鐘!快速使用Vue3重構(gòu)升級vue2項(xiàng)目

        共 24014字,需瀏覽 49分鐘

         ·

        2020-10-23 18:00

        前言

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

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

        環(huán)境搭建

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

        最后,還是決定使用Vue Cli 4.5來構(gòu)建了。

        雖然vite目前還無法正常在項(xiàng)目中使用,但是我也折騰了一回,就記錄下在折騰時(shí)的過程以及一些報(bào)錯(cuò)。

        使用vite構(gòu)建項(xiàng)目

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

        初始化項(xiàng)目

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

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

        集成Vue周邊庫

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

        具體過程如下:

        • 替換文件,替換后的項(xiàng)目目錄如下所示。
        • 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
        • 啟動(dòng)項(xiàng)目,沒報(bào)錯(cuò),嘴角瘋狂上揚(yáng)。
        • 瀏覽器訪問后,空白頁面,打開console后,發(fā)現(xiàn)main.js 404

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

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

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

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

        使用Vue Cli構(gòu)建項(xiàng)目

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

        初始化項(xiàng)目

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

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

        • IDE打開項(xiàng)目,打開package.json文件,查看項(xiàng)目啟動(dòng)命令或者直接點(diǎn)編譯器的運(yùn)行按鈕。

        • OK,大功告成,打開瀏覽器,訪問終端的內(nèi)網(wǎng)地址。

        解決報(bào)錯(cuò)問題

        在瀏覽CLI默認(rèn)創(chuàng)建的demo時(shí),打開main.js文件發(fā)現(xiàn)其中App.vue文件報(bào)類型錯(cuò)誤,無法推導(dǎo)出具體的類型。

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

        App.vue文件代碼如下:





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

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

        加入上述代碼后,我們的代碼就不報(bào)錯(cuò)了。

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

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

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

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

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


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

        image-20201009210815033

        配置IDE

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

        我們在項(xiàng)目中集成了eslintprettier,默認(rèn)情況下webstorm是沒有啟用這兩個(gè)東西的,需要我們自己手動(dòng)開啟。

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

          image-20201006153458084
        • 搜索eslint,按照下圖所示進(jìn)行配置,配置完成后點(diǎn)APPLY、OK即可。

          image-20201006153031544
        • 搜索prettier,按照下圖所示進(jìn)行配置,配置完成后點(diǎn)APPLY、OK即可。

          image-20201006153654226

        配置完上面的內(nèi)容后,還有一個(gè)問題,在組件上用v-if v-for等vue指令時(shí)沒有提示,這是因?yàn)閣ebstorm沒法正確讀取node_modules包,按照下述操作即可解決這一問題。

        image-20201006154114315

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

        image-20201006154306682

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

        image-20201006154454592

        項(xiàng)目目錄對比

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

        • 如下所示,為vue2.0項(xiàng)目的目錄

          image-20201006162826706
        • 如下所示,為vue3.0項(xiàng)目的目錄

          image-20201006162936370

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

        項(xiàng)目重構(gòu)

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

        適配路由配置

        我們先從路由配置文件開始適配,打開vue3項(xiàng)目的router/index.ts文件,發(fā)現(xiàn)有一個(gè)報(bào)錯(cuò),報(bào)錯(cuò)如下。

        image-20201006215331894

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

        ??{
        ????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項(xiàng)目中的路由配置,為了簡單起見我摘抄了部分代碼過來,如下所示。

        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

        經(jīng)過觀察后,它們的不同點(diǎn)如下:

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

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

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

        image-20201015223425458
        image-20201015223525227

        適配Vuex配置

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

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

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

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

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

        Vue.use(Vuex);

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

        經(jīng)過對比后,我們發(fā)現(xiàn)的不同點(diǎn)如下所示:

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

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

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

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

        • main.ts中的createApp方法導(dǎo)出。

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

          const?app?=?createApp(App);

          export?default?app;

        • store/index.ts中導(dǎo)入main.ts,然后調(diào)用方法掛載即可。

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

        適配axios

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

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

        適配完成的代碼如下:

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

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

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

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

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

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

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

        ????//?創(chuàng)建實(shí)例
        ????_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?=?"服務(wù)器異常";
        ????????return?Promise.reject(error);
        ??????}
        ????);
        ????//?響應(yīng)攔截器
        ????_axios.interceptors.response.use(
        ??????function(response)?{
        ????????//?清除本地存儲中的token,如果需要刷新token,在這里通過舊的token跟服務(wù)器換新token,將新的token設(shè)置的vuex中
        ????????if?(response.data.code?===?401)?{
        ??????????localStorage.removeItem("token");
        ??????????//?頁面刷新
        ??????????parent.location.reload();
        ????????}
        ????????//?只返回response中的data數(shù)據(jù)
        ????????return?response.data;
        ??????},
        ??????function(error)?{
        ????????if?(error)?{
        ??????????//?請求已發(fā)出,但不在2xx范圍內(nèi)
        ??????????errorHandle(error.status,?error.data.msg);
        ??????????return?Promise.reject(error);
        ????????}?else?{
        ??????????//?斷網(wǎng)
        ??????????return?Promise.reject(error);
        ????????}
        ??????}
        ????);
        ????//?將axios掛載到vue的全局屬性中
        ????app.config.globalProperties.$axios?=?_axios;
        ??}
        };

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

        不過上述將axios掛載到vue上是多此一舉的,因?yàn)槲乙呀?jīng)將api進(jìn)行了抽離,在每個(gè)單獨(dú)的api文件中都是通過導(dǎo)入我們封裝好的axios的配置文件,然后用導(dǎo)入進(jìn)來的axios實(shí)例來進(jìn)行的接口封裝。(ps: 之前由于自己太菜沒注意到這個(gè),傻傻的將其封裝成了插件?)

        那么,不需要將其封裝成插件的話,那它就屬于對axios進(jìn)行配置封裝了,我們將它放在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è)務(wù)代碼中通過this.$api.xx按模塊來調(diào)用我們拋出來的接口了。

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

        shims-vue.d.ts是一個(gè)Typescript的聲明文件,當(dāng)項(xiàng)目啟用ts后,有些文件是我們自己封裝的,類型較為復(fù)雜,ts不能推導(dǎo)出其具體類型,此時(shí)就需要我們進(jìn)行手動(dòng)聲明。

        例如上面我們掛載到原型上的$api,它導(dǎo)出了一個(gè)類文件,此時(shí)類型就較為復(fù)雜了,ts沒法推導(dǎo)出其類型,我們在使用時(shí)就會報(bào)錯(cuò)。

        image-20201010100416381

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

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

        注意:在shims-vue.d.ts文件中,類型聲明超過1個(gè)時(shí),組件內(nèi)需要import包就不能在其內(nèi)部進(jìn)行,需要將其寫在最外層,否則會報(bào)錯(cuò)。

        image-20201010101906448

        適配入口文件

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

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

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

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

        適配組件

        基礎(chǔ)設(shè)施完善后,接下來我們來適配組件,我們先來試試把2.x項(xiàng)目的所有組件搬過來看看,能不能直接啟動(dòng)。

        結(jié)果可想而知,無法運(yùn)行。因?yàn)槲矣昧?.x的插件,vue3.0有關(guān)插件的封裝,一些寫法變了。我項(xiàng)目中總共引用了2個(gè)插件v-viewer、vue-native-websocket,v-viewer這個(gè)插件無解,他底層使用用到的2.x語法太多了,所以我選擇放棄這個(gè)插件。vue-native-websocket這個(gè)插件就是使用的Vue.prototype.xx寫法被舍棄了,用新的寫法Vue.config.globalProperties.xx將其替換即可。

        image-20201009174402912

        替換完成后,重新編譯即可,隨后啟動(dòng)項(xiàng)目,如下所示,錯(cuò)誤解決,項(xiàng)目成功啟動(dòng)。

        image-20201009175415170

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

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

        組件優(yōu)化

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

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

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

        export?type?loginDataType?=?{
        ??loginUndo:?T;?//?禁止登錄時(shí)的圖標(biāo)
        ??loginBtnNormal:?T;?//?登錄時(shí)的按鈕圖標(biāo)
        ??loginBtnHover:?T;?//?鼠標(biāo)懸浮時(shí)的登錄圖標(biāo)
        ??loginBtnDown:?T;?//?鼠標(biāo)按下時(shí)的登錄圖標(biāo)
        ??userName:?string;?//?用戶名
        ??password:?string;?//?密碼
        ??confirmPassword:?string;?//?注冊時(shí)的確認(rèn)登錄密碼
        ??isLoginStatus:?number;?//?登錄狀態(tài):0.未登錄 1.登錄中 2.注冊
        ??loginStatusEnum:?Object;?//?登錄狀態(tài)枚舉
        ??isDefaultAvatar:?boolean;?//?頭像是否為默認(rèn)頭像
        ??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

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

        我們在enum文件夾中創(chuàng)建ComponentEnum.ts文件,組件內(nèi)用到的所有枚舉都會在此文件內(nèi)定義,接下來在組件內(nèi)創(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指向

        在適配組件過程中,方法內(nèi)部的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方法內(nèi)部的this應(yīng)該是指向_img的,但是ts并不這么認(rèn)為,報(bào)錯(cuò)如下所示。

        image-20201013171520088

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

        image-20201013171712449

        Dom對象類型定義

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

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

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

        然而,ts會報(bào)錯(cuò)altsrc屬性不存在,報(bào)錯(cuò)如下:

        image-20201013172815950

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

        image-20201019110053258

        復(fù)雜類型定義

        在適配組件過程中,遇到一個(gè)比較復(fù)雜的數(shù)據(jù)類型定義,數(shù)據(jù)如下:

        ?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

        嵌套到一起,自認(rèn)為沒問題,放進(jìn)代碼后,報(bào)錯(cuò)長度不匹配,這樣寫知識給第一個(gè)對象定義了類型。

        image-20201014214529652

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

        • 類型分開定義

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

          //?聯(lián)系人列表類型定義
          export?type?friendsListType?=?{
          ??groupName:?string;?//?分組名稱
          ??totalPeople:?number;?//?總?cè)藬?shù)
          ??onlineUsers:?number;?//?在線人數(shù)
          ??friendsData:?Array;?//?好友列表
          };

          //?聯(lián)系人類型定義
          export?type?friendsDataType?=?{
          ??username:?string;?//?昵稱
          ??avatarSrc:?string;?//?頭像地址
          ??signature:?string;?//?個(gè)性簽名
          ??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泛型的使用,經(jīng)驗(yàn)++?

        tag屬性被移除

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


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


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


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

        組件無法外鏈文件

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


        在組件中引用。




        然后,他就報(bào)錯(cuò)了,類型無法推斷。

        image-20201018224619607

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

        必須使用as進(jìn)行斷言

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

        image-20201018225114933

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

        image-20201018225618020

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

        image-20201018225919664

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

        ref數(shù)組不會自動(dòng)創(chuàng)建數(shù)組

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


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

        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{
        ????????????????//?第一次點(diǎn)擊添加transform屬性,旋轉(zhuǎn)90度
        ????????????????this.$refs.groupArrow[index].style.transform?=?"rotate(90deg)";
        ????????????????this.$refs.buddyList[index].style.display?=?"block";
        ????????????}
        ????????},
        ????????//?獲取列表好友信息
        ????????getBuddyInfo:function?(userId)?{
        ????????????//?判斷當(dāng)前路由params與當(dāng)前點(diǎn)擊項(xiàng)的userId是否相等
        ????????????if(!lodash.isEqual(this.$route.params.userId,userId)){
        ????????????????this.$router.push({name:?"dataPanel",?params:?{userId:?userId}}).then();
        ????????????}
        ????????}
        ????}
        }

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



        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            女人被狂躁到高潮30分钟 | 亚洲精品菠萝久久久久久久 | 99热成人 | 久久久久久久久久99精品 | 久久毛片基地 | 操逼挺好我看看 | 成人在线无码在线 | 欧美乱码熟妇色精精品 | 91视频久久久久 | 五月丁香六月婷 |