>웹 프론트엔드 >View.js >Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.

Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.

青灯夜游
青灯夜游앞으로
2022-12-28 20:55:273170검색

Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.

왜 이런 생각을 하게 되었나요

관리 백엔드 개발 과정에서 팝업 비즈니스 팝업이 너무 많이 뜨는데, 그 중 가장 흔한 것이 "XX 데이터 추가", "XX 데이터 편집"입니다. ", "XX 상세 데이터 보기" 등의 팝업창 유형이 가장 일반적입니다. [관련 추천 : vuejs 영상 튜토리얼, 웹 프론트 엔드 개발]

이러한 팝업 구성 요소의 코드 중 구성 요소 상태, 양식 구성 요소와 관련된 메서드 등 많은 코드가 동일합니다...

그래서 , 저는 단순히 Dialog 구성 요소의 보조 캡슐화와 후크를 사용하여 반복되는 코드를 줄입니다Dialog组件进行的二次封装和hooks,减少了一些重复的代码

要封装什么

如果是普通弹窗使用的话,直接使用el-dialog组件已经足够了

但我还是一个比较爱折腾的人,我们先看看官方dialog文档有什么可以添加的功能

...

大概看了一下,我打算封装一下功能

  • 提供全屏操作按钮(右上角)
  • 默认提供“确认”,“关闭”按钮
  • 内部添加Loading效果

封装Dialog

确定了要封装的功能之后,先来一个简单的dialog组件。

把双向绑定处理一下,这样外部就可以直接通过v-model直接控制弹窗了。

<template>
    <el-dialog :model-value="props.modelValue"></el-dialog>
</template>
<script setup>
interface PropsType {
  modelValue?: boolean;
}

const props = withDefaults(defineProps<PropsType>(), {
  modelValue: false,
});

const emits = defineEmits<{
  (e: "update:modelValue"): void;
}>();
</script>

header

这里使用到图标库@element-plus/icons-vue

如没有安装,请执行npm install @element-plus/icons-vue

使用el-dialog提供的header插槽,将全屏图表和关闭图标放置到右上角中。给el-dialog传递show-close属性关闭默认图标。

<template>
  <el-dialog :model-value="props.modelValue" :show-close="false">
    <template #header>
      <div>
        <span>{{ props.title }}</span>
      </div>
      <div>
        <el-icon><FullScreen /></el-icon>
        <el-icon><Close /></el-icon>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { FullScreen, Close } from "@element-plus/icons-vue";
</script>
<style scoped>
// 处理样式
:deep(.el-dialog__header) {
  border-bottom: 1px solid #eee;
  display: flex;
  padding: 12px 16px;
  align-items: center;
  justify-content: space-between;
  margin: 0;
}
.dialog-title {
  line-height: 24px;
  font-size: 18px;
  color: #303133;
}
.btns {
  display: flex;
  align-items: center;
  i {
    margin-right: 8px;

    font-size: 16px;
    cursor: pointer;
  }
  i:last-child {
    margin-right: 0;
  }
}
</style>

弹窗的标题文字内容通过props进行传递,默认为空(''

<script lang="ts" setup>
interface PropsType {
  // 忽略之前的代码
  title?: string;
}

const props = withDefaults(defineProps<PropsType>(), {
  title: "",
});

</script>

我们看看现在头部的效果(这里没传入标题,默认为''

Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.

现在这个按钮只有样式效果,还没有写上对应的功能 ~

给他们先绑定上对应的事件和指令

<template>
    <el-dialog
    :model-value="props.modelValue"
    :show-close="false"
    :fullscreen="attrs?.fullscreen ?? isFullscreen"
    >
        <template #header>
        <div>
            <span class="dialog-title">{{ props.title }}</span>
        </div>
        <div class="btns">
            <el-icon v-if="isFullScreenBtn" @click="handleFullscreen"
            ><FullScreen
            /></el-icon>
            <el-icon @click="handleClose"><Close /></el-icon>
        </div>
        </template>
    </el-dialog>
</template>
<script setup lang="ts">
import { FullScreen, Close } from "@element-plus/icons-vue";

interface PropsType {
  title?: string;
  modelValue?: boolean;
  hiddenFullBtn?: boolean;
}

const props = withDefaults(defineProps<PropsType>(), {
  title: "",
  modelValue: false,
  hiddenFullBtn: false,
});

const emits = defineEmits<{
  (e: "update:modelValue"): void;
  (e: "close"): void;
}>();

// 当前是否处于全屏状态
const isFullscreen = ref(false);
// 是否显示全屏效果图标
const isFullScreenBtn = computed(() => {
  if (props.hiddenFullBtn) return false;
  if (attrs?.fullscreen) return false;
  return true;
});

// 开启、关闭全屏效果
const handleFullscreen = () => {
  if (attrs?.fullscreen) return;
  isFullscreen.value = !isFullscreen.value;
};

// 关闭弹窗时向外部发送close事件
const handleClose = () => {
  emits("close");
};
</script>

再点击下全屏图标看看效果怎么样

Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.

NICE 头部功能也就完成了

Footer

接下来,再处理下底部内容,默认提供两个按钮,分别是“确定”和“关闭”,这个名称也是可以通过props属性修改的。

两个按钮绑定点击事件,向外发送不同的事件。

<template>
  <div class="">
    <el-dialog
      v-bind="attrs"
      :model-value="props.modelValue"
      :show-close="false"
      :fullscreen="attrs?.fullscreen ?? isFullscreen"
    >
      <template #footer>
        <!-- 如果没有提供其他footer插槽,就使用默认的 -->
        <span v-if="!slots.footer" class="dialog-footer">
          <el-button type="primary" @click="handleConfirm">{{
            props.confirmText
          }}</el-button>
          <el-button @click="handleClose">{{ props.cancelText }}</el-button>
        </span>
        <!-- 使用传入进来的插槽 -->
        <slot v-else name="footer"></slot>
      </template>
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import { useSlots } from "vue";
// 获取插槽
const slots = useSlots();
interface PropsType {
    title?: string;
    width?: string | number;
    isDraggable?: boolean;
    modelValue?: boolean;
    hiddenFullBtn?: boolean;
    confirmText?: string;
    cancelText?: string;
}

const props = withDefaults(defineProps<PropsType>(), {
    title: "",
    isDraggable: false,
    modelValue: false,
    hiddenFullBtn: false,
    confirmText: "确认",
    cancelText: "关闭",
});
const handleClose = () => {
    emits("close");
};
const handleConfirm = () => {
    emits("confirm");
};
</script>

Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.

又搞定了一部分了,就剩下Content了 ~

Content

弹窗内容通过默认插槽的方式传入进来,在外层的div元素上添加v-loading

캡슐화할 내용
If If 일반적인 팝업창에 사용되는 것이므로 el-dialog 컴포넌트를 직접 사용하면 충분합니다

하지만 저는 여전히 장난을 좋아하는 사람입니다. code>dialog 문서에 어떤 기능을 추가할 수 있는지

...loading效果

Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.

剩下一些细节处理

el-dialog组件提供了很多个props属性供用户选择,但我们现在封装的dialog组件只使用到了一小部分props属性。当用户想要使用其他的props属性时该怎么办?

例如使用width属性时,难道要在我们封装的组件中接收props.width再传递给<el-dialog :width="props.width"></el-dialog>组件吗?

不不不,还有另外一种方法,还记得刚刚在做全屏操作的时候使用到的useAttrs辅助函数吗

它可以获取当前组件传递进来的属性。有了这个方法之后,再配合并即可将外部传递进来的函数再传递到el-dialog组件上面啦

<template>
  <div class="">
    <el-dialog
      v-bind="attrs"
      :model-value="props.modelValue"
      :show-close="false"
      :fullscreen="attrs?.fullscreen ?? isFullscreen"
    >
        <div class="content" v-loading="props.loading">
            <slot></slot>
        </div>
    </el-dialog>
  </div>
</template>
<script lang="ts" setup>
interface PropsType {
  loading?: boolean;
}

const props = withDefaults(defineProps<PropsType>(), {
  loading: false,
});

</script>

为了避免内部传递的props被覆盖掉,v-bind="attrs"需要放在最前面

在使用时,可能会给before-close属性传递一个函数,但到了后面被内部的handleClose

대략 살펴본 후 기능을 캡슐화할 계획입니다🎜
  • 전체 화면 조작 버튼 제공(상단) 오른쪽)
  • 기본적으로 "확인" 제공. "닫기" 버튼
  • 내부적으로 로딩 효과 추가

대화 상자 캡슐화🎜🎜캡슐화 확인 함수 다음에는 간단한 dialog 구성 요소부터 시작하겠습니다. 🎜🎜 v-model을 통해 팝업창을 외부에서 직접 제어할 수 있도록 양방향 바인딩을 처리합니다. 🎜
<el-dialog
    v-bind="attrs"
    :model-value="props.modelValue"
    :show-close="false"
    :fullscreen="attrs?.fullscreen ?? isFullscreen"
    :before-close="handleClose"
>
    <!-- 忽略其他代码 -->
</el-dialog>

header🎜🎜🎜여기에서는 아이콘 라이브러리 @element-plus/icons-vue가 사용됩니다🎜🎜🎜🎜설치되지 않은 경우 npm install @element-plus를 실행하세요. /icons- vue🎜🎜🎜el-dialog에서 제공하는 header 슬롯을 사용하여 전체 화면 차트를 배치하고 오른쪽 상단에 아이콘을 닫습니다. 기본 아이콘을 닫으려면 show-close 속성을 ​​el-dialog에 전달하세요. 🎜
const handleClose = () => {
  if (
    Reflect.has(attrs, "before-close") &&
    typeof attrs["before-close"] === "function"
  ) {
    attrs["before-close"]();
  }
  emits("close");
};
🎜팝업창의 제목 텍스트 내용은 props를 통해 전달되며, 기본값은 비어있습니다('') 🎜
import { ref } from "vue";

export default function useDialog() {
  const visible = ref(false);
  const loading = ref(false);
  const openDialog = () => (visible.value = true);
  const closeDialog = () => (visible.value = false);
  const openLoading = () => (loading.value = true);
  const closeLoading = () => (loading.value = false);
  return {
    visible,
    loading,
    openDialog,
    closeDialog,
    openLoading,
    closeLoading,
  };
}
🎜다음을 살펴보겠습니다. 현재 헤드의 효과(여기에는 전달되지 않음) 제목을 입력하세요. 기본값은 '')🎜🎜Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.🎜🎜이제 이 버튼은 스타일 효과만 있고 해당 기능은 아직 작성되지 않았습니다~🎜🎜해당 이벤트 바인딩 먼저 🎜
<template>
<el-button @click="openDialog1">普通弹窗</el-button>
<DialogCmp
  title="DialogCmp1"
  :hiddenFullBtn="true"
  v-model="visible1"
  @confirm="handleConfirm"
  @close="handleClose"
>
  <h3>DialogCmp1</h3>
</DialogCmp>
</template>
<script setup lang="ts">
import useDialog from "./components/useDialog";
import DialogCmp from "./components/Dialog.vue";

const {
  visible: visible1,
  openDialog: openDialog1,
  closeDialog: closeDialog1,
} = useDialog();
</script>
🎜 그런 다음 전체 화면 아이콘을 클릭하세요. 어떻게 작동하는지 살펴보겠습니다🎜🎜Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.🎜🎜NICE 이제 헤더 기능이 완성되었습니다🎜

Footer🎜🎜다음으로 하단 컨텐츠 2개를 처리해 주세요. 기본적으로 "OK" 및 " "Close"가 제공되며 이 이름은 props 속성을 ​​통해 수정할 수도 있습니다. 🎜🎜두 개의 버튼이 클릭 이벤트를 바인딩하고 다양한 이벤트를 외부로 보냅니다. 🎜
export enum MODE {
  ADD,  EDIT,
}
🎜Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.🎜🎜 또 다른 부분은 완료하고 컨텐츠만 남습니다~🎜

Content🎜🎜팝업 컨텐츠는 기본 슬롯을 통해 전달되고, 외부 div를 추가합니다. <code>v-loading 태그를 code> 요소에 추가하여 로딩 상태를 달성합니다. 🎜🎜🎜팝업창 전체를 로딩효과로 연출하고 싶다면 v-loading을 가장 바깥쪽 요소로 옮겨주세요. el-dialog 요소에 있을 수 없으며 그렇지 않으면 구현할 수 없습니다. el-dialog가 텔레포트 구성요소를 사용하여 v-로딩이 제대로 작동하지 않을 수 있습니다. 시간나실때 공부해보세요~🎜🎜
import { ref } from "vue";
import { MODE } from "./types";
export default function useDialogState() {
  const mode = ref<MODE>(MODE.ADD);
  const visible = ref(false);
  const updateMode = (target: MODE) => {
    mode.value = target;
  };
  return { mode, visible, updateMode };
}
🎜중간에 로딩 효과를 확인해 보세요🎜🎜Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.🎜

처리해야 할 세부 사항이 남아 있습니다🎜🎜 el-dialog구성요소는 사용자가 선택할 수 있는 많은 props 속성을 ​​제공하지만, 우리가 캡슐화하고 있는 dialog 구성요소는 이제 props

속성. 사용자가 다른 props 속성을 ​​사용하려면 어떻게 해야 합니까? 🎜🎜예를 들어 width 속성을 사용할 때 캡슐화된 구성 요소에서 props.width를 받은 다음 <el-dialog :width="props.width에 전달해야 합니까? "></el-dialog> 컴포넌트?🎜🎜아니요, 아니, 다른 방법이 있습니다. 전체 화면 작업을 할 때 방금 사용한 <code>useAttrs 보조 기능을 기억하시나요?🎜🎜현재 구성 요소 전송 속성에 나타납니다. 이 방법을 사용하면 외부에서 전달된 함수를 협력하여 el-dialog 컴포넌트에 전달할 수 있습니다🎜
import { FormInstance } from "element-plus";
import { Ref, ref } from "vue";
import { MODE } from "./types";
import useDialogState from "./useDialogState";

export default function useDialogFn(
  formInstance: Ref<FormInstance>
) {
  const { visible, mode, updateMode } = useDialogState();

  const closeDialog = () => {
    formInstance.value.resetFields();
    visible.value = false;
  };
  const openDialog = (target: MODE) => {
    updateMode(target);
    visible.value = true;
  };
  return { visible, mode, openDialog, closeDialog };
}
🎜🎜내부적으로 전달된 props가 덮어쓰이는 것을 방지하기 위해 v -bind="attrs"는 앞쪽에 배치되어야 합니다🎜🎜🎜사용할 때 함수는 before-close 속성에 전달될 수 있지만 나중에는 내부 handlerClose 메서드가 재정의되었습니다. 🎜

解决方案是在handleClose函数中,获取attrs.['before-close']属性,如果类型是函数函数,先执行它。

const handleClose = () => {
  if (
    Reflect.has(attrs, "before-close") &&
    typeof attrs["before-close"] === "function"
  ) {
    attrs["before-close"]();
  }
  emits("close");
};

有关于el-dialog组件的封装就到这里了

封装hooks

利用Vue composition Api再封装一下在使用el-dialog组件状态的管理hook

useDialog

简单处理显示和加载态开关的hook

import { ref } from "vue";

export default function useDialog() {
  const visible = ref(false);
  const loading = ref(false);
  const openDialog = () => (visible.value = true);
  const closeDialog = () => (visible.value = false);
  const openLoading = () => (loading.value = true);
  const closeLoading = () => (loading.value = false);
  return {
    visible,
    loading,
    openDialog,
    closeDialog,
    openLoading,
    closeLoading,
  };
}

useDialog Demo

Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.

<template>
<el-button @click="openDialog1">普通弹窗</el-button>
<DialogCmp
  title="DialogCmp1"
  :hiddenFullBtn="true"
  v-model="visible1"
  @confirm="handleConfirm"
  @close="handleClose"
>
  <h3>DialogCmp1</h3>
</DialogCmp>
</template>
<script setup lang="ts">
import useDialog from "./components/useDialog";
import DialogCmp from "./components/Dialog.vue";

const {
  visible: visible1,
  openDialog: openDialog1,
  closeDialog: closeDialog1,
} = useDialog();
</script>

useDialogState 和 useDialogWithForm

useDialogState

针对开发管理后台弹窗状态封装的一个hook,搭配下面的useDialogWithForm使用。

export enum MODE {
  ADD,  EDIT,
}
import { ref } from "vue";
import { MODE } from "./types";
export default function useDialogState() {
  const mode = ref<MODE>(MODE.ADD);
  const visible = ref(false);
  const updateMode = (target: MODE) => {
    mode.value = target;
  };
  return { mode, visible, updateMode };
}

useDialogWithForm

针对表单弹窗组件封装的hooks,接收一个formRef实例,负责控制弹窗内标题及清空表单中的校验结果,减少多余的代码 ~

import { FormInstance } from "element-plus";
import { Ref, ref } from "vue";
import { MODE } from "./types";
import useDialogState from "./useDialogState";

export default function useDialogFn(
  formInstance: Ref<FormInstance>
) {
  const { visible, mode, updateMode } = useDialogState();

  const closeDialog = () => {
    formInstance.value.resetFields();
    visible.value = false;
  };
  const openDialog = (target: MODE) => {
    updateMode(target);
    visible.value = true;
  };
  return { visible, mode, openDialog, closeDialog };
}

useDialogWithForm Demo

Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.

<template>
  <Dialog
    :before-close="customClose"
    @confirm="confirm"
    v-model="visible"
    :title="mode == MODE.ADD ? &#39;添加数据&#39; : &#39;编辑信息&#39;"
    :confirm-text="mode == MODE.ADD ? &#39;添加&#39; : &#39;修改&#39;"
  >
    <el-form
      label-width="100px"
      :model="formData"
      ref="formDataRef"
      style="max-width: 460px"
      :rules="rules"
    >
      <el-form-item label="姓名" prop="name">
        <el-input v-model="formData.name" />
      </el-form-item>
      <el-form-item label="年龄" prop="age">
        <el-input v-model="formData.age" />
      </el-form-item>
      <el-form-item label="手机号码" prop="mobile">
        <el-input v-model="formData.mobile" />
      </el-form-item>
    </el-form>
  </Dialog>
</template>
<script setup>
import { ElMessage, FormInstance } from "element-plus";
import { Ref, ref } from "vue";
import Dialog from "./Dialog.vue";
import { MODE } from "./types";
import useDialogWithForm from "./useDialogWithForm";

const rules = {
  name: {
    type: "string",
    required: true,
    pattern: /^[a-z]+$/,
    trigger: "change",
    message: "只能是英文名称哦",
    transform(value: string) {
      return value.trim();
    },
  },
  age: {
    type: "string",
    required: true,
    pattern: /^[0-9]+$/,
    trigger: "change",
    message: "年龄只能是数字哦",
    transform(value: string) {
      return value.trim();
    },
  },
  mobile: {
    type: "string",
    required: true,
    pattern:
      /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/,
    trigger: "change",
    message: "请输入正确的手机号码",
    transform(value: string) {
      return value.trim();
    },
  },
};

interface FromDataType {
  name: string;
  age: string;
  mobile: string;
}

const formDataRef = ref<FormInstance | null>(null);

let formData = ref<FromDataType>({
  name: "",
  age: "",
  mobile: "",
});

const { visible, closeDialog, openDialog, mode } = useDialogWithForm(
  formDataRef as Ref<FormInstance>
);
const confirm = () => {
  if (!formDataRef.value) return;
  formDataRef.value.validate((valid) => {
    if (valid) {
      console.log("confirm");
      ElMessage({
        message: "提交成功",
        type: "success",
      });
      closeDialog();
    }
  });
};

const customClose = () => {
  ElMessage({
    message: "取消提交",
    type: "info",
  });
  closeDialog();
};
defineExpose({
  closeDialog,
  openDialog,
});
</script>
<style scoped></style>

仓库地址

useDialog

在线demo地址

7 (1).gif

如果您觉得本文对您有帮助,请帮帮忙点个star

您的反馈 是我更新的动力!

(学习视频分享:vuejs入门教程编程基础视频

위 내용은 Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제