性能优化组件(动态组件component、异步组件、缓存组件keep-alive/Suspense/Teleport/Transition)

6 阅读4分钟

性能优化组件

详细介绍六个性能优化组件,其他组件官网自行学习

一.动态组件(<component :is=””></component>改变is属性绑定的值即可)

例如tab栏切换

父组件App.js

import Home from "./Home.js";
import Category from "./Category.js";
import Cart from "./Cart.js";
import My from "./My.js";
/*
 *  点击tab选项 内容区域切换为对应组件
 *    1. tab选项绑定点击事件
 *    2. 切换组件
 */

export default {
  components: {
    Home,
    Category,
    Cart,
    My,
  },

  data() {
    return {
      title: "动态组件",
      currentTab:'home',
      list:[
        {name:'home',title:'首页'},
        {name:'category',title:'分类'},
        {name:'cart',title:'购物车'},
        {name:'my',title:'我的'},
      ]
    };
  },

  methods: {
    onTabChange(tabName){
      this.currentTab = tabName
    }
  },

  /*html*/
  template: `<div class="g-container">
                 <div class="g-content">
                      <component :is="currentTab"></component>
                 </div>

                 <ul class="g-footer">
                    <li v-for="item in list" @click="onTabChange(item.name)" :class="{active:currentTab==item.name}">{{item.title}}</li>
                 </ul>
            </div>`,
};

样式文件style.css

*{padding: 0;margin: 0;}

ul,li{
    list-style: none;
}

.g-container{
    height: 100vh;
    width: 100%;
    display: flex;
    flex-direction: column;
}

.g-container .g-content{
    flex:1
}

.g-container .g-footer{
    height: 60px;
    background-color: skyblue;
    display: flex;
    justify-content: space-around;
    align-items: center;
}

.active{
   color: red;    
}

子组件Home.js(其他子组件同此组件)

export default {
  data() {
    return {
      title"首页",
    };
  },

  /*html*/
  template: `<div>
                  <h2>{{title}}</h2>
              </div>`,
};

二.异步组件(服务端定义的组件,通过网络异步获取到前端,再注册使用。通过defineAsyncComponent方法获取组件)

App.js

import { defineAsyncComponent } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";

/**
 *   异步组件: 服务端定义的组件,通过网络异步获取到前端,再注册使用
 *   同步组件: 前端客户端定义组件
 */

const AsyncChild = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    //模拟网络接口
    setTimeout(() => {
      //异步获取后端定义的异步组件
      const asyncComponent = {
        template`<p>我是异步组件</p>`,
      };

      resolve(asyncComponent);
    }, 2000);
  });
});

export default {
  components: {
    AsyncChild,
  },

  data() {
    return {
      title"异步组件",
    };
  },

  methods: {},

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>

                <p>---异步组件----</p>
                <async-child></async-child>
            </div>`,
};

三.内置组件--缓存组件keep-alive/Suspense/传送门Teleport/过渡动画Transition

直接使用,无需注册

1. 缓存组件,结合动态组件使用(<keep-alive include=””></keep-alive> 通过 include可以配置哪些组件需要缓存,需要注意的是,include中填写的并不是组件注册时的名称,是定义组件时name选项定义的组件名称)

注:存组件添加之后组件生命周期钩子函数也不会执行了,但是有另外两个钩子函数会执行:activated激活deactivated失活

使用示例:b组件,a组件会销毁,再次切换到a组件时组件会重新创建,但有时是不需要重新创建的,即切换回来原数据还存在。

父组件App.js

import Home from "./components/Home.js";
import Category from "./components/Category.js";
import Cart from "./components/Cart.js";
import My from "./components/My.js";
/**
 *  点击tab选项 内容区域切换为对应组件
 *    1. tab选项绑定点击事件
 *    2. 切换组件
 */
export default {
  name:'App',
  components: {
    Home,
    Category,
    Cart,
    My,
  },

  data() {
    return {
      title: "动态组件",
      currentTab: "home",
      list: [
        { name: "home", title: "首页" },
        { name: "category", title: "分类" },
        { name: "cart", title: "购物车" },
        { name: "my", title: "我的" },
      ],
    };
  },

  methods: {
    onTabChange(tabName) {
      this.currentTab = tabName;
    },
  },

  /*html*/
  template: `<div class="g-container">
                 <div class="g-content">
                      <!--数组写法-->
                      <!--<keep-alive :include="['Home','Category','Cart']">-->

                      <!--字符串写法-->
                      <keep-alive include="Home,Category,Cart">
                          <component :is="currentTab"></component>
                      </keep-alive>
                 </div>

                 <ul class="g-footer">
                    <li v-for="item in list" @click="onTabChange(item.name)" :class="{active:currentTab==item.name}">{{item.title}}</li>
                 </ul>
            </div>`,
};

子组件Home.js

export default {
  name:'Home',
  data() {
    return {
      title"首页",
    };
  },

  created() {
    console.log("home created ");
  },

  mounted() {
    console.log("home mounted ");
  },

  activated() {
    console.log("home activated ");
  },

  deactivated() {
    console.log("home deactivated ");
  },

  unmounted() {
    console.log("home unmounted ");
  },

  /*html*/

  template: `<div>
                  <h2>{{title}}</h2>
                  <input type="text" name="message">
              </div>`,

};

子组件Category.js

export default {
  name:'Category',
  data() {
    return {
      title"分类",
      list: [],
    };
  },
  
  created() {
    console.log("category created ");
  },

  mounted() {
    console.log("category mounted ");
  },

  activated() {
    console.log("category activated ");

    // 调用接口获取分类列表数据 fetch返回promise对象,

    fetch("https://api.yuguoxy.com/api/shop/list?pageSize=4")
      .then((response) => {
        return response.json();
      })
      .then((data) => {
        console.log(data);
        this.list = data.resultInfo.list;
      });
  },

  deactivated() {
    console.log("category deactivated ");
  },

  unmounted() {
    console.log("category unmounted ");
  },

  /*html*/
  template: `<div>
                  <h2>{{title}}</h2>
                  <ul>
                    <li v-for="item in list">
                       <img :src="item.picture" style="width:100px;"/>
                       <p>{{item.shop}}</p>
                    </li>
                  </ul>
              </div>`,
};

2.Suspense组件,结合插槽使用。(当网络请求时间较长,请求的内容暂时没有获取到。等待时渲染一个加载状态,获取到之后展示获取到的内容)

App.js

import { defineAsyncComponent } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";

/**
 *   异步组件: 服务端定义的组件,通过网络异步获取到前端,再注册使用
 *   同步组件: 前端客户端定义组件
 */

const AsyncChild = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    //模拟网络接口
    setTimeout(() => {
      //异步获取后端定义的异步组件
      const asyncComponent = {
        template: `<p>我是异步组件</p>`,
      };
      resolve(asyncComponent);
    }, 2000);
  });
});
 
export default {
  components: {
    AsyncChild,
  },

  data() {
    return {
      title: "异步组件",
    };
  },

  methods: {},
  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>

                <p>---异步组件----</p>
                <!-- Suspense 作用: 首先显示 名为fallback的插槽内容,当异步组件加载完成后,显示异步组件  -->

                <Suspense>
                     <async-child></async-child>

                     <template #fallback>
                         <p>加载中...</p>
                     </template>
                </Suspense>
            </div>`,
};

3.传送门Teleport

实际应用场景:没有传送门时嵌套的css样式太深(#app div box model),可以使用传送门减少嵌套层数(#app model)

父组件App.js

import Dialog from "./Dialog.js";

export default {
  components: {
    Dialog,
  },

  data() {
    return {
      title: "父组件",
      show: false, // 控制对话框隐藏显示
    };
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <button @click="show=true">添加用户</button>

                <!--传送到body标签下面-->
                <Teleport to="body">
                    <Dialog v-if="show" @closeDialog="show=false"></Dialog>
                </Teleport>
            </div>
            `,
};

子组件Dialog.js

export default {
  emits: ["closeDialog"], // 接收事件
  data() {
    return {};
  },

  methods: {
    bindConfirm(){
       // 1. 获取表单输入框内容
       // 2. 调用添加用户接口,保存用户数据到服务端
       // 3. 保存用户成功,关闭对话框
          this.$emit('closeDialog')
    }
  },

  /*html*/
  template: `<div class="box">
              <div class="modal">
                  <!-- header -->
                  <div class="header">
                      <p class="title">标题</p>
                      <p class="close" @click="$emit('closeDialog')">x</p>
                  </div>

                  <!-- 内容区域 -->
                  <div class="content">
                      <form>
                          <input type="text" name="username" placeholder="请输入用户名">  
                          <input type="text" name="password" placeholder="请输入密码">  
                      </form>
                  </div>

                  <!-- 底部区域 -->
                  <div class="footer">
                      <button @click="bindConfirm">确定</button>
                  </div>
              </div>
            </div>`,
};

4.过渡动画<Transition name=””>

没有定义name,样式默认v-;定义了name=”a”,样式为a-

.v-enter-active,

.v-leave-active {transition: opacity 0.5s ease;}

.v-enter-from,

.v-leave-to {opacity: 0;}

父组件App.js

import Dialog from "./Dialog.js";

export default {
  components: {
    Dialog,
  },

  data() {
    return {
      title: "父组件",
      show: true, // 控制对话框隐藏显示
    };
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <button @click="show=!show">切换</button>

                <!--过渡动画效果-->
                <Transition> 
                   <p v-if="show">过度动画效果</p>
                </Transition> 

                <Transition name="fade"> 
                   <Dialog v-if="show" @closeDialog="show=false"></Dialog>
                </Transition> 
            </div>
            `,
};

子组件Dialog.js

export default {
  emits: ["closeDialog"], // 接收事件
  data() {
    return {};
  },

  methods: {
    bindConfirm(){
       // 1. 获取表单输入框内容
       // 2. 调用添加用户接口,保存用户数据到服务端
       // 3. 保存用户成功,关闭对话框
          this.$emit('closeDialog')
    }
  },

  /*html*/
  template: `<div class="box">
              <div class="modal">

                  <!-- header -->
                  <div class="header">
                      <p class="title">标题</p>
                      <p class="close" @click="$emit('closeDialog')">x</p>
                  </div>

                  <!-- 内容区域 -->

                  <div class="content">
                      <form>
                          <input type="text" name="username" placeholder="请输入用户名">  
                          <input type="text" name="password" placeholder="请输入密码">  
                      </form>

                  </div>

                  <!-- 底部区域 -->
                  <div class="footer">
                      <button @click="bindConfirm">确定</button>
                  </div>
              </div>
            </div>`,
};

样式style.css

/* 下面我们会解释这些 class 是做什么的 */

.v-enter-active,

.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,

.v-leave-to {
  opacity: 0;
}


.fade-enter-active,

.fade-leave-active {
  transition: opacity 1s ease;
}

.fade-enter-from,

.fade-leave-to {
  opacity: 0;
}