小言_互联网的博客

面试官:用Vue3.0 写过组件吗?如果想实现一个 Modal你会怎么设计?

370人阅读  评论(0)

一、组件设计

组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式

现在有一个场景,点击新增与编辑都弹框出来进行填写,功能上大同小异,可能只是标题内容或者是显示的主体内容稍微不同

这时候就没必要写两个组件,只需要根据传入的参数不同,组件显示不同内容即可

这样,下次开发相同界面程序时就可以写更少的代码,意味着更高的开发效率,更少的 Bug和更少的程序体积

二、需求分析

实现一个Modal组件,首先确定需要完成的内容:

  • 遮罩层

  • 标题内容

  • 主体内容

  • 确定和取消按钮

主体内容需要灵活,所以可以是字符串,也可以是一段 html 代码

特点是它们在当前vue实例之外独立存在,通常挂载于body之上

除了通过引入import的形式,我们还可通过API的形式进行组件的调用

还可以包括配置全局样式、国际化、与typeScript结合

三、实现流程

首先看看大致流程:

  • 目录结构

  • 组件内容

  • 实现 API 形式

  • 事件处理

  • 其他完善

目录结构

Modal组件相关的目录结构


   
  1. ├── plugins
  2. │   └── modal
  3. │       ├── Content.tsx  // 维护 Modal 的内容,用于 h 函数和 jsx 语法
  4. │       ├── Modal.vue  // 基础组件
  5. │       ├── config.ts  // 全局默认配置
  6. │       ├── index.ts  // 入口
  7. │       ├── locale  // 国际化相关
  8. │       │   ├── index.ts
  9. │       │   └── lang
  10. │       │       ├── en-US.ts
  11. │       │       ├── zh-CN.ts
  12. │       │       └── zh-TW.ts
  13. │       └── modal. type.ts  // ts类型声明相关

因为 Modal 会被 app.use(Modal) 调用作为一个插件,所以都放在plugins目录下

组件内容

首先实现modal.vue的主体显示内容大致如下


   
  1. <Teleport to= "body" :disabled= "!isTeleport">
  2.     <div v- if= "modelValue" class= "modal">
  3.         <div
  4.              class= "mask"
  5.              :style= "style"
  6.              @click= "maskClose && !loading && handleCancel()"
  7.              ></div>
  8.         <div class= "modal__main">
  9.             <div class= "modal__title line line--b">
  10.                 <span>{{ title || t( "r.title") }}</span>
  11.                 <span
  12.                       v- if= "close"
  13.                       :title= "t('r.close')"
  14.                       class= "close"
  15.                       @click= "!loading && handleCancel()"
  16.                       >✕</span
  17.                     >
  18.             </div>
  19.             <div class= "modal__content">
  20.                 <Content v- if= "typeof content === 'function'" :render= "content" />
  21.                 <slot v- else>
  22.                     {{ content }}
  23.                 </slot>
  24.             </div>
  25.             <div class= "modal__btns line line--t">
  26.                 <button :disabled= "loading" @click= "handleConfirm">
  27.                     <span class= "loading" v- if= "loading"> ❍ </span>{{ t( "r.confirm") }}
  28.                 </button>
  29.                 <button @click= "!loading && handleCancel()">
  30.                     {{ t( "r.cancel") }}
  31.                 </button>
  32.             </div>
  33.         </div>
  34.     </div>
  35. </Teleport>

最外层上通过Vue3 Teleport 内置组件进行包裹,其相当于传送门,将里面的内容传送至body之上

并且从DOM结构上来看,把modal该有的内容(遮罩层、标题、内容、底部按钮)都实现了

关于主体内容


   
  1. <div class= "modal__content">
  2.     <Content v- if= "typeof content==='function'"
  3.              :render= "content" />
  4.     <slot v- else>
  5.         {{content}}
  6.     </slot>
  7. </div>

可以看到根据传入content的类型不同,对应显示不同得到内容

最常见的则是通过调用字符串和默认插槽的形式


   
  1. // 默认插槽
  2. <Modal v-model= "show"
  3.        title= "演示 slot">
  4.     <div>hello world~</div>
  5. </Modal>
  6. // 字符串
  7. <Modal v-model= "show"
  8.        title= "演示 content"
  9.        content= "hello world~" />

通过 API 形式调用Modal组件的时候,content可以使用下面两种

  • h 函数


   
  1. $modal.show({
  2.   title:  '演示 h 函数',
  3.   content(h) {
  4.      return h(
  5.        'div',
  6.       {
  7.         style:  'color:red;',
  8.         onClick: ($event: Event) => console.log( 'clicked', $event.target)
  9.       },
  10.        'hello world ~'
  11.     );
  12.   }
  13. });
  • JSX


   
  1. $modal.show({
  2.   title:  '演示 jsx 语法',
  3.   content() {
  4.      return (
  5.       <div
  6.         onClick={($event: Event) => console.log( 'clicked', $event.target)}
  7.       >
  8.         hello world ~
  9.       </div>
  10.     );
  11.   }
  12. });

实现 API 形式

那么组件如何实现API形式调用Modal组件呢?

Vue2中,我们可以借助Vue实例以及Vue.extend的方式获得组件实例,然后挂载到body


   
  1. import Modal from  './Modal.vue';
  2. const ComponentClass = Vue.extend(Modal);
  3. const instance =  new ComponentClass({ el: document.createElement( "div") });
  4. document.body.appendChild(instance.$el);

虽然Vue3移除了Vue.extend方法,但可以通过createVNode实现


   
  1. import Modal from  './Modal.vue';
  2. const container = document.createElement( 'div');
  3. const vnode = createVNode(Modal);
  4. render(vnode, container);
  5. const instance = vnode.component;
  6. document.body.appendChild(container);

Vue2中,可以通过this的形式调用全局 API


   
  1. export  default {
  2.     install(vue) {
  3.        vue.prototype.$create = create
  4.     }
  5. }

而在 Vue3 的 setup 中已经没有 this概念了,需要调用app.config.globalProperties挂载到全局


   
  1. export  default {
  2.     install(app) {
  3.         app.config.globalProperties.$create = create
  4.     }
  5. }

事件处理

下面再看看看Modal组件内部是如何处理「确定」「取消」事件的,既然是Vue3,我们可以采用Compositon API 形式


   
  1. // Modal.vue
  2. setup(props, ctx) {
  3.   let instance = getCurrentInstance();  // 获得当前组件实例
  4.   onBeforeMount(() => {
  5.     instance._hub = {
  6.        'on-cancel': () => {},
  7.        'on-confirm': () => {}
  8.     };
  9.   });
  10.    const handleConfirm = () => {
  11.     ctx.emit( 'on-confirm');
  12.     instance._hub[ 'on-confirm']();
  13.   };
  14.    const handleCancel = () => {
  15.     ctx.emit( 'on-cancel');
  16.     ctx.emit( 'update:modelValue'false);
  17.     instance._hub[ 'on-cancel']();
  18.   };
  19.    return {
  20.     handleConfirm,
  21.     handleCancel
  22.   };
  23. }

在上面代码中,可以看得到除了使用传统emit的形式使父组件监听,还可通过_hub属性中添加 on-cancelon-confirm方法实现在API中进行监听


   
  1. app.config.globalProperties.$modal = {
  2.    show({}) {
  3.       /* 监听 确定、取消 事件 */
  4.    }
  5. }

下面再来目睹下_hub是如何实现


   
  1. // index.ts
  2. app.config.globalProperties.$modal = {
  3.     show({
  4.          /* 其他选项 */
  5.         onConfirm,
  6.         onCancel
  7.     }) {
  8.          /* ... */
  9.          const { props, _hub } = instance;
  10.          const _closeModal = () => {
  11.             props.modelValue =  false;
  12.             container.parentNode!.removeChild(container);
  13.         };
  14.          // 往 _hub 新增事件的具体实现
  15.         Object.assign(_hub, {
  16.             async  'on-confirm'() {
  17.              if (onConfirm) {
  18.                  const fn = onConfirm();
  19.                  // 当方法返回为 Promise
  20.                  if (fn && fn.then) {
  21.                     try {
  22.                         props.loading =  true;
  23.                         await fn;
  24.                         props.loading =  false;
  25.                         _closeModal();
  26.                     } catch (err) {
  27.                          // 发生错误时,不关闭弹框
  28.                         console.error(err);
  29.                         props.loading =  false;
  30.                     }
  31.                 }  else {
  32.                     _closeModal();
  33.                 }
  34.             }  else {
  35.                 _closeModal();
  36.             }
  37.         },
  38.              'on-cancel'() {
  39.                 onCancel && onCancel();
  40.                 _closeModal();
  41.             }
  42.     });
  43. }
  44. };

其他完善

关于组件实现国际化、与typsScript结合,大家可以根据自身情况在此基础上进行更改

参考文献

  • https://segmentfault.com/a/1190000038928664

  • https://vue3js.cn/docs/zh

面试官VUE3番外篇正在更新:6/6

面试官:Vue3.0的设计目标是什么?做了哪些优化?

面试官:Vue3.0 性能提升主要是通过哪几方面体现的?

面试官:Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?

面试官:Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?

面试官:说说Vue 3.0中Treeshaking特性?举例说明一下?

篇副有限,扫下方二维码查看往期


转载:https://blog.csdn.net/weixin_44475093/article/details/113533552
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场