Vue3折叠面板(Collapse)

简介: 该组件提供了一个高度可定制的折叠面板,支持多种属性设置,包括折叠面板数据、激活状态、禁用选项、边框风格、复制功能等,并可通过插槽进行进一步自定义。其丰富的样式控制选项使得面板能够适应各种场景需求。在线预览展示了不同配置下的效果。组件基于 Vue 3 开发,利用 `requestAnimationFrame` 模拟实现动画效果,并集成了按钮等其他自定义组件。

可自定义设置以下属性:

  • 折叠面板数据(collapseData),可使用 slot 替换指定 key 的 header、content、arrow、extra、lang,类型:Array<{key?: string | number, header?: string | slot, content?: string | slot, disabled?: boolean, showArrow?: boolean, extra?: string | slot}>,默认 []

  • 当前激活 tab 面板的 key(v-model:activeKey),类型:number[] | number | string[] | string | null,默认 null

  • 是否禁用(disabled),优先级低于 Collapse 的 disabled,类型:boolean,默认 false

  • 带边框风格的折叠面板(bordered),类型:boolean,默认 true

  • 是否可复制面板内容(copyable),类型:boolean,默认 false

  • 面板右上角固定内容(lang),例如标识 language,类型:string | slot,默认 undefined

  • 设置面板容器的样式(itemStyle),类型:CSSProperties,默认 {}

  • 设置面板标题的样式(headerStyle),类型:CSSProperties,默认 {}

  • 设置面板内容的样式(contentStyle),类型:CSSProperties,默认 {}

  • 自定义箭头切换图标(arrow),类型:slot,默认 undefined

  • 是否展示箭头(showArrow),优先级低于 Collapse 的 showArrow,类型:boolean,默认 true

  • 箭头位置(arrowPlacement),类型:'left' | 'right',默认 'left'

  • 设置面板箭头的样式(arrowStyle),类型:CSSProperties,默认 {}

  • 面板标题右侧的额外内容(extra),类型:string | slot,默认 undefined

  • 使折叠面板透明且无边框(ghost),类型:boolean,默认 false

效果如下图:在线预览

①创建折叠面板组件Collapse.vue:

<script setup lang="ts">
import { ref } from 'vue'
import type { CSSProperties, Slot } from 'vue'
import Button from '../button'
import { rafTimeout } from '../utils'
interface Collapse {
  key?: string | number // 对应 activeKey,如果没有传入 key 属性,则默认使用数据索引 (0,1,2...) 绑定
  header?: string // 面板标题 string | slot
  content?: string // 面板内容 string | slot
  disabled?: boolean // 是否禁用展开,默认 false
  showArrow?: boolean // 是否展示箭头,默认 true
  extra?: string // 面板标题右侧的额外内容 string | slot
}
interface Props {
  collapseData?: Collapse[] // 折叠面板数据,可使用 slot 替换指定 key 的 header、content、arrow、extra、lang
  activeKey?: number[] | number | string[] | string | null // (v-model) 当前激活 tab 面板的 key
  disabled?: boolean // 是否禁用,优先级低于 Collapse 的 disabled
  collapseStyle?: CSSProperties // 设置面板的样式
  bordered?: boolean // 带边框风格的折叠面板
  copyable?: boolean // 是否可复制面板内容
  lang?: string // 面板右上角固定内容,例如标识 language string | slot
  itemStyle?: CSSProperties // 设置面板容器的样式
  headerStyle?: CSSProperties // 设置面板标题的样式
  contentStyle?: CSSProperties // 设置面板内容的样式
  arrow?: Slot // 自定义箭头切换图标 slot
  showArrow?: boolean // 是否展示箭头,优先级低于 Collapse 的 showArrow
  arrowPlacement?: 'left' | 'right' // 箭头位置
  arrowStyle?: CSSProperties // 设置面板箭头的样式
  extra?: string // 面板标题右侧的额外内容 string | slot
  ghost?: boolean // 使折叠面板透明且无边框
}
const props = withDefaults(defineProps<Props>(), {
  collapseData: () => [],
  activeKey: null,
  disabled: false,
  collapseStyle: () => ({}),
  bordered: true,
  copyable: false,
  lang: undefined,
  itemStyle: () => ({}),
  headerStyle: () => ({}),
  contentStyle: () => ({}),
  arrow: undefined,
  showArrow: true,
  arrowPlacement: 'left',
  arrowStyle: () => ({}),
  extra: undefined,
  ghost: false
})
const contentRef = ref()
const clickIndex = ref<number>(0)
function onEnter(el: Element) {
  ;(el as HTMLElement).style.height =
    contentRef.value[clickIndex.value].offsetHeight + (props.bordered && !props.ghost ? 1 : 0) + 'px'
  ;(el as HTMLElement).style.opacity = '1'
}
function onAfterEnter(el: Element) {
  ;(el as HTMLElement).style.removeProperty('height')
  ;(el as HTMLElement).style.removeProperty('opacity')
}
function onLeave(el: Element) {
  ;(el as HTMLElement).style.height =
    contentRef.value[clickIndex.value].offsetHeight + (props.bordered && !props.ghost ? 1 : 0) + 'px'
  ;(el as HTMLElement).style.opacity = '1'
}
function onAfterLeave(el: Element) {
  ;(el as HTMLElement).style.removeProperty('height')
  ;(el as HTMLElement).style.removeProperty('opacity')
}
const emits = defineEmits(['update:activeKey', 'change'])
function emitValue(value: any) {
  emits('update:activeKey', value)
  emits('change', value)
}
function onClick(key: number | string, index: number) {
  clickIndex.value = index
  if (activeCheck(key)) {
    if (Array.isArray(props.activeKey)) {
      const res = (props.activeKey as any[]).filter((actKey: number | string) => actKey !== key)
      emitValue(res)
    } else {
      emitValue(null)
    }
  } else {
    if (Array.isArray(props.activeKey)) {
      emitValue([...props.activeKey, key])
    } else {
      emitValue(key)
    }
  }
}
function onKeyboard(e: KeyboardEvent, key: number | string, index: number) {
  if (e.key === 'Enter') {
    onClick(key, index)
  }
}
function activeCheck(key: number | string): boolean {
  if (Array.isArray(props.activeKey)) {
    return (props.activeKey as any[]).includes(key)
  } else {
    return props.activeKey === key
  }
}
const copyTxt = ref('Copy')
function onCopy(index: number) {
  navigator.clipboard.writeText(contentRef.value[index].innerText || '').then(
    () => {
      /* clipboard successfully set */
      copyTxt.value = 'Copied'
      rafTimeout(() => {
        copyTxt.value = 'Copy'
      }, 3000)
    },
    (err) => {
      /* clipboard write failed */
      copyTxt.value = err
    }
  )
}
</script>
<template>
  <div
    class="m-collapse"
    :class="{
      'collapse-borderless': !bordered,
      'collapse-arrow-right': arrowPlacement === 'right',
      'collapse-ghost': ghost
    }"
    :style="collapseStyle"
  >
    <div
      class="m-collapse-item"
      :class="{ 'collapse-item-disabled': data.disabled === undefined ? disabled : data.disabled }"
      :style="itemStyle"
      v-for="(data, index) in collapseData"
      :key="index"
    >
      <div
        tabindex="0"
        class="m-collapse-header"
        :class="{ 'collapse-header-no-arrow': data.showArrow !== undefined ? !data.showArrow : !showArrow }"
        :style="headerStyle"
        @click="
          (data.disabled === undefined ? disabled : data.disabled) ? () => false : onClick(data.key || index, index)
        "
        @keydown="onKeyboard($event, data.key || index, index)"
      >
        <div
          v-if="data.showArrow !== undefined ? data.showArrow : showArrow"
          class="collapse-arrow"
          :style="arrowStyle"
        >
          <slot name="arrow" :key="data.key || index" :active="activeCheck(data.key || index)">
            <svg
              class="arrow-svg"
              :class="{ 'arrow-rotate': activeCheck(data.key || index) }"
              focusable="false"
              data-icon="right"
              width="1em"
              height="1em"
              fill="currentColor"
              aria-hidden="true"
              viewBox="64 64 896 896"
            >
              <path
                d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
              ></path>
            </svg>
          </slot>
        </div>
        <div class="collapse-header">
          <slot name="header" :header="data.header" :key="data.key || index" :active="activeCheck(data.key || index)">
            {
  { data.header || '--' }}
          </slot>
        </div>
        <div class="collapse-extra">
          <slot name="extra" :extra="data.extra" :key="data.key || index" :active="activeCheck(data.key || index)">
            {
  { data.extra || extra }}
          </slot>
        </div>
      </div>
      <Transition
        name="collapse"
        @enter="onEnter"
        @after-enter="onAfterEnter"
        @leave="onLeave"
        @after-leave="onAfterLeave"
      >
        <div
          v-show="activeCheck(data.key || index)"
          class="m-collapse-content"
          :class="{ 'collapse-copyable': copyable }"
        >
          <div class="collapse-lang">
            <slot name="lang" :lang="lang" :key="data.key || index" :active="activeCheck(data.key || index)">
              {
  { lang }}
            </slot>
          </div>
          <Button class="collapse-copy" size="small" type="primary" @click="onCopy(index)">{
  { copyTxt }}</Button>
          <div ref="contentRef" class="collapse-content" :style="contentStyle">
            <slot
              name="content"
              :content="data.content"
              :key="data.key || index"
              :active="activeCheck(data.key || index)"
            >
              {
  { data.content }}
            </slot>
          </div>
        </div>
      </Transition>
    </div>
  </div>
</template>
<style lang="less" scoped>
.collapse-enter-active,
.collapse-leave-active {
  overflow: hidden;
  transition:
    height 0.2s cubic-bezier(0.645, 0.045, 0.355, 1),
    opacity 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.collapse-enter-from,
.collapse-leave-to {
  height: 0 !important;
  opacity: 0 !important;
}
.m-collapse {
  font-size: 14px;
  color: rgba(0, 0, 0, 0.88);
  line-height: 1.5714285714285714;
  background-color: rgba(0, 0, 0, 0.02);
  border: 1px solid #d9d9d9;
  border-bottom: 0;
  border-radius: 8px;
  .m-collapse-item {
    border-bottom: 1px solid #d9d9d9;
    &:last-child {
      border-radius: 0 0 8px 8px;
      .m-collapse-header,
      .m-collapse-content {
        border-radius: 0 0 8px 8px;
      }
    }
    .m-collapse-header {
      position: relative;
      display: flex;
      flex-wrap: nowrap;
      align-items: flex-start;
      padding: 12px 16px;
      color: rgba(0, 0, 0, 0.88);
      line-height: 1.5714285714285714;
      cursor: pointer;
      transition: all 0.3s;
      &:focus {
        outline: none;
      }
      .collapse-arrow {
        font-size: 12px;
        height: 22px;
        display: flex;
        align-items: center;
        padding-right: 12px;
        .arrow-rotate {
          transform: rotate(90deg);
        }
        :deep(svg) {
          fill: currentColor;
          transition: transform 0.3s;
        }
      }
      .collapse-header {
        // 元素会根据自身的宽度与高度来确定尺寸,但是会伸长并吸收 flex 容器中额外的自由空间,也会缩短自身来适应 flex 容器
        flex: auto; // 相当于 flex: 1 1 auto
        margin-right: auto;
        display: inline-block;
      }
      .collapse-extra {
        display: flex;
        align-items: center;
        :deep(svg) {
          fill: currentColor;
        }
      }
    }
    .collapse-header-no-arrow {
      padding-left: 12px;
    }
    .m-collapse-content {
      position: relative;
      color: rgba(0, 0, 0, 0.88);
      background-color: #ffffff;
      border-top: 1px solid #d9d9d9;
      .collapse-lang {
        position: absolute;
        right: 10px;
        top: 6px;
        font-size: 14px;
        color: rgba(0, 0, 0, 0.38);
        opacity: 1;
        transition: opacity 0.3s;
      }
      .collapse-copy {
        position: absolute;
        right: 8px;
        top: 8px;
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.3s;
      }
      .collapse-content {
        padding: 16px;
        white-space: pre-wrap;
      }
    }
    .collapse-copyable {
      &:hover {
        .collapse-lang {
          opacity: 0;
          pointer-events: none;
        }
        .collapse-copy {
          opacity: 1;
          pointer-events: auto;
        }
      }
    }
  }
  .collapse-item-disabled {
    .m-collapse-header {
      color: rgba(0, 0, 0, 0.25);
      cursor: not-allowed;
    }
  }
}
.collapse-borderless {
  background-color: rgba(0, 0, 0, 0.02);
  border: 0;
  .m-collapse-item {
    &:last-child {
      border-bottom: 0;
      .m-collapse-header {
        border-radius: 0;
      }
    }
    .m-collapse-content {
      background-color: transparent;
      border-top: 0;
    }
  }
}
.collapse-arrow-right {
  .m-collapse-item .m-collapse-header .collapse-arrow {
    order: 1; // order 属性定义项目的排列顺序。数值越小,排列越靠前,默认为 0
    padding-right: 0;
    padding-left: 12px;
  }
}
.collapse-ghost {
  background-color: transparent;
  border: 0;
  .m-collapse-item {
    border-bottom: 0;
    .m-collapse-content {
      background-color: transparent;
      border: 0;
    }
  }
}
</style>

②在要使用的页面引入:

<script setup lang="ts">
import Collapse from './Collapse.vue'
import { ref, watchEffect } from 'vue'
import { DoubleRightOutlined, RightCircleFilled, StarOutlined, StarFilled } from '@ant-design/icons-vue'
const collapseData = ref([
  {
    key: '1',
    header: 'This is panel header 1',
    content:
      'A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.'
  },
  {
    key: '2',
    header: 'This is panel header 2',
    content: `A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world. A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.`
  },
  {
    key: '3',
    header: 'This is panel header 3',
    content:
      'A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.'
  }
])
const disabledCollapseData = ref([
  {
    key: '1',
    header: 'This is panel header 1',
    content:
      'A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.'
  },
  {
    key: '2',
    header: 'This is panel header 2',
    content: `A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world. A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.`
  },
  {
    key: '3',
    disabled: true,
    header: 'This is panel header 3',
    content:
      'A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.'
  }
])
const nestCollapseData = ref([
  {
    key: '1',
    header: 'This is panel header 1',
    content:
      'A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.'
  }
])
const extraCollapseData = ref([
  {
    key: '1',
    header: 'This is panel header 1',
    extra: 'Extra',
    content:
      'A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.'
  },
  {
    key: '2',
    header: 'This is panel header 2',
    extra: 'Extra',
    content: `A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world. A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.`
  },
  {
    key: '3',
    header: 'This is panel header 3',
    extra: 'Extra',
    content:
      'A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.'
  }
])
const arrowCollapseData = ref([
  {
    key: '1',
    header: 'This is panel header 1',
    content:
      'A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.'
  },
  {
    key: '2',
    showArrow: false,
    header: 'This is panel header 2',
    content: `A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world. A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.`
  },
  {
    key: '3',
    header: 'This is panel header 3',
    content:
      'A dog is a type of domesticated animal. Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.'
  }
])
const activeKey = ref(['1'])
const nestActiveKey = ref(['1'])
const positionOptions = ref([
  {
    label: 'left',
    value: 'left'
  },
  {
    label: 'right',
    value: 'right'
  }
])
const arrowPlacement = ref('left')
watchEffect(() => {
  console.log('activeKey:', activeKey.value)
})
watchEffect(() => {
  console.log('nestActiveKey:', nestActiveKey.value)
})
const key = ref('1')
watchEffect(() => {
  console.log('key:', key.value)
})
function onChange(key: number | string) {
  console.log('change:', key)
}
function handleClick(event: Event, key: string | number) {
  event.stopPropagation() // 阻止事件冒泡
  console.log('event', event)
  console.log('key', key)
}
</script>
<template>
  <div>
    <h1>{
  { $route.name }} {
  { $route.meta.title }}</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <h3 class="mb10">activeKey 传入 number[] | string[],所有面板可同时展开</h3>
    <Collapse :collapse-data="collapseData" v-model:active-key="activeKey" @change="onChange" />
    <h2 class="mt30 mb10">'手风琴'</h2>
    <h3 class="mb10">只允许单个内容区域展开,只需 activeKey 传入 number | string 即可</h3>
    <Collapse :collapse-data="collapseData" v-model:active-key="key" />
    <h2 class="mt30 mb10">禁用</h2>
    <Flex vertical>
      <Collapse disabled :collapse-data="collapseData" v-model:active-key="activeKey" />
      <Collapse :collapse-data="disabledCollapseData" v-model:active-key="activeKey" />
    </Flex>
    <h2 class="mt30 mb10">面板嵌套</h2>
    <Collapse :collapse-data="collapseData" v-model:active-key="activeKey">
      <template #content="{ key }">
        <Collapse v-if="key === '1'" :collapse-data="nestCollapseData" v-model:active-key="nestActiveKey" />
      </template>
    </Collapse>
    <h2 class="mt30 mb10">无边框</h2>
    <Collapse :collapse-data="collapseData" v-model:active-key="activeKey" :bordered="false" />
    <h2 class="mt30 mb10">可复制</h2>
    <Collapse copyable lang="template" :collapse-data="collapseData" v-model:active-key="activeKey" />
    <h2 class="mt30 mb10">隐藏箭头</h2>
    <Collapse :collapse-data="arrowCollapseData" v-model:active-key="activeKey" />
    <h2 class="mt30 mb10">箭头位置</h2>
    <Flex vertical>
      <Radio :options="positionOptions" v-model:value="arrowPlacement" button button-style="solid" />
      <Collapse :collapse-data="collapseData" v-model:active-key="activeKey" :arrow-placement="arrowPlacement" />
    </Flex>
    <h2 class="mt30 mb10">自定义面板</h2>
    <h3 class="mb10">自定义各个面板的背景色、圆角、边距和箭头图标</h3>
    <Collapse
      :collapse-data="collapseData"
      v-model:active-key="activeKey"
      :bordered="false"
      :collapse-style="{ backgroundColor: '#fff' }"
      :item-style="{
        backgroundColor: '#f7f7f7',
        borderRadius: '8px',
        marginBottom: '16px',
        border: 0
      }"
    >
      <template #arrow="{ key, active }">
        <DoubleRightOutlined v-if="key === '2'" :rotate="active ? 90 : 0" />
        <RightCircleFilled v-else :rotate="active ? 90 : 0" />
      </template>
    </Collapse>
    <h2 class="mt30 mb10">自定义面板样式</h2>
    <Collapse
      :collapse-data="collapseData"
      v-model:active-key="activeKey"
      :arrow-style="{ fontSize: '14px', height: '25px' }"
      :header-style="{ fontSize: '16px', color: '#ff6900' }"
      :content-style="{ padding: '16px 24px', color: 'rgba(0, 0, 0, 0.65)' }"
    />
    <h2 class="mt30 mb10">面板额外内容</h2>
    <Collapse :collapse-data="extraCollapseData" v-model:active-key="activeKey">
      <template #extra="{ key }">
        <StarFilled @click="handleClick($event, key)" v-if="key === '1'" />
        <StarOutlined @click="handleClick($event, key)" v-if="key === '3'" />
      </template>
    </Collapse>
    <h2 class="mt30 mb10">幽灵折叠面板</h2>
    <Collapse :collapse-data="collapseData" v-model:active-key="activeKey" ghost />
  </div>
</template>
相关文章
|
12天前
|
开发工具 iOS开发 MacOS
基于Vite7.1+Vue3+Pinia3+ArcoDesign网页版webos后台模板
最新版研发vite7+vue3.5+pinia3+arco-design仿macos/windows风格网页版OS系统Vite-Vue3-WebOS。
139 11
|
5月前
|
缓存 JavaScript PHP
斩获开发者口碑!SnowAdmin:基于 Vue3 的高颜值后台管理系统,3 步极速上手!
SnowAdmin 是一款基于 Vue3/TypeScript/Arco Design 的开源后台管理框架,以“清新优雅、开箱即用”为核心设计理念。提供角色权限精细化管理、多主题与暗黑模式切换、动态路由与页面缓存等功能,支持代码规范自动化校验及丰富组件库。通过模块化设计与前沿技术栈(Vite5/Pinia),显著提升开发效率,适合团队协作与长期维护。项目地址:[GitHub](https://github.com/WANG-Fan0912/SnowAdmin)。
750 5
|
2月前
|
缓存 前端开发 大数据
虚拟列表在Vue3中的具体应用场景有哪些?
虚拟列表在 Vue3 中通过仅渲染可视区域内容,显著提升大数据列表性能,适用于 ERP 表格、聊天界面、社交媒体、阅读器、日历及树形结构等场景,结合 `vue-virtual-scroller` 等工具可实现高效滚动与交互体验。
272 1
|
2月前
|
缓存 JavaScript UED
除了循环引用,Vue3还有哪些常见的性能优化技巧?
除了循环引用,Vue3还有哪些常见的性能优化技巧?
148 0
|
3月前
|
JavaScript
vue3循环引用自已实现
当渲染大量数据列表时,使用虚拟列表只渲染可视区域的内容,显著减少 DOM 节点数量。
99 0
|
5月前
|
JavaScript API 容器
Vue 3 中的 nextTick 使用详解与实战案例
Vue 3 中的 nextTick 使用详解与实战案例 在 Vue 3 的日常开发中,我们经常需要在数据变化后等待 DOM 更新完成再执行某些操作。此时,nextTick 就成了一个不可或缺的工具。本文将介绍 nextTick 的基本用法,并通过三个实战案例,展示它在表单验证、弹窗动画、自动聚焦等场景中的实际应用。
420 17
|
6月前
|
JavaScript 前端开发 算法
Vue 3 和 Vue 2 的区别及优点
Vue 3 和 Vue 2 的区别及优点
|
6月前
|
存储 JavaScript 前端开发
基于 ant-design-vue 和 Vue 3 封装的功能强大的表格组件
VTable 是一个基于 ant-design-vue 和 Vue 3 的多功能表格组件,支持列自定义、排序、本地化存储、行选择等功能。它继承了 Ant-Design-Vue Table 的所有特性并加以扩展,提供开箱即用的高性能体验。示例包括基础表格、可选择表格和自定义列渲染等。
417 6
|
5月前
|
JavaScript 前端开发 API
Vue 2 与 Vue 3 的区别:深度对比与迁移指南
Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架,在过去的几年里,Vue 2 一直是前端开发中的重要工具。而 Vue 3 作为其升级版本,带来了许多显著的改进和新特性。在本文中,我们将深入比较 Vue 2 和 Vue 3 的主要区别,帮助开发者更好地理解这两个版本之间的变化,并提供迁移建议。 1. Vue 3 的新特性概述 Vue 3 引入了许多新特性,使得开发体验更加流畅、灵活。以下是 Vue 3 的一些关键改进: 1.1 Composition API Composition API 是 Vue 3 的核心新特性之一。它改变了 Vue 组件的代码结构,使得逻辑组
1509 0
|
7月前
|
JavaScript 前端开发 UED
vue2和vue3的响应式原理有何不同?
大家好,我是V哥。本文详细对比了Vue 2与Vue 3的响应式原理:Vue 2基于`Object.defineProperty()`,适合小型项目但存在性能瓶颈;Vue 3采用`Proxy`,大幅优化初始化、更新性能及内存占用,更高效稳定。此外,我建议前端开发者关注鸿蒙趋势,2025年将是国产化替代关键期,推荐《鸿蒙 HarmonyOS 开发之路》卷1助你入行。老项目用Vue 2?不妨升级到Vue 3,提升用户体验!关注V哥爱编程,全栈开发轻松上手。
448 2