Vue3 encapsulates global functional components


foreword

I believe that you often use component-based development when considering reuse logic in Vue, and you must have used functional components, which are components that can also be imported and called in js. So how to encapsulate such a functional component, this article will use the Toast component to briefly introduce the encapsulation method. After encapsulation, we can greatly improve the efficiency of our development.


1. What are functional components?

Briefly introduce declarative components and functional components. Most of the time we introduce components in a declarative way. Here we take the Vant component library as an example. Button buttons are declarative components:

<van-button type="primary">主要按钮</van-button>

<TheWelcome />There is also a declarative component that has a custom name like this and references other .vue files in the .vue file

<template>
  <main>
    <TheWelcome />
  </main>
</template>

<script setup lang="ts">
import TheWelcome from '../components/TheWelcome.vue';
</script>

The functional component is to quickly invoke the global component by calling the API, or take the Vant component library as an example, such as using the Toast component, after calling the function, the corresponding light prompt will be rendered directly on the page:

import {
    
     showToast } from 'vant';

showToast('提示内容');

Usually we use functional components to trigger when an interaction is completed, or to 非.vue文件evoke global components in it, such as encapsulating axios, and using Toast components in axios.js to display error messages:

showToast('服务器响应超时,请刷新当前页');

2. Create a functional component

The following will create a self-defined toast component. Since this toast component is displayed successfully by default, it is called "okToast". First, let's show the effect of the call:
insert image description here

1. Encapsulate the toast component

Consistent with creating a declarative component, the parameters received by the component and the style of the component are defined in the .vue file. The code is as follows (example):

<template>
  <!-- 加一点动画效果 -->
  <transition name="toast" @after-leave="onAfterLeave">
    <div class="toast" v-if="isShow" :style="{ width: toastWidth }">
      <!-- 手动点击隐藏弹窗 -->
      <div v-if="time < 0" class="cancel" @click="hidden"></div>
      <img
        v-if="type === 'success' || type === 'icon'"
        class="img"
        src="../../assets/images/[email protected]"
        alt="success"
      />
      <img v-if="type === 'warn'" class="img" src="../../assets/images/7vip_web_toast_warn.png" alt="warn" />
      <div v-if="content && type !== 'icon'" class="content" :style="{ textAlign }">{
   
   { content }}</div>
    </div>
  </transition>
</template>
<script setup>
  import {
      
       ref, computed } from "vue";

  const props = defineProps({
      
      
    //文案内容,默认success
    content: {
      
      
      type: String,
      default: "success",
    },
    //显示时间,默认2s,传小于0的值不自动消失,需要手动关闭
    time: {
      
      
      type: Number,
      default: 2000,
    },
    //宽度,默认310px,这里考虑传入的宽度可以用字符串也可以用数值,所以没有定义类型
    width: {
      
      
      default: 310,
    },
    //弹窗文案文本对齐方式,默认center
    textAlign: {
      
      
      type: String,
      default: "center",
    },
    //类型,默认图标(√),传'warn'显示(!),传其他值则不显示icon,传'icon'不显示文本
    type: {
      
      
      type: String,
      default: "success",
    },
    //接收的函数方法
    hide: {
      
      
      type: Function,
      default: () => {
      
      },
    },
  });
  // 弹窗显隐控制
  const isShow = ref(false);
  // 宽度控制,由于设计稿宽度是750px的宽度,这里通过计算属性,根据设备屏幕宽度自适应显示弹窗的宽度
  const toastWidth = computed(() => (parseInt(props.width.toString()) / 750) * document.documentElement.clientWidth + "px");
  // 显示弹窗方法
  const show = () => {
      
      
    isShow.value = true;
    if (props.time >= 0) {
      
      
      setTimeout(() => {
      
      
        isShow.value = false;
      }, props.time);
    }
  };
  // 隐藏弹窗方法
  const hidden = () => {
      
      
    isShow.value = false;
  };
  // 弹窗关闭后等动画结束再调用卸载逻辑
  const onAfterLeave = () => {
      
      
	props.hide();
  };
  // 将显示弹窗方法暴露出去
  defineExpose({
      
      
    show,
  });
</script>

<style lang="scss" scoped>
  .toast-enter-active,
  .toast-leave-active {
      
      
    transition: opacity 0.3s ease-out;
  }
  .toast-enter-from,
  .toast-leave-to {
      
      
    opacity: 0;
  }
  .toast {
      
      
    position: fixed;
    top: 45%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 99;
    background: #333333;
    border-radius: 20px;
    padding: 40px;
    text-align: center;
    .cancel {
      
      
      background: url("../../assets/images/[email protected]") no-repeat center / contain;
      position: absolute;
      top: 10px;
      right: 10px;
      width: 40px;
      height: 40px;
      &::before {
      
      
        content: "";
        position: absolute;
        top: -10px;
        right: -10px;
        bottom: -10px;
        left: -10px;
      }
    }
    .img {
      
      
      width: 80px;
      height: 80px;
    }
    .content {
      
      
      margin-top: 20px;
      font-size: 32px;
      color: #ffcc99;
      line-height: 30px;
      text-align: initial;
    }
  }
</style>

2. Create an application instance

This is the most critical step. In Vue2, the encapsulation of functional components is to use Vue.extendthis basic Vue constructor to create Vue subclass instances. However, this method is officially deleted in Vue3, but a new API is also provided. : createAppFor us to use, use createApp to create a Vue application instance. The code is as follows (example):

import {
    
     createApp } from "vue";
import OkToast from "./okToast.vue";

const okToast = options => {
    
    
  // 创建元素节点
  const rootNode = document.createElement("div");
  // 在body标签内部插入此元素
  document.body.appendChild(rootNode);
  // 创建应用实例(第一个参数是根组件。第二个参数可选,它是要传递给根组件的 props)
  const app = createApp(OkToast, {
    
    
    ...options,
    hide() {
    
    
      // 卸载已挂载的应用实例
      app.unmount();
      // 删除rootNode节点
      document.body.removeChild(rootNode);
    },
  });
  // 将应用实例挂载到创建的 DOM 元素上
  return app.mount(rootNode);
};

// 注册插件app.use()会自动执行install函数
okToast.install = app => {
    
    
  // 注册全局属性,类似于 Vue2 的 Vue.prototype
  app.config.globalProperties.$okToast = options => okToast(options).show();
};
// 定义show方法用于直接调用
okToast.show = options => okToast(options).show();

export default okToast;

3. Register plugin (can be omitted)

The code is as follows (example):

// main.js
import okToast from './plugins/okToast/index';

app.use(okToast);

Q&A: add some notes

①: Why use the method of calling functions to control the display and concealment

Answer: The purpose is for the animation effect of displaying and disappearing. When the component is created, the "isShow" in the component needs to change to trigger the <Transition>animation effect, so the show function method is written here.

②: The connection between the two files of the functional component

Answer: To put it simply, js files pass parameters and functions to vue files, which can be createApppassed in the second parameter. Vue files are equivalent to subcomponents and are received in the form of props; vue files pass values ​​and functions to js files. It can be exposed through defineExposethe method. After the application instance is created in the js file, the exposed properties and methods can be obtained.

3. Call

1. Get the global method in the .vue file after registering the plugin

<script setup>
import {
    
     getCurrentInstance } from 'vue';

// 获取当前实例,在当前实例相当于 vue2 中的 this
const {
    
     proxy }: any = getCurrentInstance();
// 最简单的调用方式,即可出来开头所展示的效果
proxy.$okToast();
// 传递自定义参数,与okToast.vue文件接收的参数对应
setTimeout(() => {
    
    
  proxy.$okToast({
    
    
	content: 'Hello World'
  });
}, 2000);
</script>

2. You can call the method directly in the .vue or .js file without registering the plug-in

import $okToast from "./plugs/okToast";

$okToast.show({
    
    
  type: "warn",
  content: "Network error,try again later",
});

4. Optimization and improvement

When multiple instances of the Toast component encapsulated above are created, they do not interfere with each other, and there will be no component parameter exceptions. Then actually observing the DOM elements, we will find that there are multiple ones on the DOM, but when called multiple times, the later ones will cover the previous Toast that has not disappeared, so the effect may not be so friendly. Then there are two optimization directions: one is to end the previous Toast when the subsequent Toast appears, and the other is to adjust the position where the subsequent Toast appears.

1. Singleton mode (recommended)

First up the code (example):

let rootNode = null;
let app = null;
const okToast = options => {
    
    
  const dom = document.body.querySelector('.my-ok-toast');
  if (!dom) {
    
    
    rootNode = document.createElement('div');
    // 给创建的元素设置 class 属性值
    rootNode.className = `my-ok-toast`;
    document.body.appendChild(rootNode);
  } else {
    
    
    // If you want to mount another app on the same host container, you need to unmount the previous app by calling `app.unmount()` first.
    app.unmount();
  }
  app = createApp(OkToast, {
    
    
    ...options,
    hide() {
    
    
      // 卸载已挂载的应用实例
      if (app) {
    
    
        app.unmount();
        app = null;
      }
      // 删除rootNode节点
      if (rootNode) {
    
    
        document.body.removeChild(rootNode);
        rootNode = null;
      }
    }
  });
  return app.mount(rootNode);
};

Show results:
Please add a picture description

How to end the Toast that appeared earlier, we only need to ensure that only one Toast pop-up window is rendered globally, so we can use the singleton mode, which means that a class can only have one instance. The Toast component similar to Vant adopts the singleton mode by default, that is, there will only be one at a time. This approach should be a common pop-up method.

2. Multiple prompt pop-up windows

First up the code (example):

// 创建临时变量保存高度值
let top = 0;
const okToast = options => {
    
    
  const rootNode = document.createElement('div');
  // 给创建的元素设置 class 属性值
  rootNode.className = `my-ok-toast`;
  document.body.appendChild(rootNode);
  const dom = document.body.querySelector('.my-ok-toast');
  // 若DOM中存在该元素则将新元素高度往下移动
  if (dom) {
    
    
    top += 120;
    rootNode.style.top = 80 + top + 'px';
  }
  const app = createApp(OkToast, {
    
    
    ...options,
    hide() {
    
    
      app.unmount();
      document.body.removeChild(rootNode);
    }
  });
  return app.mount(rootNode);
};

Then add the css style to the global

.my-ok-toast {
    
    
  position: fixed;
  z-index: 99;
  top: 80px;
  left: 50%;
  transform: translateX(-50%);
}

Show results:
Please add a picture description

The approach here provides you with a way of thinking. The actual animation effect still needs to be optimized. Due to the limited space of this article, it will not be expanded. Let’s explore in depth when encountering this kind of demand in the future.


Summarize

The above is all the content. This article briefly introduces the encapsulation method of Vue3 functional components, and installs it on Vue as a plug-in using the app.use() method to make it a tool for global functions. This is logic reuse in Vue3 The plug-in (Plugins) writing.

If this article is helpful to you, you are welcome to [Like] and [Favorite]! You are also welcome to [comments] to leave valuable comments, discuss and learn together~


further reading

  1. Vue3 plugin
  2. Vant Toast light reminder

Guess you like

Origin blog.csdn.net/m0_55119483/article/details/130100473