比方说,你创建了一个可重复使用的组件:
在 Vue 中创建真正的可重用组件可能很棘手。在本文中,我将探讨可重用组件的概念、应用这些组件时面临的问题,以及为什么必须尽可能克服这些问题。
可重用组件是用户界面构件,可用于应用程序的不同部分,甚至多个项目。它们封装了特定的功能或用户界面模式,可以轻松集成到应用程序的其他部分,而无需进行大量修改。
虽然可重用性是 Vue. 组件的一个理想特性,但有几个问题会使其难以实现:
比方说,客户想要一个内部员工目录系统。该项目采用敏捷方法,开发前无法收集所有需求。项目分为三个阶段(原型、第一阶段和第二阶段)。在本演示中,我将重点介绍一个卡片组件,如下所示:
图片
作为原型阶段的一部分,我需要提供一个用户配置文件页面。用户配置文件将包含一个基本的用户卡组件,其中包括用户头像和姓名。
图片
// Prototype.vue<script setup lang="ts"> import { defineProps, computed, Teleport, ref } from "vue"; interface Props { firstName: string; lastName: string; image?: string; } const props = defineProps<Props>();</script><template> <div class="app-card"> <img class="user-image" :src="image" alt="avatar" /> <div> <div> <label> {{ firstName }} {{ lastName }} </label> </div> </div> </div></template><style scoped> .app-card { padding-left: 10px; padding-right: 10px; padding-top: 5px; padding-bottom: 5px; background: white; box-shadow: 0 0 5px; border-radius: 5px; border: none; font-size: 1.5em; transition: 0.3s; display: flex; align-items: center; } .app-card label { font-weight: 600; } .app-card:hover { background: rgba(128, 128, 128, 0.5); } .user-image { width: 100px; }</style>
在第一阶段,客户希望在用户卡组件中添加用户详细信息(生日、年龄、电话号码和电子邮件)。
图片
//Phase1.vue<script setup lang="ts"> import { defineProps, computed } from "vue"; interface Props { firstName: string; lastName: string; image?: string; birthDay?: string; phone?: string; email?: string; } const props = defineProps<Props>(); const age = computed(() => { if (!props.birthDay) { return "0"; } const birthYear = new Date(props.birthDay).getFullYear(); const currentYear = new Date().getFullYear(); return currentYear - birthYear; });</script><template> <div ref="cardRef" class="app-card"> <img class="user-image" :src="image" alt="avatar" /> <div> <div> <label> {{ firstName }} {{ lastName }} </label> </div> <div> <div> <label> Birth day: </label> <span> {{ birthDay }} </span> </div> <div> <label> Age: </label> <span> {{ age }} </span> </div> <div> <label> Phone number: </label> <span> {{ phone }} </span> </div> <div> <label> Email: </label> <span> {{ email }} </span> </div> </div> </div> </div></template><style scoped> .app-card { padding-left: 10px; padding-right: 10px; padding-top: 5px; padding-bottom: 5px; background: white; box-shadow: 0 0 5px; border-radius: 5px; border: none; font-size: 1.5em; transition: 0.3s; display: flex; align-items: center; } .app-card label { font-weight: 600; } .app-card:hover { background: rgba(128, 128, 128, 0.5); color: black; } .user-image { width: 100px; }</style>
此外,客户还希望添加员工名录页面,并以卡片格式显示用户资料。
图片
// SearchPage<template> <div> <SearchInput v-model:value="searchValue" /> <template :key="item.id" v-for="item of list"> <div style="margin-bottom: 5px; margin-top: 5px"> <UserCard v-bind="item" /> </div> </template> </div></template><script lang="ts"> import SearchInput from "../components/SearchInput.vue"; import UserCard from "../components/Phase1.vue"; import { ref, watch } from "vue"; export default { name: "Search", components: { SearchInput, UserCard, }, setup() { const searchValue = ref<string>(); const list = ref(); fetch("https://dummyjson.com/users") .then((res) => res.json()) .then((res) => (list.value = res.users)); watch(searchValue, (v) => { fetch(`https://dummyjson.com/users/search?q=${v}`) .then((res) => res.json()) .then((res) => (list.value = res.users)); }); watch(list, (v) => console.log(v)); return { searchValue, list, }; }, };</script>
在这一阶段,用户卡组件可在两个页面上重复使用。
用户反馈 "员工名录 "页面杂乱无章。过多的信息使页面难以使用。因此,客户希望在鼠标悬停时以工具提示的形式显示用户详细信息。用户设置页面的要求保持不变。
// Phase 2<script setup lang="ts">import { defineProps, computed, Teleport, ref, onMounted, onBeforeUnmount,} from "vue";interface Props { firstName: string; lastName: string; image?: string; birthDate?: string; phone?: string; email?: string; address?: string;}const props = defineProps<Props>();const targetRef = ref<HTMLDiveElement>();const isMouseOver = ref(false);const dropdownRef = ref<HTMLDivElement>();const dropdownStyle = ref({});// add modal element in body to prevent overflow issueconst modalElement = document.createElement("div");modalElement.id = "modal";document.body.appendChild(modalElement);const age = computed(() => { if (!props.birthDate) { return "0"; } const birthYear = new Date(props.birthDate).getFullYear(); const currentYear = new Date().getFullYear(); return currentYear - birthYear;});const onMouseOver = () => { if (isMouseOver.value) { return; } isMouseOver.value = true; const dimension = targetRef.value.getBoundingClientRect(); dropdownStyle.value = { width: `${dimension.width}px`, left: `${dimension.x}px`, top: `${window.scrollY + dimension.y + dimension.height + 5}px`, };};const onMouseLeave = () => { isMouseOver.value = false;};</script><template> <div ref="targetRef" @mouseover="onMouseOver" @mouseleave="onMouseLeave" class="app-card" > <img class="user-image" :src="image" alt="avatar" /> <div> <div> <label> {{ firstName }} {{ lastName }} </label> </div> </div> </div> <Teleport to="#modal"> <div ref="dropdownRef" :style="dropdownStyle" style="position: absolute" v-show="isMouseOver" > <div class="app-card"> <div> <div> <label> Birth day: </label> <span> {{ birthDate }} </span> </div> <div> <label> Age: </label> <span> {{ age }} </span> </div> <div> <label> Phone number: </label> <span> {{ phone }} </span> </div> <div> <label> Email: </label> <span> {{ email }} </span> </div> </div> </div> </div> </Teleport></template><style scoped>.app-card { padding-left: 10px; padding-right: 10px; padding-top: 5px; padding-bottom: 5px; background: white; box-shadow: 0 0 5px; border-radius: 5px; border: none; font-size: 1.5em; transition: 0.3s; display: flex; align-items: center;}.app-card label { font-weight: 600;}.app-card:hover { background: rgba(128, 128, 128, 0.5); color: black;}.user-image { width: 100px;}</style>
这项新要求令人头疼:
因为我们不想破坏已经投入生产的项目,所以我们倾向于选择后一种方案。起初,这可能是有道理的,但它可能会造成相当大的损害,尤其是对于大型和连续性项目而言:
在整个项目中,可重复使用的组件可能不会始终保持不变,这一点要有心理准备。这听起来可能很老套,但仔细想想,需求总是在不断变化的。你无法控制未来,只能尽力而为。当然,经验会帮助你设计出更好的组件,但这需要时间
根据我的经验,我将重新设计和重构可重用的组件。重构是一个在不改变代码原有功能的前提下重组代码的过程。我相信重构的方法有很多,对我来说,我会重构并将组件分解成更小的组件。较小的组件可以在整个系统中灵活应用。让我们看看我将如何应用上述案例研究。
注意:重构用户界面组件需要严谨的态度。此外,有时这也很具有挑战性,因为需要在项目交付期限和更简洁的代码之间取得平衡。
首先,我将把现有的用户卡组件拆分成 4 个组件:
图片
// Card.vue<template> <div class="app-card"> <slot></slot> </div></template><style scoped> .app-card { padding-left: 15px; padding-right: 15px; padding-top: 10px; padding-bottom: 10px; border-radius: 5px; border: none; background: white; color: black; font-size: 1.5em; transition: 0.3s; display: flex; align-items: center; box-shadow: 0 0 5px; } .app-card:hover { background: rgba(128, 128, 128, 0.5); color: black; }</style>
// Avatar.vue<script setup lang="ts"> import { defineProps } from "vue"; interface Props { image: string; } const props = defineProps<Props>();</script><template> <img class="user-image" :src="image" alt="avatar" /></template><style scoped> .user-image { width: 100px; }</style>
// UserName.vue<script setup lang="ts"> import { defineProps } from "vue"; interface Props { firstName: string; lastName: string; } const props = defineProps<Props>();</script><template> <label> {{ firstName }} {{ lastName }} </label></template>
efineProps } from "vue"; interface Props { label: string; value: string | number; } const props = defineProps<Props>();</script><template> <div> <label> {{ label }}: </label> <span> {{ value }} </span> </div></template><style scoped> label { font-weight: 600; }</style>
// UserDescription.vue<script setup lang="ts"> import DescriptionItem from "./DescriptionItem.vue"; import { defineProps, computed } from "vue"; interface Props { birthDate: string; phone: string; email: string; } const props = defineProps<Props>(); const age = computed(() => { if (!props.birthDate) { return "0"; } const birthYear = new Date(props.birthDate).getFullYear(); const currentYear = new Date().getFullYear(); return currentYear - birthYear; });</script><template> <div> <DescriptionItem label="Birth day" :value="birthDate" /> <DescriptionItem label="Age" :value="age" /> <DescriptionItem label="Phone number" :value="phone" /> <DescriptionItem label="Email" :value="email" /> </div></template>
之后,我将创建一个工具提示组件。创建一个单独的工具提示可以让我在系统的其他部分重复使用它。
图片
// Tooltip.vue<script setup lang="ts">import { Teleport, computed, ref, onMounted, onBeforeUnmount, watch,} from "vue";const isMouseOver = ref(false);const targetRef = ref<HTMLDivElement>();const dropdownStyle = ref({});const dropdownRef = ref<HTMLDivElement>();const existModalElement = document.getElementById("modal");if (!existModalElement) { // add modal element in body to prevent overflow issue const modalElement = document.createElement("div"); modalElement.id = "modal"; document.body.appendChild(modalElement);}const onMouseOver = () => { if (isMouseOver.value) { return; } isMouseOver.value = true; const dimension = targetRef.value.getBoundingClientRect(); dropdownStyle.value = { width: `${dimension.width}px`, left: `${dimension.x}px`, top: `${window.scrollY + dimension.y + dimension.height + 5}px`, };};const onMouseLeave = () => { isMouseOver.value = false;};</script><template> <div @mouseover="onMouseOver" @mouseleave="onMouseLeave" ref="targetRef"> <slot name="default" /> </div> <Teleport to="#modal"> <div ref="dropdownRef" :style="dropdownStyle" style="position: absolute" v-show="isMouseOver" > <Card> <slot name="overlay" /> </Card> </div> </Teleport></template>
最后,我将把这些组件组合在一起,如下所示。
在用户设置页面,我将使用用户卡组件,它由卡片、头像、姓名组件和用户详情组件组成。
图片
// UserWithDescription.vue<script setup lang="ts">import AppCard from "./Card.vue";import DescriptionItem from "./DescriptionItem.vue";import Avatar from "./Avatar.vue";import UserName from "./UserName.vue";import UserDescription from "./UserDescription.vue";import { defineProps } from "vue";interface Props { firstName: string; lastName: string; image?: string; birthDate?: string; phone?: string; email?: string; address?: string;}const props = defineProps<Props>();</script><template> <AppCard> <Avatar :image="image" /> <div> <div> <UserName :firstName="firstName" :lastName="lastName" /> </div> <UserDescription v-bind="props" /> </div> </AppCard></template>
至于 "员工名录 "页面,我计划由两个部分组成
图片
// UserCard.vue<script setup lang="ts"> import AppCard from "./Card.vue"; import DescriptionItem from "./DescriptionItem.vue"; import Avatar from "./Avatar.vue"; import UserName from "./UserName.vue"; import { defineProps } from "vue"; interface Props { firstName: string; lastName: string; image?: string; } const props = defineProps<Props>();</script><template> <AppCard> <Avatar :image="image" /> <div> <div> <UserName :firstName="firstName" :lastName="lastName" /> </div> </div> </AppCard></template>
// UserCardWithTooltip.vue<script setup lang="ts"> import ToolTip from "./Tooltip.vue"; import UserDescription from "./UserDescription.vue"; import UserCard from "./UserCard.vue"; import Card from "./Card.vue"; import { defineProps } from "vue"; interface Props { firstName: string; lastName: string; image?: string; birthDate?: string; phone?: string; email?: string; } const props = defineProps<Props>();</script><template> <ToolTip> <UserCard v-bind="props" /> <template #overlay> <Card> <UserDescription v-bind="props" /> </Card> </template> </ToolTip></template>
注:你可能会注意到,所提供的解决方案基于原子设计概念。该概念首先可以将 "可重用性 "挑战降至最低。如果您对如何将其应用于 Vue.js 感兴趣,请参阅我同事的文章。
有些人可能会认为,为可重用组件编写单元测试会缓解这一问题。的确,全面的测试覆盖有助于确保对组件的修改和增强不会意外破坏功能。
然而,单元测试并不能使组件变得更可重用。它只是让组件更健壮而已。事实上,重构为更小的组件可以将任务分解为特定的部分,使单元测试的编写更易于管理。
在 Vue中创建实际的可重用组件可能具有挑战性,这是因为需要解决修改现有组件、保持一致性以及管理依赖关系和状态等相关问题。然而,可重用组件的好处使得克服这些问题是值得的。可重用组件能加强代码组织、提高开发效率,并有助于创建一致的用户界面。当我们面对新的需求或任务时,我们将不断改进,以便更好地设计可重用组件。
本文链接:http://www.28at.com/showinfo-26-13635-0.htmlVue 中可重用组件的 3 个主要问题
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: Python的变量和数据类型