vue3 性能优化技巧

196 阅读2分钟

懒加载

Suspense搭配async函数的setup

父组件

<template>
  <div class="app">
    <h3>我是App组件</h3>
    <Suspense>
      <template v-slot:default>
        <Child />
      </template>
      <template v-slot:fallback>
        <h3>稍等,加载中...</h3>
      </template>
    </Suspense>
  </div>
</template>
 
<script>
// import Child from './components/Child'//静态引入
import { defineAsyncComponent } from "vue";
const Child = defineAsyncComponent(() => import("./components/Child")); //异步引入
export default {
  name: "App",
  components: { Child },
};
</script>
 
<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>

子组件

<template>
  <div class="child">
    <h3>我是Child组件</h3>
    {{ sum }}
  </div>
</template>
 
<script>
import { ref } from "vue";
export default {
  name: "Child",
  async setup() {
    let sum = ref(0);
    let p = new Promise((resolve) => {
      setTimeout(() => {
        resolve({ sum });
      }, 3000);
    });
    return await p;
  },
};
</script>
 
<style>
.child {
  background-color: skyblue;
  padding: 10px;
}
</style>

路由懒加载

const routes = [
  {
    path: '/home',
    component: () => import('./Home.vue')
  },
  // 其他路由...
];

路由预加载

const routes = [
  {
    path: '/home',
    component: () => import('./Home.vue'),
    meta: { preload: true }
  },
  // 其他路由...
];

异步组件

const AsyncFooWithOptions = defineAsyncComponent({
  // 加载函数
  loader: () => import("./demo.vue"),
  //加载过程中的组件
  loadingComponent: LoadingComponent,
  //加载失败的组件
  errorComponent: ErrorComponent,
  // 在显示loadingComponent组件之前, 等待多长时间
  delay: 200,
  //加载组件的超时时间,如果超过这个值,则显示错误组件, 默认Infinity永不超时, 单位ms
  timeout: 3000//定义组件是否可以挂起, 默认true
  suspensible:true,
  /** 异步组件加载失败的回调函数
   * err: 错误信息,
   * retry: 函数, 调用retry尝试重新加载
   * fail: 函数, 指示加载程序结束退出
   * attempts: 记录尝试的次数
   */
  onError: function(err, retry, fail, attempts) {
  }
})

缓存路由组件

vue-router

//使用vue-router时,切换页面时缓存页面
  {
    path: '/',
    component: () => import('./views/index'),
    name: 'index',
    meta: {
      title: '首页',
      keepAlive: true,
    },
  },

vue文件

一种写法

 <template>
  <router-view v-slot="{ Component }">
    <keep-alive>
      <component :is="Component" v-if="$route.meta.keepAlive" :key="$route.name"></component>
    </keep-alive>
    <component :is="Component" v-if="!$route.meta.keepAlive" :key="$route.name">></component>
  </router-view>
</template>

另一种写法

 <template>
  <div>vue-router小例子</div>
  <div>router-link</div>
  <router-view v-slot="{ Component }">
    <keep-alive :include="KeepAliveList">
      <component :is="Component"></component>
    </keep-alive>
  </router-view>
</template>

<script>
export default {
  name: 'App',
  data() {
    return { KeepAliveList: [] }
  },
  watch: {
    $route(to) {
      //监听路由变化,把配置路由中keepAlive为true的name添加到include动态数组中
      if (to.meta.keepAlive && this.KeepAliveList.indexOf(to.name) === -1) {
        this.KeepAliveList.push(to.name)
      }
    },
  },
}
</script>

effectScope

用于收集在其中所创建的副作用,并能对其进行统一的处理

使用场景

避免随着组件生命周期重复创建某些监听

// 使用到的组件都会重复创建监听器
function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function handler(e) {
    x.value = e.x
    y.value = e.y
  }

  window.addEventListener('mousemove', handler)

  onUnmounted(() => {
    window.removeEventListener('mousemove', handler)
  })

  return { x, y }
}

通过effecScope创建独立的scope

function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function handler(e) {
    x.value = e.x
    y.value = e.y
  }

  window.addEventListener('mousemove', handler)
  // 通过onScopeDispose替换onUnmounted,意味着可以脱离组件使用
  onScopeDispose(() => {
    window.removeEventListener('mousemove', handler)
  })

  return { x, y }
}

function createSharedComposable(composable) {
  let subscribers = 0
  let state, scope

  const dispose = () => {
    // 通过闭包进行计数,当subscribers为0时,stop掉该scope
    // 如果在组件中使用,则onUnmounted就意味着subscribers-1
    if (scope && --subscribers <= 0) {
      scope.stop()
      state = scope = null
    }
  }

  return (...args) => {
    subscribers++
    if (!state) {
      scope = effectScope(true)
      state = scope.run(() => composable(...args))
    }
    onScopeDispose(dispose)
    return state
  }
}

const useSharedMouse = createSharedComposable(useMouse)

export default useSharedMouse

简易的状态管理

import { effectScope } from '@vue/composition-api'
export default run => {
  let isChange = false
  let state
  const scope = effectScope(true)
  return () => {
    // 防止重复触发
    if (!isChange) {
      state = scope.run(run)
      isChange = true
    }
    return state
  }
}

// store.js
import { computed, ref } from '@vue/composition-api'
import useGlobalState from './useGlobalState'
export default useGlobalState(
  () => {
    // state
    const count = ref(0)
    // getters
    const doubleCount = computed(() => count.value * 2)
    // actions
    function increment() {
      count.value++
    }
    return { count, doubleCount, increment }
  }
)

// A.vue
import useStore from '@/hooks/useStore'
const { count, doubleCount, increment } = useStore()
// B.vue
import useStore from '@/hooks/useStore'
const { count, doubleCount, increment } = useStore()