使用Vue3 tsx 和hooks开发Dialog组件的探索

1,680 阅读2分钟

如何使用Vue3 tsx 和hooks开发Dialog组件

这段时间在用Vue3和tsx写项目,正好遇到了需要写Dialog的场景,因此用本文来介绍一下Vue3 和 hooks 开发Dialog的经验(不足之处和可以改进的地方求大佬们提出)。由于希望用简单的篇幅描述完开发过程 因此环境搭建等流程不做介绍

实现一个Dialog

Dialog组件需要一个外部变量去控制他是否被显示,同时也需要外部传一个函数用于关闭Dialog,因此Props中需要一个visible变量和一个onClose函数。

Dialog打开时,会显示一个阴影,在整个屏幕,然后对话框居中。因此html结构也可以确定下来

组件是挂载在body之下的,TelePort可以实现这个功能

接下来就是组件的实现

import {
  defineComponent,
  Teleport,
  PropType,
  ref,
} from 'vue';
import s from './Dialog.module.scss';


export const Dialog = defineComponent({
  props: {
    visible: {
      type: Boolean,
    },
    onClose: {
      type: Function as PropType<() => void>,
    },
  },
  setup(props, context) {
    return () =>
      props.visible && (
        <Teleport to={document.body}>
          <section class={s.mask} />
          <section class={s.dialog}>
            <header>{context.slots.title?.()}</header>
            <main>{context.slots.default?.()}</main>
            <footer>{context.slots.buttons?.()}</footer>
          </section>
        </Teleport>
      );
  },
});

组件的css如下

.mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: var(--dialog-z-index);
}

.dialog {
  display: flex;
  flex-direction: column;
  z-index: calc(var(--dialog-z-index) + 1);
  background: white;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  min-width: 20em;
  > header {
    padding: 12px 16px;
    color: #333;
    background: rgba(255, 245, 102, 1);
  }
  > main {
    padding: 12px 16px;
    flex-grow: 1;
  }
  > footer {
    padding: 12px 16px;
    text-align: right;
    > button {
      border: none;
      background: white;
    }
    button + button {
      margin-left: 16px;
    }
  }
}

组件的调用方式如下:

import {ref,defineComponent} from 'vue'
import { Dialog } from './dialog'

export const Demo = defineComponent({
     setup(props,context){
         const visibleRef = ref(false)
         const openDialog = ()=>  visibleRef.value = true;
         const closeDialog = ()=> visibleRef.value = false;
         const confirm = () =>{
             // ... 确定所做的操作
             closeDialog()
         }
         return ()=> <>
             <button onClick={openDialog}>show dialog</buttton>
             <Dialog 
                 visible={visible} 
                 onClose={closeDialog}
                 v-slots={{
                     title: ()=> "标题",
                     content: ()=> "内容",
                     buttons: ()=> <button onClick={confirm}>确定</button>
                 }}
             >
         </>
     }
})

虽然这样也能使用一个Dialog 但是操作比较复杂

实现useDialog hooks

Vue3的hooks大量的借鉴了React 因此有React开发经验的开发者理解hooks的概念应该不会存在特别大的障碍 所以关于hooks本文不过多的介绍

下面的useDialog的实现代码

import {
  ref,
  VNode,
  FunctionalComponent,
} from 'vue';

interface Options {
  title?: VNode;
  content?: VNode;
  buttons?: VNode;
}

export function useDialog(options: Options = {}) {
  const visible = ref(false);
  const open = () => (visible.value = true);
  const close = () => (visible.value = false);
  const RenderDialog: FunctionalComponent = () => {
    return (
      <Dialog
        visible={visible.value}
        onClose={close}
        v-slots={{
          title: () => options.title,
          default: () => options.content,
          buttons: () => options.buttons,
        }}
      />
    );
  };
  return { open, close, RenderDialog };
}

组件的使用

import { defineComponent } from 'vue';
import { useDialog } from './components/Dialog';

export const App = defineComponent({
  setup() {
    const { RenderDialog, open, close } = useDialog({
      title: <>登陆</>,
      content: (
        <>
          内容
          <input />
        </>
      ),
      buttons: <button onClick={() => close()}>确认</button>,
    });
    return () => (
      <>
        <button onClick={open}>+1</button>
        <RenderDialog />
      </>
    );
  },
});

效果图

image.png

Stackbiltz链接

stackblitz.com/edit/vitejs…

本文简单的使用了Vue3 + hooks实现了Dialog组件 如果有什么更好的写法建议 欢迎各位留言 谢谢