在Vue2和Vue3中如何开发命令式组件

587 阅读5分钟

声明式组件是Vue中常见的组件,声明式组件通过在模板中通过添加条件来判断组件的显示和隐藏,非常方便。但是有些时候我也需要命令式的组件。比如说一些全局的弹框,通知,提示等,使用命令式组件可能会更加方便。

什么是命令式组件呢?

命令式组件,又称为程序化组件,指的是通过 JavaScript 代码直接控制组件的创建与销毁,通常用于需要动态生成或在全局范围内操作的组件,如弹窗、通知、提示框等。这种方式提供了更高的灵活性控制力,适用于复杂或需要在多处调用的组件。

Vue2中如何开发一个命令式组件?

在一些第三方库中就有命令式组件,比如在element-ui中Message 消息提示,MessageBox弹框,Notification 通知 都是命令式组件。今天我们就使用Vue2来开发一个MessageBox命令式组件。

  1. 创建组件文件夹和文件:在 components 目录下创建 MessageBox 文件夹,并在其中创建 index.js 和 MessageBox.vue 文件。

  2. 编写 MessageBox.vue:此文件定义了组件的模板、样式和逻辑。组件接收标题、内容、确认和取消按钮的文本作为属性,并提供确认和关闭的方法。

<template>
  <div class="message-box-wraper">
    <div class="message-box">
      <div class="header">
        <h3>{{ title }}</h3>
        <span @click="$messageBox.close()" class="close">X</span>
      </div>
      <div class="content">
        {{ content }}
      </div>
      <div class="footer">
        <button class="btn detault" @click="$messageBox.close()">取消</button>
        <button class="btn primary" @click="onConfirm">确认</button>
      </div>
    </div>
  </div>
</template>
<script lang="js">
export default {
  name: 'MessageBox',
  props: {
    title: { // 标题
      type: String,
      default: '消息确认框'
    },
    content: { // 弹框内容
      type: String,
      default: '这是消息提示内容'
    },
    confirm: { // 组件内部的index.js传过来,不由用户传
      type: Function,
      default () {
      }
    },
    confirmButtonText: { // 确认按钮的文案
      type: String,
      default: '确定',
    },
    cancelButtonText: { // 取消按钮的文案
      type: String,
      default: '取消'
    }
  },
  methods: {
    onConfirm () {
      this.confirm()
    }
  }
}
</script>
<style lang="scss">
  h3,
  p{
    margin: 0;
    padding: 0;
  }
  .message-box-wraper{
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.5);
    position: fixed;
    z-index: 99;

    .message-box{
      width: 420px;
      height: 200px;
      border-radius: 4px;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      background: #fff;
      padding: 30px;
      box-sizing: border-box;

      .header{
        display: flex;
        justify-content: space-between;

        .close {
          cursor: pointer;
        }
      }
      .content {
        margin: 20px 0;
      }
      .footer {
        display: flex;
        justify-content: flex-end;
        .btn {
          padding: 9px 15px;
          background: #fff;
          border: 1px solid #c6e2ff;
          border-radius: 4px;
          cursor: pointer;

          &.primary {
            background: #66b1ff;
            border-color: #66b1ff;
            margin-left: 10px;
            color: #fff;
          }
        }
      }
    }
  }
</style>
  1. 编写 index.js:此文件定义了插件的安装方法。通过 Vue.extend 创建组件实例,并在 Vue.prototype 上添加 open 和 close 方法,以便在全局范围内调用。
import _MessageBox from "./MessaeBox.vue";

export default {
  install(Vue) {
    let messageBox = null;
    Vue.component(_MessageBox.name, _MessageBox);
    Vue.prototype.$messageBox = {
      open(content, title, otherConfig) {
        if (!messageBox) {
          const _this = this;
          return new Promise((resolve) => {
            const confirm = () => {
              resolve();
              _this.close();
            };
            const MessaeBox = Vue.extend({
              render(h) {
                return h("MessageBox", {
                  props: {
                    content: content,
                    title: title,
                    confirm,
                    ...otherConfig,
                  },
                });
              },
            });

            messageBox = new MessaeBox();
            _this.vm = messageBox.$mount();
            document.body.appendChild(_this.vm.$el);
          });
        }
      },
      close() {
        document.body.removeChild(this.vm.$el);
        messageBox.$destroy();
        messageBox = null;
        this.vm = null;
      },
    };
  },
};

关键技术点:

  • install Vue插件的写法,用的时候直接Vue.use()就可以使用了
  • 在Vue.prototype添加open方法和close方法,这样我们就可以直接在使用MessageBox的地方直接调用显示的方法和关闭的方法
  • 使用Vue.extend扩展一个Vue组件构造器。
  • 返回Promise ,点击确认的时候在确认的时候在确定状态。
  • 使用render函数的h参数函数进行组件渲染。
  • mount()挂载组件实例,mount()挂载组件实例,destroy()销毁组件实例
  1. 在 main.js 中注册插件:使用 Vue.use() 方法将 MessageBox 注册为全局插件。
import Vue from "vue";
import App from "./App.vue";
// 导入MessageBox
import MessageBox from "./components/MessageBox";

Vue.config.productionTip = false;
// 全局使用MessageBox
Vue.use(MessageBox);
new Vue({
  render: (h) => h(App),
}).$mount("#app");
  1. 在应用中使用:通过 this.$messageBox.open() 调用组件,并处理组件的确认事件。
<template>
  <div id="app">
  </div>
</template>
<script>
export default {
  mounted () {
    this.$messageBox.open('新的弹框内容', '新的弹框标题', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
    }).then(() => {
      console.log('点击了确认')
    })
  }
}
</script>

<style lang="scss">
</style>

查看效果:

image.png 点击确认,就能看到点击了确认。就这样一个Vue2开发的命令式消息弹框组件就完成了。

Vue3 如何开发一个命令式组件

Element-plus 是基于Vue3.js开发的,MessageBox 同样使用的是命令式组件开发方式。接下来就来看看Vue3.js 中怎么开发一个Element-plus 中那样的MessageBox 命令式组件。

  1. 创建组件文件夹和文件:与 Vue2 相同,在 components 目录下创建 MessageBox 文件夹,并在其中创建 index.js 和 MessageBox.vue 文件。

  2. 编写 MessageBox.vue:同样定义组件的模板、样式和逻辑。与 Vue2 不同的是,Vue3 使用 Composition API 的 defineProps 和 setup 函数来处理组件的属性和方法。

<template>
  <div class="message-box-wraper">
    <div class="message-box">
      <div class="header">
        <h3>{{ title }}</h3>
        <span @click="$messageBox.close()" class="close">X</span>
      </div>
      <div class="content">
        {{ content }}
      </div>
      <div class="footer">
        <button class="btn detault" @click="close">{{ cancelButtonText }}</button>
        <button class="btn primary" @click="confirm">{{confirmButtonText}}</button>
      </div>
    </div>
  </div>
</template>
<script setup>
const props = defineProps({
  title: { // 标题
      type: String,
      default: '消息确认框'
    },
    content: { // 弹框内容
      type: String,
      default: '这是消息提示内容'
    },
    confirmButtonText: { // 确认按钮的文案
      type: String,
      default: '确定',
    },
    cancelButtonText: { // 取消按钮的文案
      type: String,
      default: '取消'
    },
    confirm: {
      type: Function,
      default () {
      }
    },
    close: {
      type: Function,
      default () {}
    }
})
</script>
<style lang="scss">
  h3,
  p{
    margin: 0;
    padding: 0;
  }
  .message-box-wraper{
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.5);
    position: fixed;
    z-index: 99;

    .message-box{
      width: 420px;
      height: 200px;
      border-radius: 4px;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      background: #fff;
      padding: 30px;
      box-sizing: border-box;

      .header{
        display: flex;
        justify-content: space-between;

        .close {
          cursor: pointer;
        }
      }
      .content {
        margin: 20px 0;
      }
      .footer {
        display: flex;
        justify-content: flex-end;
        .btn {
          padding: 9px 15px;
          background: #fff;
          border: 1px solid #c6e2ff;
          border-radius: 4px;
          cursor: pointer;

          &.primary {
            background: #66b1ff;
            border-color: #66b1ff;
            margin-left: 10px;
            color: #fff;
          }
        }
      }
    }
  }
</style>
  1. 编写 index.js:使用 Vue3 的 createApp 方法创建组件实例。通过 h 函数渲染组件,并提供确认和关闭的回调函数。在组件销毁时,清理 DOM 节点以释放资源。
import { createApp, h } from "vue";
import _MessageBox from "./MessageBox.vue";

function showMessageBox(options) {
  return new Promise((resolve, reject) => {
    const container = document.createElement("div");
    document.body.appendChild(container);

    const confirm = () => {
      resolve();
      clearUp();
    };

    const close = () => {
      reject();
      clearUp();
    };

    const app = createApp({
      render() {
        return h(_MessageBox, {
          ...options,
          confirm,
          close,
        });
      },
    });
    app.mount(container);

    function clearUp() {
      app.unmount();
      if (container.parentNode) {
        container.parentNode.removeChild(container);
      }
    }
  });
}

const MessageBox = {
  confirm(content, title, options) {
    return showMessageBox({
      content,
      title,
      ...options,
    });
  },
};

export default MessageBox;

  1. 在组件中使用:通过 MessageBox.confirm() 方法调用组件,并处理组件的确认和关闭事件。

Vue3 的实现方式更为简洁和现代化,得益于 Vue3 的 Composition API 和 createApp 方法。它简化了组件的创建和管理,使得命令式组件的开发更加直观。

<script setup>
import MessageBox from './components/MessageBox/index.js';
MessageBox.confirm('这是一段新的提示内容', '新的提示标题', {
  confirmButtonText: '确定',
  cancelButtonText: '取消',
}).then(() => {
  console.log('点击了确定')
}, () => {
  console.log('点击了取消或关闭')
})
</script>

<template>
  <div>
  </div>
</template>

<style scoped>
</style>

可以看到Vue3 和Vue2开发命令式组件最大的区别就在于Vue2采用Vue.extend构建组件,Vue3采用createApp构造组件。

总结

命令式组件在 Vue.js 中提供了灵活的组件控制方式,适用于需要动态生成和全局调用的场景。通过本文的介绍,我们了解了在 Vue2 和 Vue3 中实现命令式组件的不同方法。无论是使用 Vue2 的 Vue.extend 还是 Vue3 的 createApp,命令式组件都为开发者提供了强大的工具,帮助他们在复杂的应用场景中实现更高效的组件管理。随着 Vue.js 的不断发展,命令式组件的实现方式也将继续演变,为开发者提供更为强大和灵活的解决方案。