普通视图

发现新文章,点击刷新页面。
昨天 — 2026年2月12日首页

Vue2跨组件通信方案:全局事件总线与Vuex的灵活结合

作者 青屿ovo
2026年2月12日 14:17

Vue2跨组件通信方案:全局事件总线与Vuex的灵活结合

前端高频面试/开发考点!一文吃透Vue2跨组件通信核心,Bus+Vuex结合用法拆解,代码可直接复制复用,新手也能快速避坑,收藏备用~

📋 目录

  • 一、核心前言(为什么需要两种方案结合?)

  • 二、全局事件总线(Bus)详解(3步落地+避坑)

  • 三、Vuex详解(Vue2状态管理核心,5大模块+4步实战)

  • 四、Bus与Vuex灵活结合(实战场景+核心优势)

  • 五、高频避坑指南(面试常考)

  • 六、核心总结(快速回顾重点)


一、核心前言

Vue2开发中,跨组件通信是绕不开的高频需求,不同组件层级(父子、兄弟、隔代、无关联)对应不同解决方案,单一方案往往有局限性:

  • props/emit:仅适合父子组件,层级嵌套多时会出现“props drilling”(props穿透),代码冗余;

  • 全局事件总线(Bus):轻量高效,但无状态管理,复杂场景难以维护;

  • Vuex:集中管理状态,适合复杂场景,但配置繁琐,简单通信成本高。

核心原则:简单通信用Bus(轻量高效),复杂状态用Vuex(统一管理),两者灵活结合,可高效解决99%的Vue2跨组件通信需求。


二、全局事件总线(Bus)详解

1. 什么是全局事件总线?

本质:通过Vue实例作为“中间桥梁”,实现任意组件间的事件传递(触发+监听),无需层层传递,轻量无依赖、无需额外安装,是简单跨组件通信的最优解。

适用场景:兄弟组件通信、隔代组件简单通信、无关联组件单次通信(如弹窗关闭、通知提示、页面刷新通知)。

2. 实现步骤(3步落地,代码可直接复制)

步骤1:创建全局Bus实例(main.js配置)
// main.js(Vue2项目)
import Vue from 'vue'
import App from './App.vue'

// 创建全局事件总线,挂载到Vue原型,所有组件可直接访问
Vue.prototype.$Bus = new Vue()

new Vue({
  el: '#app',
  render: h => h(App)
})
步骤2:发送事件(触发方组件)

通过 this.$Bus.$emit('事件名', 传递的数据) 发送事件,支持任意类型数据(对象、数组、基本类型)。

<template>
  <button @click="sendMsg" style="padding: 8px 16px; cursor: pointer;">发送消息给兄弟组件</button>
</template>

<script>
export default {
  methods: {
    sendMsg() {
      // 事件名建议语义化,避免冲突(可加组件前缀,如brother-msg)
      this.$Bus.$emit('brotherMsg', {
        content: 'Hello,兄弟组件!',
        time: new Date().toLocaleString()
      })
    }
  }
}
</script>
步骤3:监听事件(接收方组件)

通过 this.$Bus.$on('事件名', 回调函数) 监听事件,重点:必须在beforeDestroy中销毁监听,避免内存泄漏和事件多次触发。

<template>
  <div class="brother-component">
    <h4>接收兄弟组件消息:</h4>
    <p v-if="msg">{{ msg.content }}({{ msg.time }})</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: null
    }
  },
  mounted() {
    // 监听事件,与发送方事件名保持一致
    this.$Bus.$on('brotherMsg', (data) => {
      this.msg = data
    })
  },
  beforeDestroy() {
    // 销毁监听,避免内存泄漏(必写!面试常考)
    this.$Bus.$off('brotherMsg')
  }
}
</script>

3. Bus核心方法速查(表格清晰记)

方法名 说明 使用示例
$emit 发送事件,传递数据 this.Bus.Bus.emit('name', data)
$on 监听事件,接收数据 this.Bus.Bus.on('name', (data)=>{})
$off 销毁监听,避免泄漏 this.Bus.Bus.off('name')

4. Bus优缺点(辩证看待)

✅ 优点

  • 轻量、简单、无依赖,接入成本极低

  • 无需额外配置,开箱即用

  • 适合简单通信场景,效率高

❌ 缺点

  • 无状态管理,无法追踪数据来源

  • 事件名易冲突,维护成本随项目变大升高

  • 不适合多组件共享、频繁修改的复杂状态


三、Vuex详解(Vue2状态管理核心)

1. 什么是Vuex?

Vue2官方状态管理库,用于集中管理所有组件的共享状态(如用户信息、购物车数据、全局设置),实现组件间状态共享和统一修改,可追踪状态变化,是中大型Vue2项目的首选方案。

适用场景:多组件共享状态、需频繁修改/追踪的复杂状态、全局状态管理(如用户登录状态、主题切换)。

2. Vuex核心概念(5大模块,面试必背)

记牢这5个模块,即可掌握Vuex核心用法,面试高频提问!

  • state:存储全局状态(类似组件的data),唯一数据源,所有组件共享;

  • mutations:修改state的唯一方式(仅支持同步操作),禁止写异步代码;

  • actions:处理异步操作(如接口请求),不能直接修改state,需通过commit调用mutations;

  • getters:对state进行加工处理(类似组件的computed),可缓存结果,避免重复计算;

  • modules:拆分模块(大型项目用),避免state过于臃肿,每个模块可拥有独立的state、mutations等。

3. 使用步骤(4步落地,实战可直接复用)

步骤1:安装Vuex(Vue2专属版本,避坑关键)

Vue2必须安装3.x版本,4.x版本仅适配Vue3,装错会直接报错!

# Vue2项目安装命令(固定版本,避免兼容问题)
npm install vuex@3.6.2 --save
步骤2:创建Vuex实例(src/store/index.js)
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

// 安装Vuex插件
Vue.use(Vuex)

// 创建Vuex实例
const store = new Vuex.Store({
  // 存储全局状态
  state: {
    userInfo: null, // 多组件共享:用户信息
    count: 0 // 示例:简单共享计数
  },
  // 同步修改state(仅同步操作)
  mutations: {
    setUserInfo(state, data) {
      state.userInfo = data // 只能通过mutation修改state
    },
    increment(state) {
      state.count++
    }
  },
  // 处理异步操作(如接口请求)
  actions: {
    // 模拟异步获取用户信息(实际项目替换为接口请求)
    getUserInfoAsync({ commit }, data) {
      setTimeout(() => {
        // 异步操作完成后,通过commit调用mutation修改state
        commit('setUserInfo', data)
      }, 1000)
    }
  },
  // 加工state,缓存结果
  getters: {
    // 判断用户是否登录
    isLogin(state) {
      return !!state.userInfo
    },
    // 获取计数的2倍(缓存结果,避免重复计算)
    doubleCount(state) {
      return state.count * 2
    }
  }
})

export default store
步骤3:挂载Vuex到Vue实例(main.js)
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store' // 引入store实例
import Vuex from 'vuex'

Vue.use(Vuex)

new Vue({
  el: '#app',
  render: h => h(App),
  store // 挂载后,所有组件可通过this.$store访问Vuex
})
步骤4:组件中使用Vuex(读取/修改状态)
<template>
  <div class="vuex-demo">
    <h4>Vuex状态使用示例</h4>
    <p>当前计数:{{ $store.state.count }}</p>
    <p>计数的2倍:{{ $store.getters.doubleCount }}</p>
    <p>用户是否登录:{{ $store.getters.isLogin ? '已登录' : '未登录' }}</p>
    
    <button @click="addCount" style="margin-right: 10px; padding: 8px 16px;">增加计数</button>
    <button @click="getUserInfo" style="padding: 8px 16px;">模拟登录</button>
  </div>
</template>

<script>
export default {
  methods: {
    // 同步修改state:调用mutation(唯一方式)
    addCount() {
      this.$store.commit('increment')
    },
    // 异步修改state:调用action,由action触发mutation
    getUserInfo() {
      this.$store.dispatch('getUserInfoAsync', {
        username: 'vue2demo',
        age: 22
      })
    }
  }
}
</script>

4. Vuex优缺点(辩证看待)

✅ 优点

  • 集中管理共享状态,可追踪状态变化(调试方便);

  • 规范组件通信,避免数据混乱;

  • 适合复杂场景,维护成本低,扩展性强。

❌ 缺点

  • 配置繁琐,简单通信场景(如单次弹窗)使用成本高;

  • 小型项目无需使用,过度封装会增加冗余。


四、Bus与Vuex的灵活结合(核心重点)

1. 结合原则(实战核心)

记住一句话:简单通信用Bus,复杂状态用Vuex,两者互补,避开单一方案的弊端,提升开发效率。

  • 用Bus的场景:一次性通信、无状态依赖通信(弹窗关闭、兄弟组件单次消息、页面刷新通知);

  • 用Vuex的场景:多组件共享状态、需频繁修改/追踪的复杂状态(用户信息、购物车、全局设置)。

2. 实战结合示例(面试常考场景)

场景:用户登录成功后,用Vuex同步全局用户状态,用Bus通知所有相关组件(导航栏、个人中心)刷新页面。

// 1. 登录组件(触发登录,调用Vuex action + 发送Bus事件)
export default {
  methods: {
    login() {
      // 模拟接口请求登录,获取用户数据
      const userData = { username: 'vue2demo', role: 'admin' }
      // ① 调用Vuex action,同步用户状态到全局(复杂状态管理)
      this.$store.dispatch('getUserInfoAsync', userData)
      // ② 发送Bus事件,通知其他组件刷新(简单一次性通信)
      this.$Bus.$emit('userLoginSuccess', userData)
    }
  }
}

// 2. 导航栏组件(监听Bus事件 + 读取Vuex状态)
export default {
  data() {
    return {
      userInfo: null
    }
  },
  mounted() {
    // 监听Bus事件,接收登录成功通知,局部更新
    this.$Bus.$on('userLoginSuccess', (data) => {
      this.userInfo = data
    })
    // 初始化时,读取Vuex中的全局用户状态
    this.userInfo = this.$store.state.userInfo
  },
  beforeDestroy() {
    // 销毁Bus监听,避免内存泄漏
    this.$Bus.$off('userLoginSuccess')
  }
}

3. 结合优势(为什么要这么用?)

  • ✅ 高效:简单场景无需配置复杂Vuex,降低开发成本;复杂场景用Vuex,保证状态规范;

  • ✅ 灵活:按需选择方案,避免“一刀切”(不用为了简单通信写一堆Vuex配置);

  • ✅ 易维护:状态集中管理(Vuex),单次通信解耦(Bus),代码清晰,后期好维护。


五、高频避坑指南(面试常考,必看!)

这些坑90%的新手都会踩,收藏起来,避免踩坑!

1. Bus避坑(2个核心)

  • 事件名必须语义化,可加组件前缀(如header-close、brother-msg),避免冲突;

  • 必须在beforeDestroy中销毁监听(this.Bus.Bus.off('事件名')),否则会导致内存泄漏、事件多次触发。

2. Vuex避坑(3个核心)

  • Vue2必须安装Vuex@3.x版本,4.x仅适配Vue3,装错会直接报错;

  • mutations只能写同步代码,异步操作(如接口请求)必须放在actions中,否则无法追踪状态变化;

  • 禁止直接修改state(如this.$store.state.count = 1),必须通过mutation修改(面试高频考点)。

3. 结合避坑(2个核心)

  • 不滥用Vuex,简单通信用Bus即可,避免过度封装;

  • Bus仅用于“通知”,不传递大量复杂数据(复杂数据用Vuex存储),避免数据混乱。


六、核心总结

本文核心是「Bus+Vuex灵活结合」,记住以下4点,轻松应对Vue2跨组件通信所有场景:

  1. 全局事件总线(Bus):Vue实例作为桥梁,轻量简单,适合简单通信,重点是销毁监听

  2. Vuex:Vue2官方状态管理库,集中管理共享状态,适合复杂场景,核心是5大模块,禁止直接修改state

  3. 结合逻辑:简单通信用Bus,复杂状态用Vuex,互补使用,提升开发效率和代码可维护性;

  4. 避坑关键:Bus销毁监听、Vuex版本适配、不直接修改state、事件名语义化。

你在Vue2跨组件通信中还遇到过哪些坑?欢迎在评论区留言交流,一起避坑成长~

vue-esign 用途及使用教程

作者 青屿ovo
2026年2月12日 14:06

vue-esign 用途及使用教程

一、✍️ vue-esign 核心用途

vue-esignVue项目专属轻量级电子签名插件,不用依赖后端,纯前端就能实现手写签名、导出保存,体积小、易接入,解决前端手写签名的核心需求。

📌 常见使用场景(手绘重点标注)

  • 📋 办公场景:电子合同签署、审批流程手写确认、报销单/表单签名(高频使用!)

  • 📝 教育场景:在线作业手写答题、试卷批注、课堂笔记手绘、学生签名确认

  • ✨ 通用场景:用户手写昵称、留言板手绘、实名认证手写、小程序签名

🌟 核心优点(手绘小亮点)

  • 轻量无依赖,接入成本极低,不用额外装其他插件

  • 兼容 Vue2、Vue3 双版本,适配绝大多数Vue项目

  • 可自定义:笔迹粗细、笔迹颜色、画板背景(纯色/图片都可)

  • 签名可导出为base64格式,方便传给后端保存或本地预览

二、📝 手把手使用教程(新手友好·一步一标)

⚠️ 前置小提醒

✅ Vue3 项目 → 安装 vue-esign@2.x 版本(推荐)

✅ Vue2 项目 → 安装 vue-esign@1.x 版本(别装错哦!)

✅ 无需额外依赖,安装完成直接使用,不用配置复杂环境

步骤1:安装依赖 📦(复制命令直接执行)

打开项目终端,根据自己的Vue版本,粘贴对应命令,回车安装即可:

# Vue3 项目(推荐,适配最新Vue版本)
npm install vue-esign --save

# Vue2 项目(指定1.x版本,避免兼容问题)
npm install vue-esign@1.0.10 --save

步骤2:引入插件 ✨(两种方式,选一种就好)

方式1:全局引入(项目通用,推荐!)

在main.js/ main.ts中引入,全局注册后,所有组件都能直接使用:

// Vue3 项目(main.js / main.ts)
import { createApp } from 'vue'
import App from './App.vue'
import vueEsign from 'vue-esign'

const app = createApp(App)
app.use(vueEsign) // 全局注册签名组件
app.mount('#app')
// Vue2 项目(main.js)
import Vue from 'vue'
import App from './App.vue'
import vueEsign from 'vue-esign'

Vue.use(vueEsign) // 全局注册签名组件
new Vue({
  el: '#app',
  render: h => h(App)
})
方式2:局部引入(仅单个组件使用,节省资源)

如果只有一个组件需要用签名,直接在该组件内引入即可:

<template&gt;
  <!-- 签名组件,直接使用 -->
  <vue-esign ref="esign" :width="800" :height="400" />
</template>

<script setup>
// Vue3 局部引入
import { ref } from 'vue'
import vueEsign from 'vue-esign'

const esign = ref(null) // 获取签名组件实例(必须!)
&lt;/script&gt;

<!-- Vue2 局部引入 -->
<script>
import vueEsign from 'vue-esign'
export default {
  components: {
    vueEsign // 局部注册组件
  },
  data() {
    return {
      esign: null // 获取签名组件实例
    }
  }
}
</script>

步骤3:基础使用示例 ✍️(复制即用,新手必看)

以下是Vue3完整可运行示例,包含「签名画板+清空+保存+预览」,粘贴到组件中就能直接测试:

<template>
  <div class="esign-container" style="padding: 20px;">
    <!-- 签名画板(核心组件) -->
    <vue-esign
      ref="esign"
      :width="800"       <!-- 画板宽度(px),可自定义 -->
      :height="400"      <!-- 画板高度(px),可自定义 -->
      :lineWidth="6"     <!-- 笔迹宽度,越大数据越粗 -->
      :lineColor="#000"  <!-- 笔迹颜色,支持十六进制(如#ff6600) -->
      :bgColor="#f5f5f5" <!-- 画板背景色,浅灰色更显笔迹 -->
      style="border: 1px solid #ddd; border-radius: 8px;"
    />

    <!-- 操作按钮(清空+保存) -->
    <div style="margin-top: 20px;">
      <button @click="handleReset" style="margin-right: 10px; padding: 8px 16px; cursor: pointer;">🗑️ 清空签名</button>
      <button @click="handleSave" style="padding: 8px 16px; background: #409eff; color: #fff; border: none; border-radius: 4px; cursor: pointer;">💾 保存签名</button>
    </div>

    <!-- 签名预览(保存后显示) -->
    <div v-if="signImg" style="margin-top: 20px;">
      <h4>📸 签名预览:</h4>
      <img :src="signImg" alt="签名图片" style="max-width: 800px; border: 1px solid #eee;">
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 签名组件实例(必须通过ref获取,才能调用内置方法)
const esign = ref(null)
// 保存的签名图片(base64格式,可用于预览、上传)
const signImg = ref('')

// 清空签名(调用插件内置reset方法)
const handleReset = () => {
  esign.value.reset() // 清空画板
  signImg.value = '' // 清空预览图
}

// 保存签名(转为base64格式)
const handleSave = async () => {
  try {
    // 调用插件内置generate方法,生成base64图片
    const base64 = await esign.value.generate()
    if (!base64) {
      alert('✍️ 请先完成签名再保存哦!')
      return
    }
    signImg.value = base64 // 显示预览图
    // 后续可将base64传给后端保存,或转为文件上传
    console.log('签名base64:', base64)
  } catch (error) {
    console.error('保存签名失败:', error)
    alert('保存签名失败,请重试!')
  }
}
</script>

步骤4:自定义配置项 ⚙️(按需调整,手绘表格)

根据项目需求,自定义画板样式,所有配置项整理如下(直接复制使用):

配置项 类型 默认值 手绘说明(重点)
width Number 800 画板宽度(单位px,不可写百分比)
height Number 400 画板高度(单位px,按需调整)
lineWidth Number 4 笔迹宽度,数值越大越粗(推荐5-8)
lineColor String #000000 笔迹颜色,支持十六进制(如#ff0000红色)
bgColor String #ffffff 画板背景色,纯色即可(推荐#f5f5f5浅灰)
bgImg String '' 画板背景图片(url地址,注意跨域问题)
disabled Boolean false 是否禁用签名(true=禁用,false=可签名)
自定义示例(带背景图的签名板)
<vue-esign
  ref="esign"
  :width="800"
  :height="400"
  :lineWidth="8"       <!-- 粗笔迹 -->
  :lineColor="#ff6600" <!-- 橙色笔迹,更醒目 -->
  bgImg="https://xxx.com/sign-bg.png" <!-- 自定义背景图URL -->
/>

步骤5:常见问题 & 注意事项 ⚠️(手绘避坑指南)

  • 📱 移动端适配:画板宽度别写固定值,用屏幕宽度计算,避免溢出// Vue3 适配移动端(获取屏幕宽度,减去边距) const width = ref(document.documentElement.clientWidth - 40)

  • 📦 base64体积过大:生成的签名base64可能偏长,可安装image-conversion库压缩后再上传后端 npm install image-conversion --save

  • ✍️ 签名为空提示:调用generate()时,如果没签名,会返回false,需先判断再保存(示例中已包含)

  • 🌐 跨域问题:bgImg(背景图)需确保支持跨域,否则会报错,优先用本地图片

  • 🔍 版本兼容:Vue2和Vue3版本不能混装,装错会导致组件无法显示,严格按照步骤1安装

三、📌 核心方法速查(手绘小卡片)

插件只有2个核心方法,记牢就能满足99%的需求,不用记复杂语法:

方法名 手绘说明 使用方式(必记)
reset() 清空当前签名,重置画板 esign.value.reset()
generate() 生成签名base64图片(异步) await esign.value.generate()

四、💾 MD文档保存方法(手绘步骤)

复制本文档全部内容,按照以下步骤保存,即可下载使用:

  1. 新建一个文本文档(.txt),打开后粘贴全部内容

  2. 重命名文件,将后缀从「.txt」改为「.md」(例:vue-esign手绘笔记.md)

  3. 双击文件,用Markdown编辑器(Typora、VS Code等)打开,即可查看/编辑

五、✍️ 笔记总结(手绘重点)

  1. vue-esign 是Vue专属轻量级电子签名插件,纯前端实现,无依赖、易接入;

  2. 核心流程:安装依赖 → 全局/局部引入 → 配置画板 → 调用方法(保存/清空);

  3. 关键注意:版本别装错、移动端适配、签名为空判断,避开这3个坑就能顺利使用。

昨天以前首页

CSS自适应屏幕

作者 青屿ovo
2026年2月11日 11:46

CSS自适应屏幕代码教程

✏️ 开篇说明 🎨

本文为全新原创CSS自适应教程,避开复杂理论,聚焦「90%项目必用」的自适应方案,精选4个搜索量TOP的核心知识点,主打极简可复制代码、新手零门槛,每部分都附实战示例+避坑提示,开发时直接抄作业,有错欢迎指出来~

💡 核心目标:学会后能快速实现「PC端+移动端」自适应,适配不同屏幕尺寸(手机、平板、电脑),不用写多套CSS!

📌 四大高频自适应方案(搜索量拉满,刚需实用)

精选知识点(按使用频率排序):1. 媒体查询(最常用,兼容所有浏览器)2. Rem适配(移动端首选)3. Vw/Vh适配(极简无依赖)4. Flex/Grid自适应布局(配合适配方案使用),逐个拆解,代码可直接复制运行。

一、媒体查询(Media Query)—— 自适应万能方案

搜索量TOP1,最基础、最常用的自适应方案,核心逻辑:根据不同屏幕宽度,执行不同的CSS样式,兼容所有浏览器(包括旧版),PC端+移动端通用,新手入门首选。

1. 核心语法(必记)


/* 基础语法:屏幕宽度满足条件时,执行内部样式 */
@media screen and (条件) {
  /* 这里写满足条件后的CSS样式 */
}

/* 常用条件(重点记这3个) */
@media screen and (max-width: 768px) { /* 屏幕宽度 ≤ 768px(移动端) */ }
@media screen and (min-width: 769px) and (max-width: 1200px) { /* 平板端 */ }
@media screen and (min-width: 1201px) { /* 屏幕宽度 ≥ 1201px(PC端) */ }
    

2. 实战示例(可直接复制运行)

需求:实现一个卡片,PC端显示3列、移动端显示1列,字体和内边距随屏幕自适应。


<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8"&gt;
  <!-- 关键移动端适配必须加这个meta标签否则媒体查询无效 -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>媒体查询实战</title>
  <style>
    /* 通用样式(所有屏幕都生效) */
    .card-container {
      display: flex;
      flex-wrap: wrap; /* 自动换行,配合自适应 */
      gap: 20px; /* 卡片之间的间距 */
      padding: 20px;
    }
    .card {
      background: #f5f5f5;
      border-radius: 8px;
      padding: 16px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }

    /* 1. PC端(≥1201px):3列布局 */
    @media screen and (min-width: 1201px) {
      .card {
        width: calc(33.33% - 20px); /* 3列均分,减去间距 */
        font-size: 16px;
      }
    }

    /* 2. 平板端(769px-1200px):2列布局 */
    @media screen and (min-width: 769px) and (max-width: 1200px) {
      .card {
        width: calc(50% - 20px); /* 2列均分 */
        font-size: 15px;
      }
    }

    /* 3. 移动端(≤768px):1列布局 */
    @media screen and (max-width: 768px) {
      .card {
        width: 100%; /* 占满屏幕宽度 */
        font-size: 14px;
        padding: 12px; /* 减小内边距,适配小屏幕 */
      }
      .card-container {
        padding: 10px; /* 减小容器内边距 */
        gap: 10px;
      }
    }
  </style>
</head>
<body>
  <div class="card-container">
    <div class="card">卡片1(自适应宽度)</div>
    <div class="card">卡片2(自适应宽度)</div>
    <div class="card">卡片3(自适应宽度)</div>
  </div>
</body>
</html>
    

3. 核心避坑(90%新手踩过)

  • 🚨 避坑1:必须添加 viewport meta 标签(示例中已写),否则移动端媒体查询无效,样式错乱。

  • 🚨 避坑2:媒体查询的顺序不能乱!要遵循「从小到大」或「从大到小」,推荐「从大到小」(PC→平板→移动端),避免样式覆盖。

  • 🚨 避坑3:不要在媒体查询中重复写所有样式,只写需要修改的样式(通用样式写在媒体查询外面),减少冗余。

二、Rem适配 —— 移动端首选方案

搜索量TOP2,专门针对移动端的自适应方案,核心逻辑:以根元素(html)的字体大小为基准,所有元素尺寸用rem单位,通过JS动态修改根字体大小,实现「等比例缩放」,适配所有手机屏幕。

1. 核心准备(2步搞定)

步骤1:设置根字体大小(默认基准)

/* CSS 中设置默认根字体大小(PC端/平板端) */
html {
  font-size: 16px; /* 1rem = 16px(默认基准,可自定义) */
}

/* 移动端通过JS动态修改,后面会写 */
    
步骤2:添加JS动态适配代码(核心)

作用:根据手机屏幕宽度,动态修改html的font-size,实现rem等比例缩放(复制到html的head标签内即可)。


<script>
// Rem自适应核心JS(无需修改,直接复制)
function setRemUnit() {
  // 1. 获取屏幕宽度(兼容不同浏览器)
  const screenWidth = document.documentElement.clientWidth || window.innerWidth;
  // 2. 设定基准宽度(通常以375px为手机设计稿基准,可修改)
  const baseWidth = 375;
  // 3. 计算根字体大小(1rem = 屏幕宽度 / 10,方便计算)
  const fontSize = screenWidth / 10;
  // 4. 设置根元素字体大小(最大不超过32px,避免字体过大)
  document.documentElement.style.fontSize = Math.min(fontSize, 32) + 'px';
}

// 5. 初始化执行一次
setRemUnit();
// 6. 屏幕旋转/ resize时,重新执行(适配屏幕变化)
window.addEventListener('resize', setRemUnit);
window.addEventListener('orientationchange', setRemUnit);
</script>
    

2. 实战示例(移动端适配卡片)


<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Rem自适应实战</title>
  <script>
    // 先复制Rem自适应JS(核心)
    function setRemUnit() {
      const screenWidth = document.documentElement.clientWidth || window.innerWidth;
      const baseWidth = 375;
      const fontSize = screenWidth / 10;
      document.documentElement.style.fontSize = Math.min(fontSize, 32) + 'px';
    }
    setRemUnit();
    window.addEventListener('resize', setRemUnit);
    window.addEventListener('orientationchange', setRemUnit);
  </script>
  <style>
    /* 所有元素尺寸用rem单位(1rem = 屏幕宽度/10) */
    .card {
      width: 8rem; /* 相当于屏幕宽度的80%(8/10) */
      height: 5rem;
      background: #42b983;
      color: #fff;
      border-radius: 0.5rem; /* 5px(以375px屏幕为例) */
      padding: 0.8rem;
      font-size: 1rem;
      margin: 0 auto;
      display: flex;
      align-items: center;
      justify-content: center;
    }
  </style>
</head>
<body>
  <div class="card">Rem自适应卡片</div>
</body>
</html>
    

3. 核心避坑

  • 🚨 避坑1:JS代码必须放在CSS前面(优先修改根字体大小),否则CSS中的rem计算会出错。

  • 🚨 避坑2:设计稿基准宽度(baseWidth)要和UI设计稿一致(通常375px/750px),否则缩放比例不对。

  • 🚨 避坑3:不要混合使用px和rem(除了根字体大小),否则无法实现等比例自适应。

三、Vw/Vh适配 —— 极简无依赖方案

搜索量TOP3,最简洁的自适应方案,核心逻辑:以「屏幕可视区域」为基准,1vw = 屏幕宽度的1%,1vh = 屏幕高度的1%,无需JS、无需媒体查询,直接用vw/vh单位,适配所有屏幕(推荐简单页面使用)。

1. 核心单位说明(必记)

  • ✅ 1vw = 视口宽度的1%(例:屏幕宽375px,1vw = 3.75px)

  • ✅ 1vh = 视口高度的1%(例:屏幕高667px,1vh = 6.67px)

  • ✅ vmin = vw和vh中的最小值,vmax = vw和vh中的最大值(适配横竖屏)

2. 实战示例(极简自适应页面)

需求:实现一个全屏自适应卡片,居中显示,字体、间距随屏幕缩放。


<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vw/Vh自适应实战</title>
  <style>
    /* 重置默认边距 */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    /* 页面全屏 */
    body {
      width: 100vw; /* 占满屏幕宽度 */
      height: 100vh; /* 占满屏幕高度 */
      display: flex;
      align-items: center;
      justify-content: center;
      background: #f8f9fa;
    }

    /* 自适应卡片(所有尺寸用vw) */
    .vw-card {
      width: 80vw; /* 卡片宽 = 屏幕宽的80% */
      height: 50vw; /* 卡片高 = 屏幕宽的50%(避免竖屏过高) */
      background: #6495ed;
      color: #fff;
      border-radius: 2vw; /* 圆角随屏幕缩放 */
      padding: 5vw;
      font-size: 4vw; /* 字体大小 = 屏幕宽的4% */
      text-align: center;
      display: flex;
      align-items: center;
      justify-content: center;
    }
  </style>
</head>
<body>
  <div class="vw-card">Vw/Vh自适应<br/>极简无依赖</div>
</body>
</html>
    

3. 核心避坑

  • 🚨 避坑1:同样需要添加viewport meta标签,否则vw/vh的计算基准会错误(适配移动端)。

  • 🚨 避坑2:vh会受手机状态栏、导航栏影响,高度建议用vw或vmin,避免竖屏时高度溢出。

  • 🚨 避坑3:复杂页面不推荐单独使用vw/vh(会出现字体过大/过小),建议配合媒体查询微调。

四、Flex/Grid自适应布局 —— 配合适配方案使用

搜索量TOP4,布局层面的自适应,核心逻辑:通过Flex(弹性布局)或Grid(网格布局),让元素自动分配空间,配合前面的媒体查询、rem、vw/vh,实现更灵活的自适应,是项目中最常用的组合方案。

1. Flex自适应(最常用,重点记)


<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Flex自适应布局</title>
  <style>
    .flex-container {
      display: flex; /* 开启Flex布局 */
      flex-wrap: wrap; /* 自动换行(关键,适配小屏幕) */
      gap: 1rem; /* 间距用rem,配合Rem适配 */
      padding: 1rem;
    }

    .flex-item {
      flex: 1; /* 自动分配剩余空间,实现均分 */
      min-width: 200px; /* 最小宽度,小于这个宽度就换行 */
      height: 100px;
      background: #f08080;
      color: #fff;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 8px;
    }

    /* 移动端微调(配合媒体查询) */
    @media screen and (max-width: 768px) {
      .flex-item {
        min-width: 100%; /* 移动端占满宽度,1列显示 */
      }
    }
  </style>
</head>
<body>
  <div class="flex-container">
    <div class="flex-item">Flex item1</div>
    <div class="flex-item">Flex item2</div>
    <div class="flex-item">Flex item3</div>
  </div>
</body>
</html>
    

2. Grid自适应(复杂布局首选)


<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Grid自适应布局</title>
  <style>
    .grid-container {
      display: grid; /* 开启Grid布局 */
      /* 关键:自动填充,每列最小200px,最大1fr(均分) */
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      gap: 1rem;
      padding: 1rem;
    }

    .grid-item {
      height: 100px;
      background: #9370db;
      color: #fff;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 8px;
    }

    /* 移动端微调 */
    @media screen and (max-width: 768px) {
      .grid-container {
        grid-template-columns: 1fr; /* 移动端1列布局 */
      }
    }
  </style>
</head>
<body>
  <div class="grid-container">
    <div class="grid-item">Grid item1</div>
    <div class="grid-item">Grid item2</div>
    <div class="grid-item">Grid item3</div>
    <div class="grid-item">Grid item4</div>
  </div>
</body>
</html>
    

3. 核心避坑

  • 🚨 避坑1:Flex布局中,flex-wrap: wrap 必须写,否则元素不会换行,会溢出屏幕。

  • 🚨 避坑2:Grid布局的grid-template-columns,用auto-fill配合minmax,实现自动适配列数,新手首选。

  • 🚨 避坑3:Flex/Grid只是布局方式,单独使用无法实现“字体、间距”自适应,需配合媒体查询/rem/vw使用。

五、实战组合方案(重点!项目必用)

实际开发中,不会单独使用某一种方案,推荐「组合搭配」,兼顾兼容性和灵活性,示例如下(可直接复制到项目中):


<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CSS自适应组合方案</title>
  <!-- 1. Rem自适应JS(优先加载) -->
  <script>
    function setRemUnit() {
      const screenWidth = document.documentElement.clientWidth || window.innerWidth;
      const baseWidth = 375;
      const fontSize = screenWidth / 10;
      document.documentElement.style.fontSize = Math.min(fontSize, 32) + 'px';
    }
    setRemUnit();
    window.addEventListener('resize', setRemUnit);
  </script>
  <style>
    /* 2. 通用样式(rem单位) */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body {
      font-size: 1rem;
      background: #f5f5f5;
    }

    /* 3. Flex布局(自适应布局) */
    .container {
      display: flex;
      flex-wrap: wrap;
      gap: 1rem;
      padding: 1rem;
    }

    .item {
      flex: 1;
      min-width: 12rem; /* 192px(以375px屏幕为例) */
      height: 8rem;
      background: #42b983;
      color: #fff;
      border-radius: 0.5rem;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    /* 4. 媒体查询(微调细节) */
    @media screen and (max-width: 768px) {
      .container {
        gap: 0.8rem;
        padding: 0.8rem;
      }
      .item {
        min-width: 100%; /* 移动端1列 */
      }
    }

    @media screen and (min-width: 1201px) {
      .container {
        max-width: 1200px; /* PC端限制最大宽度,避免过宽 */
        margin: 0 auto;
      }
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="item">组合方案1</div>
    <div class="item">组合方案2</div>
    <div class="item">组合方案3</div>
  </div>
</body>
</html>
    

六、总结(重点提炼,快速记忆)

  • ✅ 媒体查询:万能方案,兼容所有浏览器,适合PC+移动端,核心是“按屏幕宽度写样式”。

  • ✅ Rem适配:移动端首选,等比例缩放,需配合JS修改根字体大小,适配所有手机。

  • ✅ Vw/Vh适配:极简无依赖,适合简单页面,核心是“以屏幕可视区域为基准”。

  • ✅ Flex/Grid:布局方案,配合前面3种使用,实现元素自动分配空间,灵活适配。

  • 💡 项目首选:Rem + Flex/Grid + 媒体查询(兼顾兼容性、灵活性和开发效率)。

✏️ CSS自适应教程完结,所有代码均可直接复制运行,避坑点已标注清楚~ 收藏起来,开发时直接抄作业,再也不用愁屏幕适配问题!

Vue3 爆款实用插件教程

作者 青屿ovo
2026年2月11日 11:34

Vue3 爆款实用插件教程(全新版·高频搜索款)

✏️ 开篇说明 🎨

本文为全新原创插件教程,避开所有之前用过的插件,精选「网上搜索量TOP3、90%项目必用」的Vue3爆款插件,主打极简可复制、新手零门槛,每款插件只讲核心用法+避坑,开发时直接抄作业,有错欢迎指出来~

📌 三款高频爆款插件(搜索量拉满,刚需实用)

精选插件:VueUse(万能工具集)、vue-i18n(国际化)、Vant 4(移动端UI组件库),均为Vue3生态搜索量TOP级,覆盖工具、多语言、移动端开发三大高频场景,实用性拉满!

一、VueUse(搜索量TOP1,Vue3万能工具集)

VueUse 是Vue3生态最火的工具集插件,搜索量常年稳居前列,封装了100+常用工具(防抖节流、本地存储、窗口操作、权限判断等),不用重复造轮子,极大提升开发效率,新手必备,一行代码调用,无需复杂配置。

1. 安装VueUse


# npm安装(推荐)
npm install @vueuse/core
# yarn安装
yarn add @vueuse/core

2. 核心用法(4个高频工具,极简示例可复制)

VueUse 无需全局配置,引入即用,以下是开发中最常用的4个工具,覆盖80%使用场景:

(1)useLocalStorage(本地存储,替代localStorage原生写法)

核心优势:自动实现响应式,无需手动JSON.parse/JSON.stringify,刷新页面数据不丢失。


<script setup>
// 引入工具(按需引入,不浪费体积)
import { useLocalStorage } from '@vueuse/core'
import { ref } from 'vue'

// 用法:参数1=存储key,参数2=默认值(自动响应式)
const userInfo = useLocalStorage('userInfo', { name: '测试', age: 20 })
const token = useLocalStorage('token', '')

// 直接修改,自动同步到localStorage(无需手动setItem)
const updateUser = () => {
  userInfo.value.name = '新名字' // 自动同步到localStorage
  token.value = 'abc123456' // 自动同步
}

// 清空存储
const clearStorage = () => {
  userInfo.value = null
  token.value = ''
}
</script>

<template>
  <div>
    <p>用户名:{{ userInfo.name }}</p>
    <button @click="updateUser">修改用户信息</button>
    <button @click="clearStorage">清空存储</button>
  </div>
</template>
(2)useDebounceFn(防抖,解决频繁触发问题)

高频场景:搜索框输入、按钮频繁点击、滚动事件,避免多次触发接口/方法,提升性能。


<script setup>
import { useDebounceFn } from '@vueuse/core'
import { ref } from 'vue'

const inputVal = ref('')

// 防抖方法:参数1=要防抖的函数,参数2=防抖时间(ms)
const search = useDebounceFn((val) => {
  // 模拟搜索接口请求(防抖后,输入停止500ms才触发)
  console.log('搜索关键词:', val)
}, 500)

// 输入框输入时触发防抖方法
const handleInput = (e) => {
  inputVal.value = e.target.value
  search(inputVal.value)
}
</script>

<template>
  <input 
    v-model="inputVal" 
    placeholder="请输入搜索关键词"
    @input="handleInput"
  />
</template>
(3)useThrottleFn(节流,控制方法触发频率)

高频场景:滚动加载、resize事件,控制方法每隔固定时间只能触发一次,区别于防抖(停止输入才触发)。


<script setup>
import { useThrottleFn } from '@vueuse/core'

// 节流方法:每隔1000ms只能触发一次
const handleScroll = useThrottleFn(() => {
  console.log('滚动事件触发(节流):', window.scrollY)
}, 1000)

// 监听滚动事件
window.addEventListener('scroll', handleScroll)

// 组件卸载时移除监听(避免内存泄漏)
onUnmounted(() => {
  window.removeEventListener('scroll', handleScroll)
})
</script>
(4)useWindowSize(监听窗口大小,适配响应式)

高频场景:响应式布局、适配移动端/PC端,自动监听窗口宽高变化,无需手动写resize事件。


<script setup>
import { useWindowSize } from '@vueuse/core'

// 自动监听窗口宽高,响应式变化
const { width, height } = useWindowSize()

// 可直接判断设备类型(比如移动端宽小于768px)
const isMobile = computed(() => {
  return width.value < 768
})
</script>

<template>
  <div>
    <p>窗口宽度:{{ width }}px</p>
    <p>窗口高度:{{ height }}px</p>
    <p v-if="isMobile">当前为移动端布局</p>
    <p v-else>当前为PC端布局</p>
  </div>
</template>

3. 核心避坑

  • 🚨 避坑1:VueUse 支持按需引入,不要全局引入所有工具(会增加项目体积),用什么引什么。

  • 🚨 避坑2:useLocalStorage 存储复杂数据(对象/数组)时,无需手动序列化,直接赋值即可。

  • 🚨 避坑3:监听类工具(如useWindowSize、useScroll),组件卸载时无需手动清除,VueUse会自动清理,避免内存泄漏。

二、vue-i18n(搜索量TOP2,国际化必备插件)

vue-i18n 是Vue3官方推荐的国际化插件,搜索量常年稳居前列,高频用于多语言项目(中/英/日等),支持文本翻译、日期/数字格式化,配置简单,切换语言无刷新,企业级项目必用。

1. 安装vue-i18n


# npm安装(推荐)
npm install vue-i18n@9
# yarn安装
yarn add vue-i18n@9

⚠️ 注意:Vue3 必须安装 vue-i18n@9+ 版本,低版本不兼容。

2. 全局配置(3步搞定,一次配置全局可用)

步骤1:创建多语言文件(src/lang目录)

在src目录下新建lang文件夹,创建中文(zh-CN.js)、英文(en-US.js)两个语言文件(可扩展其他语言):


// src/lang/zh-CN.js(中文)
export default {
  home: '首页',
  about: '关于我们',
  login: '登录',
  username: '用户名',
  password: '密码',
  submit: '提交',
  switchLang: '切换语言'
}

// src/lang/en-US.js(英文)
export default {
  home: 'Home',
  about: 'About Us',
  login: 'Login',
  username: 'Username',
  password: 'Password',
  submit: 'Submit',
  switchLang: 'Switch Language'
}
步骤2:配置i18n(src/lang/index.js)
// src/lang/index.js
import { createI18n } from 'vue-i18n'
// 引入多语言文件
import zhCN from './zh-CN'
import enUS from './en-US'

// 创建i18n实例
const i18n = createI18n({
  legacy: false, // 必须设为false,适配Vue3的<script setup>语法糖
  locale: 'zh-CN', // 默认语言(中文)
  fallbackLocale: 'en-US', // 备用语言(当默认语言没有对应翻译时,使用英文)
  messages: {
    'zh-CN': zhCN, // 中文语言包
    'en-US': enUS  // 英文语言包
  }
})

export default i18n
步骤3:全局注册(main.js)

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './lang' // 引入i18n配置

const app = createApp(App)
app.use(i18n) // 全局注册i18n
app.mount('#app')

3. 组件内使用(极简示例,翻译+切换语言)


<script setup>
// 引入useI18n,获取翻译方法和语言切换方法
import { useI18n } from 'vue-i18n'

// 解构t(翻译方法)、locale(当前语言)、setLocale(切换语言)
const { t, locale, setLocale } = useI18n()

// 切换语言方法(中文 ↔ 英文)
const switchLanguage = () => {
  if (locale.value === 'zh-CN') {
    setLocale('en-US') // 切换为英文
  } else {
    setLocale('zh-CN') // 切换为中文
  }
}
</script>

<template>
  <div class="lang-demo">
    <h1>{{ t('home') }}</h1>
    <div class="login-form">
      <label>{{ t('username') }}:</label>
      <input placeholder="{{ t('username') }}" />
      
      <label>{{ t('password') }}:</label>
      <input type="password" placeholder="{{ t('password') }}" />
      
      <button @click="switchLanguage">{{ t('switchLang') }}</button>
      <button>{{ t('submit') }}</button>
    </div>
  </div>
</template>

4. 核心避坑

  • 🚨 避坑1:必须设置 legacy: false,否则

  • 🚨 避坑2:翻译关键词(如home、username)必须和语言文件中的关键词完全一致,否则无法显示翻译。

  • 🚨 避坑3:切换语言后,页面会自动刷新翻译内容,无需手动刷新页面,复杂数据也会自动适配。

三、Vant 4(搜索量TOP3,移动端UI组件库)

Vant 4 是Vue3生态最火的移动端UI组件库,搜索量稳居移动端UI榜首,由有赞团队开发,封装了60+移动端常用组件(按钮、表单、弹窗、轮播、导航等),适配各种移动端设备,样式美观、可自定义,快速搭建移动端页面,企业级移动端项目首选。

1. 安装Vant 4


# npm安装(推荐)
npm install vant
# yarn安装
yarn add vant

2. 全局配置(新手首选,一次配置全局可用)

// main.js
import { createApp } from 'vue'
import App from './App.vue'
// 1. 引入Vant核心和样式
import Vant from 'vant'
import 'vant/lib/index.css'

const app = createApp(App)
app.use(Vant) // 全局注册Vant,所有组件可直接使用
app.mount('#app')

进阶:按需引入(精简体积),新手可先全局引入,后期优化时再改为按需引入。

3. 组件内使用(3个高频组件,移动端必备)

精选移动端开发最常用的3个组件,示例可直接复制,适配各种移动端场景:

(1)Button(按钮组件,移动端高频)

<template>
  <div class="button-demo">
    <!-- 基础按钮 -->
    <van-button>默认按钮</van-button>
    
    <!-- 不同类型按钮(主色/成功/危险) -->
    <van-button type="primary">主色按钮</van-button>
    <van-button type="success">成功按钮</van-button>
    <van-button type="danger">危险按钮</van-button>
    
    <!-- 禁用状态/加载状态 -->
    <van-button disabled>禁用按钮</van-button>
    <van-button loading>加载中按钮</van-button>
    
    <!-- 自定义大小/颜色 -->
    <van-button size="small">小尺寸按钮</van-button>
    <van-button color="#42b983">自定义颜色按钮</van-button>
  </div>
</template>
(2)Swipe(轮播组件,首页必备)

<template>
  <!-- 轮播组件(自动播放、指示器、点击切换) -->
  <van-swipe class="swipe-demo" autoplay interval="3000" indicator-color="#fff">
    <van-swipe-item>
      <img src="/images/banner1.jpg" alt="轮播图1" class="swipe-img" />
    </van-swipe-item>
    <van-swipe-item>
      <img src="/images/banner2.jpg" alt="轮播图2" class="swipe-img" />
    </van-swipe-item>
    <van-swipe-item>
      <img src="/images/banner3.jpg" alt="轮播图3" class="swipe-img" />
    </van-swipe-item>
  </van-swipe>
</template>

<style scoped>
.swipe-demo {
  height: 180px; /* 移动端轮播图常用高度 */
}
.swipe-img {
  width: 100%;
  height: 100%;
  object-fit: cover; /* 图片自适应,不拉伸 */
}
</style>
(3)Form(表单组件,登录/注册必备)

<script setup>
import { ref } from 'vue'
import { useToast } from 'vant' // 引入Vant的提示组件(配合表单校验)

const toast = useToast()
// 表单数据
const form = ref({
  username: '',
  password: ''
})

// 表单校验+提交
const handleSubmit = () => {
  if (!form.value.username) {
    toast('请输入用户名')
    return
  }
  if (!form.value.password) {
    toast('请输入密码')
    return
  }
  // 校验通过,提交表单(模拟登录接口)
  console.log('表单提交:', form.value)
  toast.success('登录成功')
}
</script>

<template>
  <div class="form-demo">
    <van-form @submit="handleSubmit">
      <van-field
        v-model="form.username"
        label="用户名"
        placeholder="请输入用户名"
        required
      />
      <van-field
        v-model="form.password"
        label="密码"
        type="password"
        placeholder="请输入密码"
        required
      />
      <van-button type="primary" block native-type="submit">登录</van-button>
    </van-form>
  </div>
</template>

4. 核心避坑

  • 🚨 避坑1:Vant 4 仅适配Vue3,Vue2项目需使用Vant 2版本,不要装错版本。

  • 🚨 避坑2:移动端组件需配合viewport配置(public/index.html),否则样式错乱。

  • 🚨 避坑3:表单组件的native-type="submit"不要遗漏,否则表单提交事件无法触发。

<!-- public/index.html 必须添加viewport配置(移动端适配) -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

四、插件总结(重点提炼,快速记忆)

  • ✅ VueUse:万能工具集,搜索量TOP1,防抖节流、本地存储等100+工具,引入即用,不用造轮子。

  • ✅ vue-i18n:国际化必备,搜索量TOP2,多语言切换无刷新,配置简单,企业级项目必用。

  • ✅ Vant 4:移动端UI首选,搜索量TOP3,组件丰富、样式美观,快速搭建移动端页面。

💡 小技巧:这3款插件覆盖Vue3项目90%的高频场景,示例可直接复制到项目中使用,无需复杂修改;后续开发遇到工具、多语言、移动端相关需求,直接查这篇即可!

Vue3 手绘风爆款实用笔记合集(含避坑+实战+API)

作者 青屿ovo
2026年2月10日 16:48

Vue3 手绘风爆款实用笔记合集(含避坑+实战+API)

✏️ 开篇说明 🎨

合集主打「爆款实用」,每篇聚焦1个Vue3高频需求,随手记、轻松学,核心知识点+极简可复制示例,新手友好、进阶可用,有错欢迎指出来~

📌 第一篇:Vue3 新手必看避坑指南(90%的人都踩过!)

新手学Vue3,最容易栽在细节上!这篇汇总高频坑点,附解决方案,帮你少走弯路、快速上手~

一、响应式数据踩坑(最高频!)

坑1:ref在JS里忘加.value


<script setup>
import { ref } from 'vue'
const count = ref(0)

// ❌ 错误:JS里操作ref忘了加.value,数据不更新
const add = () => {
  count++ 
}

// ✅ 正确:JS里必须加.value,模板里不用
const add = () => {
  count.value++ 
}
</script>

坑2:reactive绑简单类型无效


<script setup>
import { reactive } from 'vue'

// ❌ 错误:reactive只能绑对象/数组,简单类型无效
const count = reactive(0) // 不会触发响应式更新

// ✅ 正确:简单类型用ref,或包成对象用reactive
const count = ref(0)
// 或
const data = reactive({ count: 0 })
</script>

坑3:解构reactive数据丢失响应式


<script setup>
import { reactive } from 'vue'
const user = reactive({ name: '手绘君', age: 20 })

// ❌ 错误:解构后的数据不是响应式的
const { name } = user
name = '新名字' // 页面不更新

// ✅ 正确:用toRefs解构,保留响应式
import { reactive, toRefs } from 'vue'
const { name } = toRefs(user)
name.value = '新名字' // 正常更新
</script>

二、组件相关避坑

坑4:

🚨 重点:


<script setup>
import { ref } from 'vue'
const count = ref(0)

// ❌ 错误:setup语法糖里没有this
const add = () => {
  this.count.value++ 
}

// ✅ 正确:直接用变量名
const add = () => {
  count.value++ 
}
</script>

坑5:组件传值忘了defineProps/defineEmits


<!-- 子组件 ❌ 错误示例 -->
<script setup>
// 忘了defineProps,直接用props会报错
console.log(props.msg)

// 忘了defineEmits,直接emit会报错
emit('change')
</script>

<!-- 子组件 ✅ 正确示例 -->
<script setup>
// 父传子:defineProps声明
const props = defineProps({
  msg: String
})

// 子传父:defineEmits声明
const emit = defineEmits(['change'])
const send = () => {
  emit('change', '子组件消息')
}
</script>

三、其他高频避坑

  • 🚨 坑6:Vue3移除了filter,用computed替代

  • 🚨 坑7:v-model绑定reactive对象,不用加.value(模板里)

  • 🚨 坑8:onMounted等生命周期,不用写在methods里,直接在setup里调用

💡 小总结:新手避坑核心——记住ref/reative用法、setup语法糖无this、传值需声明!

📌 第二篇:Pinia 极简实战笔记(Vue3状态管理首选)

Vue3官方推荐用Pinia替代Vuex,更轻量、更简洁、不用配置modules,新手也能快速上手,这篇吃透核心用法~

一、快速上手(3步搞定)

1. 安装Pinia


# npm安装
npm install pinia
# yarn安装
yarn add pinia

2. 全局注册Pinia(main.js)

// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia' // 引入Pinia
import App from './App.vue'

const app = createApp(App)
app.use(createPinia()) // 注册Pinia
app.mount('#app')

3. 创建Store(核心文件)

在src目录下新建store文件夹,创建index.js(或单独创建userStore.js),用defineStore定义仓库。


// src/store/index.js
import { defineStore } from 'pinia'

// 第一个参数:仓库名(唯一,不能重复)
// 第二个参数:配置对象(state/actions/getters)
export const useUserStore = defineStore('user', {
  // 状态(类似Vue2的data)
  state: () => ({
    name: '手绘君',
    age: 20,
    isLogin: false
  }),
  // 方法(类似Vue2的methods,可修改state)
  actions: {
    // 修改单个状态
    setName(newName) {
      this.name = newName // 这里的this指向state,不用.value!
    },
    // 修改多个状态
    login(userInfo) {
      this.name = userInfo.name
      this.isLogin = true
    }
  },
  // 计算属性(类似Vue2的computed)
  getters: {
    // 简单计算
    doubleAge() {
      return this.age * 2
    },
    // 带参数的计算(返回一个函数)
    getAgeAdd(n) {
      return this.age + n
    }
  }
})

二、组件中使用Store


<script setup>
// 1. 引入创建好的Store
import { useUserStore } from '@/store'

// 2. 实例化Store
const userStore = useUserStore()

// 3. 使用state(3种方式)
console.log(userStore.name) // 方式1:直接使用
console.log(userStore.$state.age) // 方式2:通过$state访问

// 4. 调用actions(直接调用,不用dispatch!)
userStore.setName('新名字')
userStore.login({ name: '测试君' })

// 5. 使用getters(直接调用,不用加括号,带参数除外)
console.log(userStore.doubleAge) // 40
console.log(userStore.getAgeAdd(5)) // 25

// 6. 重置state(一键恢复初始值)
const reset = () => {
  userStore.$reset()
}
</script>

<template>
  <div>
    <p>姓名:{{ userStore.name }}</p>
    <p>年龄:{{ userStore.age }}</p>
    <p>年龄翻倍:{{ userStore.doubleAge }}</p>
    <button @click="userStore.setName('小明')">修改姓名</button>
  </div>
</template>

三、核心优势(为什么用Pinia?)

  • ✅ 无需配置modules:一个文件就是一个仓库,不用嵌套

  • ✅ 不用commit/mutations:直接在actions里修改state,更简洁

  • ✅ TypeScript友好:自动推导类型,不用手动声明

  • ✅ 体积小:仅1KB左右,比Vuex轻量很多

  • ✅ 支持热更新:修改Store不用重启项目

💡 小总结:Pinia用法=定义仓库+实例化+直接使用,新手无脑冲就对了!

📌 第三篇:Vue Router 4 实战手册(Vue3配套路由)

Vue3配套Vue Router 4,单页面应用(SPA)必备,这篇覆盖路由核心用法:路由配置、跳转、传参、守卫,极简示例可直接复制!

一、快速上手(4步搞定)

1. 安装Vue Router 4


# 注意:Vue3必须装vue-router@4版本
npm install vue-router@4
yarn add vue-router@4

2. 创建路由配置文件

src目录下新建router文件夹,创建index.js:


// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

// 1. 引入组件(两种方式)
// 方式1:普通引入(适合首页、高频组件)
import Home from '@/views/Home.vue'
// 方式2:懒加载(适合非首页,优化性能)
const About = () => import('@/views/About.vue')

// 2. 路由规则配置
const routes = [
  // 首页路由
  {
    path: '/', // 路由路径(url地址)
    name: 'Home', // 路由名称(唯一)
    component: Home, // 对应组件
    meta: {
      title: '首页', // 页面标题(可自定义)
      requireAuth: false // 是否需要登录(自定义)
    }
  },
  // 关于页路由
  {
    path: '/about',
    name: 'About',
    component: About,
    meta: { title: '关于我们' }
  },
  // 动态路由(参数传递)
  {
    path: '/user/:id', // :id是动态参数
    name: 'User',
    component: () => import('@/views/User.vue'),
    meta: { title: '用户中心' }
  },
  // 404路由(匹配所有未定义路由)
  {
    path: '/:pathMatch(.*)*', // 匹配所有路径
    name: 'NotFound',
    component: () => import('@/views/NotFound.vue'),
    meta: { title: '页面不存在' }
  }
]

// 3. 创建路由实例
const router = createRouter({
  history: createWebHistory(), // 哈希模式用createWebHashHistory()
  routes // 传入路由规则
})

// 4. 导出路由
export default router

3. 全局注册路由(main.js)


// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 引入路由

const app = createApp(App)
app.use(router) // 注册路由
app.mount('#app')

4. 配置路由出口(App.vue)


<template>
  &lt;div id="app"&gt;
    <!-- 路由导航(类似a标签) -->
    <nav>
      <router-link to="/">首页</router-link>
      <router-link to="/about">关于我们</router-link>
      <router-link :to="{ name: 'User', params: { id: 123 } }"&gt;用户中心&lt;/router-link&gt;
    &lt;/nav&gt;
    
    <!-- 路由出口:匹配的组件会渲染到这里 -->
    <router-view></router-view>
  </div>
</template>

二、核心用法(高频刚需)

1. 路由跳转(3种方式)


<script setup>
import { useRouter, useRoute } from 'vue-router'

// 实例化路由
const router = useRouter() // 用于跳转
const route = useRoute() // 用于获取路由信息

// 方式1:router-link(模板中使用,最简洁)
// <router-link to="/home">首页</router-link>

// 方式2:编程式跳转(JS中使用)
const goHome = () => {
  router.push('/home') // 直接传路径
  // 或传对象(推荐,可传参数)
  router.push({
    name: 'Home', // 用路由名称跳转(更稳定)
    query: { name: '手绘君' } // 拼接在url上的参数
  })
}

// 方式3:替换当前路由(不会留下历史记录,适合登录页)
const goLogin = () => {
  router.replace('/login')
}

// 后退/前进
const goBack = () => {
  router.back() // 后退
  // router.forward() // 前进
}
</script>

2. 路由传参(2种方式)

方式1:query传参(url拼接,类似get请求)

// 跳转页:传参
router.push({
  name: 'About',
  query: { id: 1, msg: '测试' }
})
// url会变成:/about?id=1&msg=测试

// 接收页:获取参数
const route = useRoute()
console.log(route.query.id) // 1
console.log(route.query.msg) // 测试
方式2:params传参(动态路由,url不拼接,类似post请求)
// 1. 先配置动态路由(router/index.js)
{
  path: '/user/:id', // :id是动态参数
  name: 'User',
  component: User
}

// 2. 跳转页:传参
router.push({
  name: 'User', // 必须用name跳转,不能用path
  params: { id: 123, name: '手绘君' }
})

// 3. 接收页:获取参数
const route = useRoute()
console.log(route.params.id) // 123
console.log(route.params.name) // 手绘君

3. 路由守卫(权限控制核心)

以全局前置守卫为例(登录拦截),其他守卫用法类似:

// router/index.js
router.beforeEach((to, from, next) => {
  // to:要跳转到的路由
  // from:从哪个路由跳转过来
  // next:放行/跳转的函数

  // 1. 设置页面标题
  document.title = to.meta.title || 'Vue3笔记'

  // 2. 登录拦截(只有登录才能访问/user路由)
  const isLogin = localStorage.getItem('isLogin') // 模拟登录状态
  if (to.name === 'User' && !isLogin) {
    // 未登录,跳转到登录页
    next('/login')
  } else {
    // 已登录/无需登录,放行
    next()
  }
})

💡 小总结:路由核心=配置路由+跳转+传参+守卫,掌握这4点,满足90%的项目需求!

📌 第四篇:Vue3 组件封装实战(新手也能学会)

组件封装是Vue3的核心优势,能大幅提高代码复用率、简化开发,这篇以3个高频组件(按钮、输入框、弹窗)为例,教你从零封装可复用组件!

一、封装核心原则

  • ✅ 单一职责:一个组件只做一件事(比如按钮组件只负责按钮展示和点击)

  • ✅ 可复用:通过props传参,适配不同场景

  • ✅ 可扩展:通过slot插槽,支持自定义内容

  • ✅ 易维护:代码简洁,注释清晰,逻辑分离

二、实战1:封装通用按钮组件(Btn.vue)


<!-- src/components/Btn.vue -->
<template>
  <button 
    class="custom-btn"
    :class="['btn-' + type, { 'btn-disabled': disabled }]"
    @click="handleClick"
    :disabled="disabled"
  &gt;
    <!-- 插槽:自定义按钮内容 -->
    <slot>默认按钮</slot>
  </button>
</template>

<script setup>
// 1. 接收父组件传参(props)
const props = defineProps({
  // 按钮类型(primary/success/danger/default)
  type: {
    type: String,
    default: 'default' // 默认值
  },
  // 是否禁用
  disabled: {
    type: Boolean,
    default: false
  }
})

// 2. 子传父:点击事件
const emit = defineEmits(['click'])
const handleClick = () => {
  // 禁用状态下不触发事件
  if (!props.disabled) {
    emit('click')
  }
}
</script>

<style scoped>
.custom-btn {
  padding: 6px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}
/* 不同类型按钮样式 */
.btn-default {
  background: #f5f5f5;
  color: #333;
}
.btn-primary {
  background: #42b983; /* Vue绿 */
  color: #fff;
}
.btn-success {
  background: #67c23a;
  color: #fff;
}
.btn-danger {
  background: #f56c6c;
  color: #fff;
}
/* 禁用样式 */
.btn-disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
</style>

组件使用示例


<template>
  <div>
    <Btn @click="handleClick">默认按钮</Btn>
    <Btn type="primary" @click="handleClick">主要按钮</Btn>
    <Btn type="success" @click="handleClick">成功按钮</Btn>
    <Btn type="danger" disabled>禁用按钮</Btn>
    <!-- 插槽自定义内容 -->
    <Btn type="primary">
      <i class="icon">✓</i> 带图标按钮
    </Btn>
  </div>
</template>

<script setup>
import Btn from '@/components/Btn.vue'

const handleClick = () => {
  console.log('按钮被点击了')
}
</script>

三、实战2:封装通用输入框组件(Input.vue)


<!-- src/components/Input.vue -->
<template>
  &lt;div class="custom-input"&gt;
    <!-- 标签插槽 -->
    <label class="input-label" v-if="label">{{ label }}</label>
    <input
      class="input-content"
      :type="type"
      :placeholder="placeholder"
      :value="modelValue"
      @input="handleInput"
      :disabled="disabled"
    />
  </div>
</template>

<script setup>
// 1. 接收父组件传参
const props = defineProps({
  // 输入框类型(text/password/number)
  type: {
    type: String,
    default: 'text'
  },
  // 占位提示
  placeholder: {
    type: String,
    default: ''
  },
  // 绑定值(v-model)
  modelValue: {
    type: [String, Number],
    default: ''
  },
  // 标签文本
  label: {
    type: String,
    default: ''
  },
  // 是否禁用
  disabled: {
    type: Boolean,
    default: false
  }
})

// 2. 子传父:同步输入值(配合v-model使用)
const emit = defineEmits(['update:modelValue'])
const handleInput = (e) => {
  emit('update:modelValue', e.target.value)
}
</script>

<style scoped>
.custom-input {
  display: flex;
  align-items: center;
  margin: 10px 0;
}
.input-label {
  width: 80px;
  font-size: 14px;
  margin-right: 10px;
}
.input-content {
  padding: 6px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
  width: 200px;
}
.input-content:disabled {
  background: #f5f5f5;
  cursor: not-allowed;
}
</style>

组件使用示例(v-model绑定)


<template>
  <div>
    <Input 
      label="用户名" 
      placeholder="请输入用户名" 
      v-model="username"
    />
    <Input 
      label="密码" 
      type="password" 
      placeholder="请输入密码" 
      v-model="password"
    />
    <Input 
      label="手机号" 
      type="number" 
      placeholder="请输入手机号" 
      v-model="phone"
      disabled
    />
  </div>
</template>

<script setup>
import Input from '@/components/Input.vue'
import { ref } from 'vue'

const username = ref('')
const password = ref('')
const phone = ref('13800138000')
</script>

四、实战3:封装通用弹窗组件(Modal.vue)


<!-- src/components/Modal.vue -->
<template>
  <div class="modal-mask" v-if="visible" @click="handleMaskClick">
    &lt;div class="modal-content" @click.stop&gt;
      <!-- 弹窗标题 -->
      <div class="modal-header">
        <h3 class="modal-title">{{ title }}</h3>
        <button class="modal-close" @click="handleClose">×</button>
      </div>
      <!-- 弹窗内容(插槽,自定义) -->
      <div class="modal-body">
        <slot>默认弹窗内容</slot>
      </div&gt;
      <!-- 弹窗底部(插槽,自定义按钮) -->
      <div class="modal-footer">
        <slot name="footer">
          <Btn @click="handleClose">取消</Btn>
          <Btn type="primary" @click="handleConfirm">确认</Btn>
        </slot>
      </div>
    </div>
  </div>
</template>

<script setup>
import Btn from './Btn.vue' // 引入之前封装的按钮组件

// 接收父组件传参
const props = defineProps({
  // 是否显示弹窗
  visible: {
    type: Boolean,
    default: false
  },
  // 弹窗标题
  title: {
    type: String,
    default: '提示'
  },
  // 点击遮罩是否关闭
  maskClose: {
    type: Boolean,
    default: true
  }
})

// 子传父:关闭、确认事件
const emit = defineEmits(['close', 'confirm'])
// 关闭弹窗
const handleClose = () => {
  emit('close')
}
// 确认按钮
const handleConfirm = () => {
  emit('confirm')
}
// 点击遮罩关闭
const handleMaskClick = () => {
  if (props.maskClose) {
    emit('close')
  }
}
</script>

<style scoped>
/* 遮罩层 */
.modal-mask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 999;
}
/* 弹窗内容 */
.modal-content {
  width: 400px;
  background: #fff;
  border-radius: 8px;
  overflow: hidden;
}
/* 弹窗标题 */
.modal-header {
  padding: 16px;
  border-bottom: 1px solid #eee;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.modal-title {
  font-size: 16px;
  margin: 0;
}
.modal-close {
  border: none;
  background: none;
  font-size: 20px;
  cursor: pointer;
  color: #999;
}
/* 弹窗内容 */
.modal-body {
  padding: 20px;
  font-size: 14px;
}
/* 弹窗底部 */
.modal-footer {
  padding: 16px;
  border-top: 1px solid #eee;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
</style>

组件使用示例


<template>
  <div>
    <Btn type="primary" @click="showModal = true">打开弹窗</Btn>
    
    <Modal
      :visible="showModal"
      title="提示弹窗"
      :mask-close="false"
      @close="showModal = false"
      @confirm="handleConfirm"
    >
      <!-- 自定义弹窗内容 -->
      <p>确定要执行这个操作吗?</p>
      <Input placeholder="请输入备注" v-model="remark" />
      <!-- 自定义底部按钮(覆盖默认) -->
      <template #footer>
        <Btn @click="showModal = false">取消</Btn>
        <Btn type="danger" @click="handleDelete">删除</Btn>
        <Btn type="primary" @click="handleConfirm">确认</Btn>
      </template>
    </Modal>
  </div>
</template>

<script setup>
import Modal from '@/components/Modal.vue'
import Input from '@/components/Input.vue'
import Btn from '@/components/Btn.vue'
import { ref } from 'vue'

const showModal = ref(false)
const remark = ref('')

const handleConfirm = () => {
  console.log('确认操作,备注:', remark.value)
  showModal.value = false
}

const handleDelete = () => {
  console.log('执行删除操作')
  showModal.value = false
}
</script>

💡 小总结:组件封装=props传参+slot插槽+emit传事件,掌握这三点,能封装任何你需要的组件!

📌 第五篇:Vue3+Vite 优化实战(打包提速+页面优化)

Vue3+Vite 本身就比Vue2+Webpack快,但项目变大后仍会出现打包慢、页面卡顿,这篇汇总5个高频优化技巧,新手也能轻松操作,直接提升项目性能!

一、Vite 基础优化(打包提速)

1. 优化依赖预构建

Vite会预构建依赖,减少打包时间,修改vite.config.js配置,指定预构建的依赖:


// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  // 优化依赖预构建
  optimizeDeps: {
    // 强制预构建的依赖(高频使用的第三方库)
    include: ['vue', 'vue-router', 'pinia', 'axios'],
    // 排除不需要预构建的依赖
    exclude: ['lodash-es']
  }
})

2. 打包压缩优化(减小体积)

安装压缩插件,打包时自动压缩JS/CSS/HTML,减小文件体积,提升加载速度:


# 安装压缩插件
npm install vite-plugin-compression --save-dev

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import compression from 'vite-plugin-compression'

export default defineConfig({
  plugins: [
    vue(),
    // 打包压缩(gzip格式)
    compression({
      algorithm: 'gzip', // 压缩算法
      threshold: 10240, // 超过10KB的文件才压缩
      deleteOriginFile: false // 不删除原文件
    })
  ]
})

二、代码层面优化(页面提速)

1. 组件懒加载(路由懒加载)

之前路由配置中提到的懒加载,是最核心的优化技巧,减少首页加载时间:


// router/index.js
// ❌ 错误:全部引入,首页加载慢
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'

// ✅ 正确:懒加载,只有访问时才加载组件
const Home = () => import('@/views/Home.vue')
const About = () => import('@/views/About.vue')

// 进阶:分块打包(相同模块打包到一个文件)
const User = () => import(/* webpackChunkName: "user" */ '@/views/User.vue')
const UserInfo = () => import(/* webpackChunkName: "user" */ '@/views/UserInfo.vue')

2. 图片优化(减小图片体积)

Vite自带图片处理,配合插件优化图片,支持webp格式(体积更小):


# 安装图片优化插件
npm install vite-plugin-imagemin --save-dev

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import imagemin from 'vite-plugin-imagemin'

export default defineConfig({
  plugins: [
    vue(),
    // 图片优化
    imagemin({
      gifsicle: {
        optimizationLevel: 7, // gif优化等级
        interlaced: false
      },
      optipng: {
        optimizationLevel: 7 // png优化等级
      },
      mozjpeg: {
        quality: 80 // jpg质量(0-100)
      },
      pngquant: {
        quality: [0.8, 0.9], // png质量范围
        speed: 4
      },
      svgo: {
        plugins: [
          { name: 'removeViewBox' },
          { name: 'removeEmptyAttrs', active: false }
        ]
      }
    })
  ],
  // 配置图片路径,优化加载
  assetsInclude: ['**/*.png', '**/*.jpg', '**/*.webp'],
  build: {
    assetsInlineLimit: 4096 // 小于4KB的图片转base64,减少请求
  }
})

3. 减少不必要的响应式数据

Vue3的响应式会消耗性能,非响应式数据不用ref/reactive包裹:


<script setup>
import { ref, reactive } from 'vue'

// ✅ 正确:非响应式数据,直接定义(不会变化的数据)
const staticData = {
  name: '手绘君',
  age: 20
}

// ✅ 正确:响应式数据,用ref/reactive(会变化的数据)
const count = ref(0)
const user = reactive({
  name: '测试君'
})
</script>

4. 虚拟列表(长列表优化)

当列表数据超过1000条时,会出现卡顿,用虚拟列表只渲染可视区域的内容:


# 安装虚拟列表插件(vue3专用)
npm install vue-virtual-scroller@next --save

<template>
  <!-- 虚拟列表组件 -->
  <RecycleScroller
    class="scroller"
    :items="longList" // 长列表数据
    :item-size="50" // 每个列表项的高度
    key-field="id" // 唯一标识字段
  >
    <template #default="{ item }">
      <div class="list-item">{{ item.name }}</div>
    </template>
  </RecycleScroller>
</template>

<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

// 模拟10000条长列表数据
const longList = Array.from({ length: 10000 }, (_, i) => ({
  id: i + 1,
  name: `列表项 ${i + 1}`
}))
</script>

<style scoped>
.scroller {
  height: 500px; // 必须设置固定高度
  width: 100%;
}
.list-item {
  height: 50px; // 和item-size一致
  line-height: 50px;
  border-bottom: 1px solid #eee;
}
</style>

三、优化总结

  • 🚀 打包优化:依赖预构建+压缩+图片优化,减小体积、提升打包速度

  • 🚀 页面优化:路由懒加载+虚拟列表,减少首页加载时间、解决长列表卡顿

  • 🚀 代码优化:减少不必要的响应式数据,降低性能消耗

💡 小技巧:优化后可以用 npm run build 查看打包体积,用浏览器开发者工具(Network)查看加载速度!

✏️ 合集持续更新ing,每篇都是爆款实用向,有错欢迎指出来~ 收藏起来,学Vue3不迷路!

(注:文档部分内容可能由 AI 生成)

❌
❌