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>
<div id="app">
<!-- 路由导航(类似a标签) -->
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于我们</router-link>
<router-link :to="{ name: 'User', params: { id: 123 } }">用户中心</router-link>
</nav>
<!-- 路由出口:匹配的组件会渲染到这里 -->
<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"
>
<!-- 插槽:自定义按钮内容 -->
<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>
<div class="custom-input">
<!-- 标签插槽 -->
<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">
<div class="modal-content" @click.stop>
<!-- 弹窗标题 -->
<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>
<!-- 弹窗底部(插槽,自定义按钮) -->
<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 生成)