阅读 243

Vue3 登录注册模态框 |项目复盘

源码:地址

前段时间写下的Vue.extend 登录注册模态框是基于 Vue 2.x 中 Vue.extend 版本来做

而这段时间内都是在学习 Vue 3.0 且也完全重构了该项目,记录学习下重构的过程与思考。

组件

组件还是那几个组件,需要改动的地方不是很多,只是把其改成 Vue 3 的形式
image.png
sigin.vueregister.vue 组件分别修改

// sigin.vue
<template>
  <div>
    <button @click="onClick">登录确认</button>
  </div>
</template>

<script>
export default {
  name: "Sigin",
  emits: ['sigin'],
  setup(props, { emit }) {
    const onClick = () => {
      emit("sigin")
    }
    return {
      onClick
    }
  }
};
</script>

// register.vue
<template>
  <button @click="onClick">注册确认</button>
</template>

<script>
export default {
  name: "Register",
  emits: ['register'],
  setup(props, { emit }) {
    const onClick = () => {
      emit("register")
    }
    return {
      onClick
    }
  }
};
</script>

复制代码

index.vue 组件

<template>
  <div v-show="show">
    <Sigin v-if="type === 'sigin'" @sigin="loginCallback" />
    <Register v-if="type === 'register'" @register="loginCallback" />
  </div>
</template>

<script>
import Sigin from "./sigin.vue";
import Register from "./register.vue";
import { ref } from 'vue';
export default {
  components: {
    Register,
    Sigin
  },
  props: {
    visible: Boolean,
    type: String,
    close: [Function, Boolean]
  },
  emits: ['destroy'],
  setup(props, { emit }) {
    const show = ref(props.visible);
    const doClose = () => {
      show.value = false;
      emit('destroy');
    }

    const loginCallback = () => {
      if (!props.close) return;
      props.close().then(valid => {
        if (valid) {
          doClose();
        }
      });
    }

    return {
      show,
      doClose,
      loginCallback
    }
  }
};
</script>

复制代码

子类

由于 Vue.extend 被废弃,Vue 3 这里使用 hrender 两个函数,我们来看下这两个函数介绍,具体使用情况,可以查看文档

  • h 函数参数:
    • type 它的类型可以是HTML 标签名、组件或异步组件
    • props 是一个对象,与我们模板组件内的 attributeprop、事件相对应
  • render 渲染函数接收一个组件和容器

修改 index.js

import { render, h } from "vue";
import ModalCops from "./index.vue"; 

let instance;

const ModalBox = ({type, ok}) => {
  if (instance) {
    instance.component.proxy.doClose();
  }

  // 组件接收的 props
  let _opt = {
    visible: true,
    type,
    close: () => {
      return new Promise(resolve => {
        const before = ok ? ok() : false;

        if (before && before.then) {
          before.then(
            () => resolve(true),
            () => {
              console.log("reject");
            }
          );
        } else if (typeof before === "boolean" && before !== false) {
          resolve(true);
        }
      });
    }
  };

  instance = h(ModalCops, _opt); // 组件

  const container = document.createElement('div');

  // 组件销毁时
  instance.props.onDestroy = () => {
    render(null, container);
  }

  // 渲染组件
  render(instance, container);
  
  // 将模态框添加至 body
  document.body.appendChild(container.firstElementChild);

};

ModalBox.sigin = ok => {
  return ModalBox({
    type: "sigin",
    ok
  });
};

ModalBox.register = ok => {
  return ModalBox({
    type: "register",
    ok
  });
};

ModalBox.close = () => {
  instance.component.proxy.doClose();
  instance.component.proxy.show = false;
};

export default {
  install(app) {
    app.config.globalProperties.$loginer = ModalBox;
  }
};

复制代码

最后在全局挂载

import { createApp } from 'vue'
import Login from './Login.vue'
import LoginModal from "./LoginModal/index.js";
createApp(Login).use(LoginModal).mount('#app')
复制代码

Login.vue 组件中使用

import { ref, getCurrentInstance } from "vue";
export default {
  setup(props) {
    const { proxy } = getCurrentInstance();

    const isOk = ref(true);

    const onLogin = (type) => {
      const funcs = {
        sigin: () => {
          console.log("登录请求");
        },
        register: () => {
          console.log("注册请求");
        },
      };
      proxy.$loginer({
        type,
        ok: () => {
          return new Promise((resolve, reject) => {
            if (isOk.value) {
              funcs[type]();
              resolve();
            } else {
              reject();
            }
          });
        },
      });
    };

    return {
      isOk,
      onLogin,
    };
  },
};
复制代码

最后还是放一下效果

logo.gif

注意

在修改其中的部分代码时,我们需要注意的几个点

  • 全局挂载

Vue 3 还是提供 use 函数,来使用我们的插件,不过需要挂载的属性变了,不再是直接挂载在 Vue.prototype 上,而是需要添加到全局实例的 config.globalProperties 对象上

  install(app) {
    app.config.globalProperties.$loginer = ModalBox;
  }
复制代码
  • 组件销毁时的 onDestroy 事件

这个事件对应了 index.vue 模板组件中 emit 出的 destroy 事件,这是由于 Vue 源码中 packages\runtime-core\src\componentEmits.tsemit 函数给我们事件自动加上了 on 前缀
image.png

总结

学了就要用。本次是在学习 Vue3 后就在项目中全面使用,其中也碰到不少需要规避的坑,也尝试着阅读部分源码,收获满满😁

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情

文章分类
前端
文章标签