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

          如何優(yōu)化 Vue.js項(xiàng)目

          共 18008字,需瀏覽 37分鐘

           ·

          2023-10-14 04:14

          單頁(yè)面應(yīng)用(SPAs)當(dāng)處理實(shí)時(shí)、異步數(shù)據(jù)時(shí),可以提供豐富的、可交互的用戶體驗(yàn)。但它們也可能很重,很臃腫,而且性能很差。在這篇文章中,我們將介紹一些前端優(yōu)化技巧,以保持我們的Vue應(yīng)用程序相對(duì)精簡(jiǎn),并且只在需要的時(shí)候提供必需的JS。

          注意:這里假設(shè)你對(duì)Vue和Composition API有一定的熟悉程度,但無(wú)論你選擇哪種框架,都希望能有一些收獲。

          本文作者是一名前端開(kāi)發(fā)工程師,職責(zé)是構(gòu)建Windscope應(yīng)用程序。下面介紹基于該程序所做的一系列優(yōu)化。

          選擇框架

          我們選擇的JS框架是Vue,部分原因是它是我最熟悉的框架。以前,Vue與React相比,整體包規(guī)模較小。然而,自從最近的React更新以來(lái),平衡似乎已經(jīng)轉(zhuǎn)移到React身上。這并不重要,因?yàn)槲覀儗⒃诒疚闹醒芯咳绾沃粚?dǎo)入我們需要的東西。這兩個(gè)框架都有優(yōu)秀的文檔和龐大的開(kāi)發(fā)者生態(tài)系統(tǒng),這是另一個(gè)考慮因素。Svelte是另一個(gè)可能的選擇,但由于不熟悉,它需要更陡峭的學(xué)習(xí)曲線,而且由于較新,它的生態(tài)系統(tǒng)不太發(fā)達(dá)。

          Vue Composition API

          Vue 3引入了Composition API,這是一套新的API用于編寫(xiě)組件,作為Options API的替代。通過(guò)專門(mén)使用Composition API,我們可以只導(dǎo)入我們需要的Vue函數(shù),而不是整個(gè)包。它還使我們能夠使用組合式函數(shù)編寫(xiě)更多可重用的代碼。使用Composition API編寫(xiě)的代碼更適合于最小化,而且整個(gè)應(yīng)用程序更容易受到tree-shaking的影響。

          注意:如果你正在使用較老版本的Vue,仍然可以使用Composition API:它已被補(bǔ)丁到Vue 2.7,并且有一個(gè)適用于舊版本的官方插件。

          導(dǎo)入依賴

          一個(gè)關(guān)鍵目標(biāo)是減少通過(guò)客戶端下載的初始JS包的尺寸。Windscope廣泛使用D3進(jìn)行數(shù)據(jù)可視化,這是一個(gè)龐大的庫(kù),范圍很廣。然而,Windscope只需要使用D3的一部分。

          讓我們的依賴包盡可能小的一個(gè)最簡(jiǎn)單的方法是,只導(dǎo)入我們需要的模塊。

          讓我們來(lái)看看D3的selectAll函數(shù)。我們可以不使用默認(rèn)導(dǎo)入,而只從d3-selection模塊中導(dǎo)入我們需要的函數(shù):

                
                // Previous:
          import * as d3 from 'd3'

          // Instead:
          import { selectAll } from 'd3-selection'

          代碼分割

          有一些包在整個(gè)Windscope的很多地方都有使用,比如AWS Amplify認(rèn)證庫(kù),特別是Auth方法。這是一個(gè)很大的依賴,對(duì)我們的JS包的大小有很大貢獻(xiàn)。比起在文件頂部靜態(tài)導(dǎo)入模塊,動(dòng)態(tài)導(dǎo)入允許我們?cè)诖a中需要的地方準(zhǔn)確導(dǎo)入模塊。

          比起這么導(dǎo)入:

                
                import { Auth } from '@aws-amplify/auth'

          const user = Auth.currentAuthenticatedUser()

          我們可以在想要使用它的地方導(dǎo)入模塊:

                
                import('@aws-amplify/auth').then(({ Auth }) => {
              const user = Auth.currentAuthenticatedUser()
          })

          這意味著該模塊將被分割成一個(gè)單獨(dú)的JS包(或 "塊"),只有該模塊被使用時(shí),才會(huì)被瀏覽器下載。

          除此之外,瀏覽器可以緩存這些依賴,比起應(yīng)用程序的其他部分代碼,這些模塊基本不會(huì)改變。

          懶加載

          我們的應(yīng)用程序使用Vue Router作為導(dǎo)航路由。與動(dòng)態(tài)導(dǎo)入類似,我們可以懶加載我們的路由組件,這樣就可以在用戶導(dǎo)航到路由時(shí),它們才會(huì)被導(dǎo)入(連同其相關(guān)的依賴關(guān)系)。

          index/router.js文件:

                
                // Previously:
          import Home from "../routes/Home.vue";
          import About = "../routes/About.vue";

          // Lazyload the route components instead:
          const Home = () => import("../routes/Home.vue");
          const About = () => import("../routes/About.vue");

          const routes = [
            {
              name"home",
              path"/",
              component: Home,
            },
            {
              name"about",
              path"/about",
              component: About,
            },
          ];

          當(dāng)用戶點(diǎn)擊About鏈接并導(dǎo)航到路由時(shí),About路由所對(duì)應(yīng)的代碼才會(huì)被加載。

          異步組件

          除了懶加載每個(gè)路由外,我們還可以使用Vue的defineAsyncComponent方法懶加載單個(gè)組件。

                
                const KPIComponent = defineAsyncComponent(() => import('../components/KPI.vue'))

          這意味著KPI組件的代碼會(huì)被異步導(dǎo)入,正如我們?cè)诼酚墒纠锌吹降哪菢印.?dāng)組件正在加載或者處于錯(cuò)誤狀態(tài)時(shí),我們也可以提供展示的組件(這個(gè)在加載特別大的文件時(shí)非常有用)。

                
                const KPIComponent = defineAsyncComponent({
            loader() => import('../components/KPI.vue'),
            loadingComponent: Loader,
            errorComponentError,
            delay200,
            timeout5000,
          });

          分割A(yù)PI請(qǐng)求

          我們的應(yīng)用程序主要關(guān)注的是數(shù)據(jù)可視化,并在很大程度上依賴于從服務(wù)器獲取大量的數(shù)據(jù)。其中一些請(qǐng)求可能相當(dāng)慢,因?yàn)榉?wù)器必須對(duì)數(shù)據(jù)進(jìn)行一些計(jì)算。在最初的原型中,我們對(duì)每個(gè)路由的REST API進(jìn)行了一次請(qǐng)求。不幸地是,我們發(fā)現(xiàn)這會(huì)導(dǎo)致用戶必須等待很長(zhǎng)時(shí)間。

          我們決定將API分成幾個(gè)端點(diǎn),為每個(gè)部件發(fā)出請(qǐng)求。雖然這可能會(huì)增加整體的響應(yīng)時(shí)間,但這意味著應(yīng)用程序應(yīng)該更快可用,因?yàn)橛脩魧⒖吹巾?yè)面的一部分被渲染,而他們?nèi)栽诘却渌糠帧4送猓魏慰赡馨l(fā)生的錯(cuò)誤都會(huì)被本地化,而頁(yè)面的其他部分仍然可以使用。

          有條件加載組件

          現(xiàn)在我們可以把它和異步組件結(jié)合起來(lái),只在我們收到服務(wù)器的成功響應(yīng)時(shí)才加載一個(gè)組件。下面示例中我們獲取數(shù)據(jù),然后在fetch函數(shù)成功返回時(shí)導(dǎo)入組件:

                
                <template>
            <div>
              <component :is="KPIComponent" :data="data"></component>
            </div>

          </template>

          <script>
          import {
            defineComponent,
            ref,
            defineAsyncComponent,
          } from "vue";
          import Loader from "./
          Loader";
          import Error from "
          ./Error";

          export default defineComponent({
              components: { Loader, Error },

              setup() {
                  const data = ref(null);

                  const loadComponent = () => {
                    return fetch('<https://api.npoint.io/ec46e59905dc0011b7f4>')
                      .then((response) => response.json())
                      .then((response) => (data.value = response))
                      .then(() => import("
          ../components/KPI.vue") // Import the component
                      .catch((e) => console.error(e));
                  };

                  const KPIComponent = defineAsyncComponent({
                    loader: loadComponent,
                    loadingComponent: Loader,
                    errorComponent: Error,
                    delay: 200,
                    timeout: 5000,
                  });

                  return { data, KPIComponent };
              }
          }

          該模式可以擴(kuò)展到應(yīng)用程序的任意地方,組件在用戶交互后進(jìn)行渲染。比如說(shuō),當(dāng)用戶點(diǎn)擊Map標(biāo)簽時(shí),加載map組件以及相關(guān)依賴。

          CSS

          除了動(dòng)態(tài)導(dǎo)入JS模塊外,在組件的<style>塊中導(dǎo)入依賴也會(huì)懶加載CSS:

                
                // In MapView.vue
          <style>
          @import "../../node_modules/leaflet/dist/leaflet.css";

          .map-wrapper {
            aspect-ratio: 16 / 9;
          }
          </style>

          完善加載狀態(tài)

          在這一點(diǎn)上,我們的API請(qǐng)求是并行運(yùn)行的,組件在不同時(shí)間被渲染。可能會(huì)注意到一件事,那就是頁(yè)面看起來(lái)很糟糕,因?yàn)椴季謺?huì)有很大的變化。

          一個(gè)讓用戶感覺(jué)更順暢的快速方法,是在部件上設(shè)置一個(gè)與渲染的組件大致對(duì)應(yīng)的長(zhǎng)寬比,這樣用戶就不會(huì)看到那么大的布局變化。我們可以傳入一個(gè)參數(shù)以考慮到不同的組件,并用一個(gè)默認(rèn)值來(lái)回退。

                
                // WidgetLoader.vue
          <template>
            <div class="widget" :style="{ 'aspect-ratio': loading ? aspectRatio : '' }">
              <component :is="AsyncComponent" :data="data"></component>
            </div>

          </template>

          <script>
          import { defineComponent, ref, onBeforeMount, onBeforeUnmount } from "vue";
          import Loader from "./
          Loader";
          import Error from "
          ./Error";

          export default defineComponent({
            components: { Loader, Error },

            props: {
              aspectRatio: {
                type: String,
                default: "
          5 / 3", // define a default value
              },
              url: String,
              importFunction: Function,
            },

            setup(props) {
                const data = ref(null);
                const loading = ref(true);

                  const loadComponent = () => {
                    return fetch(url)
                      .then((response) => response.json())
                      .then((response) => (data.value = response))
                      .then(importFunction
                      .catch((e) => console.error(e))
                      .finally(() => (loading.value = false)); // Set the loading state to false
                  };

              /* ...Rest of the component code */

              return { data, aspectRatio, loading };
            },
          });
          </script>

          取消API請(qǐng)求

          在一個(gè)有大量API請(qǐng)求的頁(yè)面上,如果用戶在所有請(qǐng)求還沒(méi)有完成時(shí)離開(kāi)頁(yè)面,會(huì)發(fā)生什么?我們可能不想這些請(qǐng)求繼續(xù)在后臺(tái)運(yùn)行,拖慢了用戶體驗(yàn)。

          我們可以使用AbortController接口,這使我們能夠根據(jù)需要中止API請(qǐng)求。

          setup函數(shù)中,我們創(chuàng)建一個(gè)新的controller,并傳遞signalfetch請(qǐng)求參數(shù)中:

                
                setup(props) {
              const controller = new AbortController();

              const loadComponent = () => {
                return fetch(url, { signal: controller.signal })
                  .then((response) => response.json())
                  .then((response) => (data.value = response))
                  .then(importFunction)
                  .catch((e) => console.error(e))
                  .finally(() => (loading.value = false));
                  };
          }

          然后我們使用Vue的onBeforeUnmount函數(shù),在組件被卸載之前中止請(qǐng)求:

                
                onBeforeUnmount(() => controller.abort());

          如果你運(yùn)行該項(xiàng)目并在請(qǐng)求完成之前導(dǎo)航到另一個(gè)頁(yè)面,你應(yīng)該看到控制臺(tái)中記錄的錯(cuò)誤,說(shuō)明請(qǐng)求已經(jīng)被中止。

          Stale While Revalidate

          目前為止,我們已經(jīng)做了相當(dāng)好的一部分優(yōu)化。但是當(dāng)用戶前往下個(gè)頁(yè)面后,然后返回上一頁(yè),所有的組件都會(huì)重新掛載,并返回自身的加載狀態(tài),我們又必須再次等待請(qǐng)求有所響應(yīng)。

          Stale-while-revalidate是一種HTTP緩存失效策略,瀏覽器決定是在內(nèi)容仍然新鮮的情況下從緩存中提供響應(yīng),還是在響應(yīng)過(guò)期的情況下"重新驗(yàn)證 "并從網(wǎng)絡(luò)上提供響應(yīng)。

          除了在我們的HTTP響應(yīng)中應(yīng)用cache-control頭部(不在本文范圍內(nèi),但可以閱讀Web.dev的這篇文章以了解更多細(xì)節(jié)),我們可以使用SWRV庫(kù)對(duì)我們的Vue組件狀態(tài)應(yīng)用類似的策略。

          首先,我們必須從SWRV庫(kù)中導(dǎo)入組合式內(nèi)容:

                
                import useSWRV from "swrv";

          然后,我們可以在setup函數(shù)使用它。我們把loadComponent函數(shù)改名為fetchData,因?yàn)樗鼘⒅惶幚頂?shù)據(jù)的獲取。我們將不再在這個(gè)函數(shù)中導(dǎo)入我們的組件,因?yàn)槲覀儗为?dú)處理這個(gè)問(wèn)題。

          我們將把它作為第二個(gè)參數(shù)傳入useSWRV函數(shù)調(diào)用。只有當(dāng)我們需要一個(gè)自定義函數(shù)來(lái)獲取數(shù)據(jù)時(shí),我們才需要這樣做(也許我們需要更新一些其他的狀態(tài)片段)。因?yàn)槲覀兪褂玫氖茿bort Controller,所以我們要這樣做;否則,第二個(gè)參數(shù)可以省略,SWRV將使用Fetch API:

                
                // In setup()
          const { url, importFunction } = props;

          const controller = new AbortController();

          const fetchData = () => {
            return fetch(url, { signal: controller.signal })
              .then((response) => response.json())
              .then((response) => (data.value = response))
              .catch((e) => (error.value = e));
          };

          const { data, isValidating, error } = useSWRV(url, fetchData);

          然后我們將從我們的異步組件定義中刪除loadingComponenterrorComponent選項(xiàng),因?yàn)槲覀儗⑹褂肧WRV來(lái)處理錯(cuò)誤和加載狀態(tài)。

                
                // In setup()
          const AsyncComponent = defineAsyncComponent({
            loader: importFunction,
            delay200,
            timeout5000,
          });

          這意味著,我們需要在模板文件中包含LoaderError組件,展示或隱藏取決于狀態(tài)。isValidating的返回值告訴我們是否有一個(gè)請(qǐng)求或重新驗(yàn)證發(fā)生。

                
                <template>
            <div>
              <Loader v-if="isValidating && !data"></Loader>
              <Error v-else-if="error" :errorMessage="error.message"></Error>
              <component :is="AsyncComponent" :data="data" v-else></component>
            </div>

          </template>

          <script>
          import {
            defineComponent,
            defineAsyncComponent,
          } from "vue";
          import useSWRV from "swrv";

          export default defineComponent({
            components: {
              Error,
              Loader,
            },

            props: {
              url: String,
              importFunction: Function,
            },

            setup(props) {
              const { url, importFunction } = props;

              const controller = new AbortController();

              const fetchData = () => {
                return fetch(url, { signal: controller.signal })
                  .then((response) => response.json())
                  .then((response) => (data.value = response))
                  .catch((e) => (error.value = e));
              };

              const { data, isValidating, error } = useSWRV(url, fetchData);

              const AsyncComponent = defineAsyncComponent({
                loader: importFunction,
                delay: 200,
                timeout: 5000,
              });

              onBeforeUnmount(() => controller.abort());

              return {
                AsyncComponent,
                isValidating,
                data,
                error,
              };
            },
          });
          </
          script>

          我們可以將其重構(gòu)為自己的組合式代碼,使我們的代碼更簡(jiǎn)潔一些,并使我們能夠在任何地方使用它。

                
                // composables/lazyFetch.js
          import { onBeforeUnmount } from "vue";
          import useSWRV from "swrv";

          export function useLazyFetch(url{
            const controller = new AbortController();

            const fetchData = () => {
              return fetch(url, { signal: controller.signal })
                .then((response) => response.json())
                .then((response) => (data.value = response))
                .catch((e) => (error.value = e));
            };

            const { data, isValidating, error } = useSWRV(url, fetchData);

            onBeforeUnmount(() => controller.abort());

            return {
              isValidating,
              data,
              error,
            };
          }
                
                // WidgetLoader.vue
          <script>
          import { defineComponent, defineAsyncComponent, computed } from "vue";
          import Loader from "./Loader";
          import Error from "./Error";
          import { useLazyFetch } from "../composables/lazyFetch";

          export default defineComponent({
            components: {
              Error,
              Loader,
            },

            props: {
              aspectRatio: {
                typeString,
                default"5 / 3",
              },
              urlString,
              importFunctionFunction,
            },

            setup(props) {
              const { aspectRatio, url, importFunction } = props;
              const { data, isValidating, error } = useLazyFetch(url);

              const AsyncComponent = defineAsyncComponent({
                loader: importFunction,
                delay200,
                timeout5000,
              });

              return {
                aspectRatio,
                AsyncComponent,
                isValidating,
                data,
                error,
              };
            },
          });
          </script>

          更新指示

          如果我們能在我們的請(qǐng)求重新驗(yàn)證的時(shí)候向用戶顯示一個(gè)指示器,這樣他們就知道應(yīng)用程序正在檢查新的數(shù)據(jù),這可能會(huì)很有用。在這個(gè)例子中,我在組件的角落里添加了一個(gè)小的加載指示器,只有在已經(jīng)有數(shù)據(jù),但組件正在檢查更新時(shí)才會(huì)顯示。我還在組件上添加了一個(gè)簡(jiǎn)單的fade-in過(guò)渡(使用Vue內(nèi)置的Transition組件),所以當(dāng)組件被渲染時(shí),不會(huì)有突兀的跳躍。

                
                <template>
            <div
              class="widget"
              :style="{ 'aspect-ratio': isValidating && !data ? aspectRatio : '' }"
            >

              <Loader v-if="isValidating && !data"></Loader>
              <Error v-else-if="error" :errorMessage="error.message"></Error>
              <Transition>
                  <component :is="AsyncComponent" :data="data" v-else></component>
              </Transition>

              <!--Indicator if data is updating-->
              <Loader
                v-if="isValidating && data"
                text=""
              >
          </Loader>
            </div>

          </template>

          總結(jié)

          在建立我們的網(wǎng)絡(luò)應(yīng)用程序時(shí),優(yōu)先考慮性能,可以提高用戶體驗(yàn),并有助于確保它們可以被盡可能多的人使用。我希望這篇文章提供了一些關(guān)于如何使你的應(yīng)用程序盡可能高效的觀點(diǎn)--無(wú)論你是選擇全部還是部分地實(shí)施它們。

          SPA可以工作得很好,但它們也可能成為性能瓶頸。所以,讓我們?cè)囍阉鼈冏兊酶谩?/p>

          • 本文譯自:https://www.smashingmagazine.com/2022/11/optimizing-vue-app/
          • 作者:Michelle Barker

          以上就是本文的全部?jī)?nèi)容,如果幫助到了你,歡迎點(diǎn)贊、收藏、轉(zhuǎn)發(fā)~


          瀏覽 82
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  天天操天天添 | 亚洲午夜精品 | 成年人性爱视频网站 | 亚洲特黄片 | 国内自拍网 |