阅读视图

发现新文章,点击刷新页面。

退出登录后头像还在?这个缓存问题坑过多少前端!

大家好,我是小杨,一个干了6年的前端老司机。今天要聊一个看似简单却经常被忽略的问题——为什么用户退出登录后,头像还显示在页面上?

这个问题我遇到过不止一次,甚至有一次差点被测试同学当成严重BUG提上来。其实背后的原因很简单,但解决起来有几个关键点需要注意。


1. 为什么退出登录后头像还在?

通常,头像不会自动消失,主要有以下几个原因:

① 缓存没清理干净

  • 浏览器缓存:图片可能被浏览器缓存了,即使退出登录,浏览器仍然显示旧的头像。
  • 前端状态没重置:Vue/React 的全局状态(如 Vuex、Redux)可能还保留着用户信息。

② 头像URL没更新

很多网站的头像是通过URL加载的,比如:

<img src="https://example.com/avatars/我的头像.jpg" />

如果退出登录后,前端没强制刷新页面或更新URL,浏览器可能仍然显示缓存中的旧图片。

③ 后端会话失效,但静态资源可访问

即使退出登录,头像图片如果放在公开可访问的路径下(如 /public/avatars/),浏览器仍然能加载到。


2. 怎么解决?5种常见方案

✅ 方案1:强制刷新页面(简单粗暴)

退出登录后,直接 window.location.reload(),让浏览器重新加载所有资源。

logout() {
  clearUserToken(); // 清除Token
  window.location.reload(); // 强制刷新
}

缺点:体验不好,页面会闪烁。

✅ 方案2:给头像URL加时间戳(推荐)

在头像URL后面加一个随机参数,让浏览器认为是新图片:

<img :src="`/avatars/${user.avatar}?t=${Date.now()}`" />

或者用 Vue 的 v-if 控制显示:

<img v-if="isLoggedIn" :src="user.avatar" />

✅ 方案3:清除前端缓存状态

如果用了 Vuex/Pinia,退出时一定要清空用户数据:

// store/user.js
actions: {
  logout() {
    this.user = null;
    localStorage.removeItem('token');
  }
}

✅ 方案4:后端返回默认头像(保险做法)

如果用户未登录,后端可以返回一个默认头像URL,而不是让前端处理缓存问题。

✅ 方案5:Service Worker 缓存控制(高级玩法)

如果你用了 PWA,可以通过 Service Worker 动态控制缓存策略:

// service-worker.js
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('avatar')) {
    event.respondWith(
      fetch(event.request, { cache: 'no-store' }) // 不缓存头像
    );
  }
});

3. 我踩过的坑:本地开发没问题,上线出BUG

有一次,我在本地测试退出登录功能,头像正常消失。但上线后,用户反馈退出后头像还在!

原因

  • 本地开发时,浏览器没缓存图片。
  • 生产环境用了 CDN,图片被缓存了,导致退出后仍然显示旧头像。

解决方案
在头像URL后面加版本号,比如:

<img :src="`/avatars/${user.avatar}?v=${user.avatarVersion}`" />

每次用户更新头像,后端都更新 avatarVersion,这样浏览器就会重新加载。


4. 终极解决方案:综合策略

最佳实践是 前端 + 后端 一起处理:

  1. 前端:退出时清空状态,加随机参数避免缓存。
  2. 后端:返回正确的 HTTP 缓存头(如 Cache-Control: no-store)。

5. 总结

  • 问题根源:浏览器缓存 + 前端状态没清理干净。

  • 解决方案

    • 加随机参数(?t=时间戳
    • 清空 Vuex/Redux 状态
    • 后端控制缓存策略
  • 高级方案:Service Worker 动态管理缓存

如果你也遇到过这个问题,欢迎在评论区分享你的解决方案! 🚀

⭐  写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

Vue的'读心术':它怎么知道数据偷偷变了?

大家好,我是小杨。做了6年前端,经常被新手问:"Vue怎么知道我修改了数据?"今天就来揭秘这个"读心术"!

1. 先看个神奇现象

data() {
  return {
    message: '你好'
  }
}

当我在代码中修改this.message = '新消息'时,视图自动更新了!这背后发生了什么?

2. 核心原理:数据劫持

Vue其实是个"老六",它偷偷做了三件事:

  1. 监听对象属性(Object.defineProperty)
  2. 建立依赖收集(Dep)
  3. 通知视图更新(Watcher)

3. 手写一个极简版

我们来模拟Vue的实现:

class 简易Vue {
  constructor(options) {
    this._data = options.data
    this.劫持数据(this._data)
  }
  
  劫持数据(obj) {
    Object.keys(obj).forEach(key => {
      let value = obj[key]
      Object.defineProperty(obj, key, {
        get() {
          console.log(`${key}被读取了`)
          return value
        },
        set(newVal) {
          console.log(`${key}${value}变成了${newVal}`)
          value = newVal
          // 这里应该通知视图更新
        }
      })
    })
  }
}

// 使用
const app = new 简易Vue({
  data: { message: '我是初始值' }
})
app._data.message = '我是新值' // 控制台会打印变化!

4. 我遇到的真实案例

曾经有个bug让我排查到凌晨3点:

data() {
  return {
    user: { name: '小杨' }
  }
}

// 错误写法!
this.user.age = 25 // 视图不会更新!

原因:Vue无法检测新增的属性!必须用this.$set(this.user, 'age', 25)

5. 数组的特殊处理

Vue对数组方法做了hack:

// 这些能触发更新
this.items.push('新项目')
this.items.splice(0, 1)

// 这些不行!
this.items[0] = '修改项' // 要用Vue.set
this.items.length = 0 // 不会触发

6. Vue 3的升级版:Proxy

Vue 3改用Proxy实现,解决了Vue 2的限制:

const data = new Proxy({ message: '你好' }, {
  set(target, key, value) {
    console.log(`检测到${key}变化`)
    target[key] = value
    return true
  }
})

data.message = '再见' // 自动触发set

7. 性能优化小技巧

  1. 冻结不需要响应的数据Object.freeze
  2. 扁平化数据结构:嵌套太深影响性能
  3. 避免在模板中使用复杂表达式

8. 调试技巧

想知道谁修改了数据?在组件中添加:

watch: {
  message(newVal, oldVal) {
    console.log(`[小杨的调试] message从${oldVal}变成了${newVal}`)
  }
}

最后说句掏心窝的

理解响应式原理后,再看Vue就像开了透视挂。下次遇到"视图不更新"的问题,你就能快速定位了!

⭐  写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

手把手教你造一个自己的v-model:原来双向绑定这么简单!

大家好,我是小杨,一个写了6年前端的老码农。今天想带大家揭开Vue里v-model的神秘面纱,我们自己动手实现一个简易版!

记得刚学Vue时,我觉得v-model简直是黑魔法——输入框的值怎么就自动同步到数据了呢?直到有一天我看了源码,才发现...

1. v-model的本质是什么?

一句话:语法糖!
它其实就是value属性 + @input事件的快捷写法。比如:

<input v-model="message">

等价于:

<input 
  :value="message"
  @input="message = $event.target.value"
>

2. 自己实现一个简易v-model

让我们造个轮子叫my-model

<template>
  <input 
    :value="value"
    @input="$emit('input', $event.target.value)"
  >
</template>

<script>
export default {
  props: ['value']
}
</script>

使用时:

<my-model v-model="message"></my-model>

效果:  和官方v-model一模一样!不信你试试。

3. 我踩过的坑

有次我自作聪明加了额外功能:

@input="handleInput($event.target.value)"

然后在methods里:

handleInput(val) {
  this.$emit('input', val + '后缀') // 自动加后缀
}

结果用户每输入一个字符就追加后缀,直接炸了😂。所以直接emit原始值最安全!

4. 进阶玩法:自定义组件的v-model

Vue 2.x默认使用value属性和input事件,但我们可以改!

model: {
  prop: '我喜欢的名字',  // 改用其他属性名
  event: 'change'      // 改用其他事件名
}

这样就能:

<custom-input 
  v-model="message"
  我喜欢的名字="初始值"
  @change="处理函数"
></custom-input>

5. Vue 3的小变化

Vue 3中更灵活了:

  • 默认属性名改为modelValue
  • 默认事件名改为update:modelValue
  • 支持多个v-model绑定
<MyComponent v-model:title="title" v-model:content="content" />

6. 活学活用案例

我做过一个颜色选择器组件:

<color-picker v-model="themeColor" />

内部实现:

// 当用户选颜色时
this.$emit('input', newColor)

这样父组件完全不用写监听逻辑,干净又卫生!

7. 为什么理解这个很重要?

  1. 面试常考题(我当面试官必问)
  2. 自定义表单组件必备技能
  3. 避免滥用v-model(有些场景应该用.sync)

最后送大家一句话:

"理解v-model,就是理解Vue双向绑定的第一课" —— 这是当年我的导师说的,现在送给你们。

⭐  写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

v-for中key值的作用:为什么我总被要求加这个'没用的'属性?

大家好,我是小杨,一个干了6年的前端老油条。今天想和大家聊聊Vue中一个看似简单却经常被问起的问题——v-for里的key值到底有什么用。

记得我刚学Vue那会儿,每次用v-for都会收到ESLint的红色警告:"Elements in iteration expect to have 'v-bind:key' directives"。当时的我总在想:"不加不也能用吗?这玩意儿到底有啥用?"

1. key值是什么?

简单说,key就是给每个循环项一个"身份证号"。比如我们渲染一个列表:

<ul>
  <li v-for="item in items" :key="item.id">
    {{ 我 }}喜欢{{ item.name }}
  </li>
</ul>

2. 为什么需要key?

Vue需要key来高效地更新DOM。没有key时,当列表顺序变化,Vue会怎么做?它会直接就地更新元素,而不是移动它们。

举个我踩过的坑:

// 初始数据
items: [
  { id: 1, name: '苹果' },
  { id: 2, name: '香蕉' }
]

// 后来数据变成了
items: [
  { id: 2, name: '香蕉' },
  { id: 1, name: '苹果' }
]

没有key时,Vue不会交换这两个li的位置,而是直接更新内容。这会导致:

  1. 性能浪费(不必要的DOM更新)
  2. 可能的状态问题(比如输入框内容错乱)

3. key的正确打开方式

✅ 正确做法:

<li v-for="item in items" :key="item.id">

❌ 错误做法:

<li v-for="item in items" :key="index">

(用index当key和没加差不多,特别是列表会变化时)

4. 我总结的key使用原则

  1. 唯一性:key应该在当前列表中唯一
  2. 稳定性:key不应该随时间改变(别用随机数!)
  3. 可预测性:相同内容应该生成相同key

5. 实际工作中的经验

有次我做了一个复杂的列表组件,每个项都有内部状态。最初偷懒用了index当key,结果用户排序时各种bug。后来老老实实改用item.id,问题迎刃而解。

6. 什么时候可以不加key?

理论上说,纯静态列表(不会排序、过滤、修改)可以不加。但我的建议是:永远加上key!这就像系安全带,平时觉得麻烦,关键时刻能救命。

最后

key值看似是个小细节,却体现了Vue的响应式原理。理解它不仅能避免bug,还能写出更高性能的代码。希望我的经验对你有帮助!

小贴士:如果你也在纠结key的问题,记住这句话——"给Vue一个靠谱的身份证,它还你一个稳定的列表渲染"。

⭐  写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

Vue计算属性:为什么我的代码突然变优雅了?

大家好,我是小杨,一个写了6年前端的老油条。今天想和大家聊聊Vue中一个看似简单但超级实用的功能——计算属性。记得我刚接触Vue时,总觉得data和methods已经够用了,直到发现了计算属性这个"神器",我的代码才真正开始变得优雅起来。

一、计算属性是什么?

简单来说,计算属性就是基于现有数据计算出来的新数据。它就像一个智能的中间人,帮你处理data中的数据,给你想要的结果。

举个🌰,假设我要显示用户的全名:

data() {
  return {
    firstName: '小',
    lastName: '杨'
  }
}

没有计算属性时,我可能会这样写:

<p>{{ firstName + ' ' + lastName }}</p>

或者用方法:

methods: {
  fullName() {
    return this.firstName + ' ' + this.lastName
  }
}

但有了计算属性,事情就变得简单多了:

computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName
  }
}

看起来和方法差不多?别急,它的妙处还在后面呢!

二、计算属性的三大超能力

1. 自动缓存 - 懒人的福音

计算属性最厉害的特点就是缓存。只要依赖的数据不改变,多次访问计算属性会立即返回之前缓存的结果,而不会重新计算。

比如我有一个复杂的计算:

computed: {
  complicatedCalculation() {
    console.log('重新计算了!')
    return this.someData * 100 / Math.PI + 1000
  }
}

即使我在模板里用十次:

<p>{{ complicatedCalculation }}</p>
<p>{{ complicatedCalculation }}</p>
<p>{{ complicatedCalculation }}</p>

控制台只会输出一次"重新计算了!"。如果是方法,每次都会重新执行。

2. 响应式依赖追踪 - 智能的管家

计算属性会自动追踪它依赖的响应式数据。只有当依赖变化时,它才会重新计算。

computed: {
  userInfo() {
    return {
      name: this.user.name,
      age: this.user.age,
      // 只要user.address没被用到,address变化不会触发重新计算
    }
  }
}

3. 可读可写 - 灵活的双向门

计算属性默认只有getter,但也可以提供setter:

computed: {
  fullName: {
    get() {
      return this.firstName + ' ' + this.lastName
    },
    set(newValue) {
      const names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[1] || ''
    }
  }
}

现在你可以这样用:

// 读取
console.log(this.fullName)

// 设置
this.fullName = '大 杨'

三、什么时候该用计算属性?

根据我的经验,以下场景特别适合使用计算属性:

1. 复杂的数据转换

比如从后端拿到一组数据,需要加工后再显示:

computed: {
  filteredProducts() {
    return this.products.filter(p => p.price > 100)
                      .sort((a,b) => b.price - a.price)
                      .slice(0, 5)
  }
}

2. 表单验证

computed: {
  emailError() {
    if (!this.email) return '邮箱不能为空'
    if (!/.+@.+..+/.test(this.email)) return '邮箱格式不正确'
    return ''
  },
  passwordError() {
    if (!this.password) return '密码不能为空'
    if (this.password.length < 6) return '密码太短'
    return ''
  },
  isValid() {
    return !this.emailError && !this.passwordError
  }
}

3. 组件props的派生状态

props: ['size'],
computed: {
  normalizedSize() {
    return this.size.trim().toLowerCase()
  }
}

四、计算属性 vs 方法 vs 侦听器

很多新手会困惑:什么时候用计算属性,什么时候用方法,什么时候用watch?

计算属性 vs 方法

  • 计算属性:适合需要缓存的结果,基于响应式依赖
  • 方法:适合不需要缓存,或者需要参数的情况
// 计算属性 - 无参数,自动缓存
computed: {
  currentDate() {
    return new Date().toLocaleDateString()
  }
}

// 方法 - 可以有参数,每次调用都执行
methods: {
  formatDate(date) {
    return new Date(date).toLocaleDateString()
  }
}

计算属性 vs 侦听器

  • 计算属性:声明式的,你告诉Vue你想要什么
  • 侦听器:命令式的,你告诉Vue当某些数据变化时要做什么
// 计算属性 - 更简洁
computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName
  }
}

// 侦听器 - 更灵活
watch: {
  firstName(newVal) {
    this.fullName = newVal + ' ' + this.lastName
  },
  lastName(newVal) {
    this.fullName = this.firstName + ' ' + newVal
  }
}

五、计算属性的高级用法

1. 结合v-model使用

computed: {
  searchQuery: {
    get() {
      return this.$store.state.searchQuery
    },
    set(value) {
      this.$store.commit('updateSearchQuery', value)
    }
  }
}

然后在模板中:

<input v-model="searchQuery">

2. 动态计算属性

有时候你可能需要动态创建计算属性:

computed: {
  dynamicComputed() {
    return () => {
      // 根据某些条件返回不同的计算逻辑
      if (this.mode === 'simple') {
        return this.data.length
      } else {
        return this.data.reduce((sum, item) => sum + item.value, 0)
      }
    }
  }
}

3. 组合式API中的计算属性

在Vue3的组合式API中,计算属性用起来也很简单:

import { computed } from 'vue'

setup() {
  const count = ref(0)
  
  const doubleCount = computed(() => count.value * 2)
  
  return {
    count,
    doubleCount
  }
}

六、性能优化小技巧

  1. 避免在计算属性中做复杂操作:计算属性会在依赖变化时重新计算,复杂的操作会影响性能
  2. 不要修改依赖数据:计算属性应该是纯函数,不要在里面修改依赖的数据
  3. 合理拆分计算属性:一个计算属性只做一件事,可以提高可读性和维护性
  4. 避免长依赖链:计算属性依赖其他计算属性时,链条太长会影响性能

七、常见坑点

  1. 异步操作:计算属性不能包含异步操作,这时候应该用方法或者watch
  2. 副作用:计算属性不应该有副作用(如修改DOM、发起请求等)
  3. 依赖未声明:如果计算属性依赖的数据没有在data中声明,Vue无法追踪变化
// 错误示范
computed: {
  badComputed() {
    return window.innerWidth // window不是响应式的!
  }
}

八、我的实战经验

在做一个电商项目时,我遇到过这样一个需求:需要根据用户选择的筛选条件动态显示商品列表。最初我用watch来实现,代码变得又长又难维护。后来改用计算属性,代码量减少了60%!

重构前:

data() {
  return {
    products: [],
    filteredProducts: [],
    category: '',
    priceRange: [0, 1000],
    sortBy: 'price'
  }
},
watch: {
  category() {
    this.filterProducts()
  },
  priceRange() {
    this.filterProducts()
  },
  sortBy() {
    this.filterProducts()
  }
},
methods: {
  filterProducts() {
    // 一大段过滤和排序逻辑
  }
}

重构后:

computed: {
  filteredProducts() {
    let result = this.products
    
    // 按类别过滤
    if (this.category) {
      result = result.filter(p => p.category === this.category)
    }
    
    // 按价格范围过滤
    result = result.filter(p => 
      p.price >= this.priceRange[0] && 
      p.price <= this.priceRange[1]
    )
    
    // 排序
    if (this.sortBy === 'price') {
      result = [...result].sort((a, b) => a.price - b.price)
    } else if (this.sortBy === 'sales') {
      result = [...result].sort((a, b) => b.sales - a.sales)
    }
    
    return result
  }
}

代码不仅更简洁,而且性能也更好,因为计算属性会自动缓存结果,只有依赖变化时才会重新计算。

九、总结

计算属性是Vue中一个非常强大的特性,它能够:

  1. 让你的代码更简洁、更易读
  2. 自动缓存计算结果,提高性能
  3. 智能追踪依赖,只在需要时重新计算
  4. 可以读写结合,处理复杂逻辑

记住:当你需要基于现有数据派生新数据时,首先考虑计算属性。它能让你的Vue代码从"能用"升级到"优雅"的水平。

我是小杨,一个喜欢分享的前端开发者。如果这篇文章对你有帮助,别忘了点赞收藏。如果有任何问题,欢迎在评论区留言讨论!

⭐  写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

Vue路由钩子全攻略:让你的页面跳转更丝滑!

大家好,我是小杨,一个写了6年前端的老司机。今天咱们聊聊Vue路由里的钩子函数,这些钩子就像是路由的"关卡守卫",能在页面跳转前后做各种骚操作,比如权限拦截、数据预加载、页面过渡动画等等。

1. 路由钩子是什么?能干啥?

简单来说,路由钩子就是Vue Router在路由切换时提供的回调函数,可以在不同阶段拦截或处理路由跳转。比如:

  • 登录拦截:没登录?滚回登录页!
  • 数据预加载:进页面之前先把数据请求好
  • 页面权限控制:你没权限?抱歉,此路不通
  • 滚动行为控制:跳转后自动回到顶部

2. 路由钩子分类

Vue Router的钩子主要分三类:

  1. 全局钩子(整个路由生效)
  2. 路由独享钩子(只对某个路由生效)
  3. 组件内钩子(在组件内部使用)

(1)全局钩子:整个路由的守门人

① beforeEach(路由跳转前)

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !我.isLoggedIn) {
    next('/login') // 没登录?去登录页!
  } else {
    next() // 放行
  }
})

适用场景:全局权限控制、登录拦截

② afterEach(路由跳转后)

router.afterEach((to, from) => {
  sendAnalytics(to.path) // 统计页面访问
})

适用场景:埋点统计、页面访问日志

(2)路由独享钩子:单个路由的专属逻辑

const routes = [
  {
    path: '/dashboard',
    component: Dashboard,
    beforeEnter: (to, from, next) => {
      if (!我.hasPermission('admin')) {
        next('/403') // 没权限?去403页面!
      } else {
        next()
      }
    }
  }
]

适用场景:特定路由的权限控制

(3)组件内钩子:组件自己的路由逻辑

在组件里可以直接用这些钩子:

  • beforeRouteEnter(进入组件前)
  • beforeRouteUpdate(路由参数变化但组件复用时)
  • beforeRouteLeave(离开组件前)

① beforeRouteEnter(进入组件前)

export default {
  beforeRouteEnter(to, from, next) {
    // 这里还不能用 `this`,因为组件还没创建!
    next(vm => {
      console.log(vm.myData) // 现在可以访问组件实例了
    })
  }
}

适用场景:进入页面前的数据预加载

② beforeRouteUpdate(路由参数变化时)

export default {
  beforeRouteUpdate(to, from, next) {
    if (to.params.id !== from.params.id) {
      this.fetchData(to.params.id) // 重新获取数据
    }
    next()
  }
}

适用场景:动态路由参数变化时刷新数据

③ beforeRouteLeave(离开页面时)

export default {
  beforeRouteLeave(to, from, next) {
    if (this.unsavedChanges) {
      if (confirm('有未保存的内容,确定离开吗?')) {
        next()
      } else {
        next(false) // 取消导航
      }
    } else {
      next()
    }
  }
}

适用场景:防止用户误操作离开页面(比如填了一半的表单)

3. 实际开发中的经典场景

场景1:登录拦截

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !localStorage.getItem('token')) {
    next('/login?redirect=' + to.fullPath) // 记录跳转目标,登录后自动回来
  } else {
    next()
  }
})

场景2:动态修改页面标题

router.afterEach((to) => {
  document.title = to.meta.title || '默认标题'
})

场景3:滚动行为控制

const router = new VueRouter({
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition // 返回之前的位置(比如浏览器后退时)
    } else {
      return { x: 0, y: 0 } // 默认滚动到顶部
    }
  }
})

4. 总结

  • 全局钩子beforeEachafterEach(适合全局逻辑)
  • 路由独享钩子beforeEnter(适合单个路由的逻辑)
  • 组件内钩子beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave(适合组件内部逻辑)

合理使用这些钩子,能让你的路由跳转更智能、更安全!

⭐  写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

Vuex数据突然消失?六招教你轻松找回来!

大家好,我是小杨,一个经历过无数次Vuex数据"失踪案"的前端老司机。今天要和大家分享的是我在项目中遇到的Vuex数据丢失问题以及解决方案。记得有一次项目上线后,用户反馈登录状态总是莫名其妙丢失,我排查了整整两天才发现问题所在...

一、页面刷新导致数据丢失

问题现象:刷新页面后,Vuex中的数据全部归零。

原因分析:Vuex的状态是存储在内存中的,刷新页面相当于重置了JavaScript运行环境。

解决方案

1. 使用vuex-persistedstate插件

// store/index.js
import createPersistedState from 'vuex-persistedstate'

export default new Vuex.Store({
  // ...
  plugins: [createPersistedState()]
})

这个插件会自动将state保存到localStorage,刷新后恢复数据。

2. 手动持久化关键数据

// 在mutation中保存数据
mutations: {
  SET_USER(state, user) {
    state.user = user
    localStorage.setItem('user', JSON.stringify(user))
  }
}

// 在store初始化时恢复数据
const user = JSON.parse(localStorage.getItem('user'))
const store = new Vuex.Store({
  state: {
    user: user || null
    // ...
  }
})

二、路由跳转导致数据丢失

问题现象:某些页面跳转后,部分Vuex数据不见了。

原因分析:可能是路由配置问题或组件生命周期导致的。

解决方案

1. 检查路由配置

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/user/:id',
      component: User,
      props: true,  // 保持组件实例复用
    }
  ]
})

2. 使用keep-alive缓存组件

<template>
  <keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
  </keep-alive>
  <router-view v-if="!$route.meta.keepAlive"></router-view>
</template>

三、异步操作导致的数据不一致

问题现象:数据看似丢失,实际上是异步操作未完成导致的显示问题。

解决方案

1. 添加loading状态

state: {
  user: null,
  loading: false
},
actions: {
  async fetchUser({ commit }) {
    commit('SET_LOADING', true)
    try {
      const user = await api.getUser()
      commit('SET_USER', user)
    } finally {
      commit('SET_LOADING', false)
    }
  }
}

2. 使用v-if控制渲染时机

<template>
  <div v-if="!loading">
    <!-- 用户数据展示 -->
  </div>
  <div v-else>
    加载中...
  </div>
</template>

四、模块化导致的命名冲突

问题现象:模块中的数据似乎被重置或覆盖。

解决方案

1. 检查模块命名空间

const userModule = {
  namespaced: true,  // 关键!
  state: { /* ... */ },
  mutations: { /* ... */ }
}

2. 正确使用map辅助函数

computed: {
  ...mapState('user', ['info']),  // 指定命名空间
},
methods: {
  ...mapActions('user', ['fetchUser'])
}

五、开发环境热重载导致的问题

问题现象:开发时保存代码后,部分状态丢失。

解决方案

1. 配置webpack热重载

// store/index.js
export default new Vuex.Store({
  // ...
  strict: process.env.NODE_ENV !== 'production'
})

if (module.hot) {
  module.hot.accept(['./modules'], () => {
    store.hotUpdate({
      modules: {
        ...require('./modules').default
      }
    })
  })
}

六、浏览器隐私模式导致的问题

问题现象:在隐私模式下,数据无法持久化。

解决方案

1. 添加错误处理

function safeLocalStorageSet(key, value) {
  try {
    localStorage.setItem(key, JSON.stringify(value))
    return true
  } catch (e) {
    console.warn('本地存储失败:', e)
    return false
  }
}

2. 降级方案

// 使用sessionStorage作为fallback
function persistData(key, value) {
  if (!safeLocalStorageSet(key, value)) {
    sessionStorage.setItem(key, JSON.stringify(value))
  }
}

七、终极解决方案组合拳

在我的电商项目中,我是这样综合应用的:

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'

Vue.use(Vuex)

const store = new Vuex.Store({
  plugins: [
    createPersistedState({
      paths: ['user', 'cart'],  // 只持久化关键数据
      storage: {
        getItem: key => {
          // 我是小杨,这里添加安全读取逻辑
          try {
            return localStorage.getItem(key)
          } catch (e) {
            return sessionStorage.getItem(key)
          }
        },
        setItem: (key, value) => {
          try {
            localStorage.setItem(key, value)
          } catch (e) {
            sessionStorage.setItem(key, value)
          }
        },
        removeItem: key => {
          localStorage.removeItem(key)
          sessionStorage.removeItem(key)
        }
      }
    })
  ],
  modules: {
    // 模块化组织
  }
})

export default store

八、写在最后

Vuex数据丢失问题看似简单,但实际上可能有很多隐藏的原因。通过本文介绍的六种解决方案,你应该能够应对大多数场景:

  1. 页面刷新:使用持久化插件
  2. 路由跳转:检查路由配置和组件缓存
  3. 异步操作:添加loading状态
  4. 模块冲突:正确使用命名空间
  5. 热重载问题:配置webpack热更新
  6. 隐私模式:添加错误处理和降级方案

⭐  写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

Vue状态管理进阶:数据到底是怎么"跑"的?

大家好,我是小杨,一个摸爬滚打了6年的前端老司机。今天想和大家聊聊Vue项目中状态管理的数据流向问题。记得我第一次用Vuex时,被各种action、mutation搞得晕头转向,数据就像迷宫里的老鼠,完全不知道它从哪来,要往哪去。今天,我就来给大家画张清晰的"数据地图"!

一、为什么需要状态管理?

先讲个真实案例。去年我做了一个电商后台项目,开始没用状态管理,各个组件各自为政:

// 商品列表组件
data() {
  return {
    products: []
  }
},
created() {
  fetchProducts().then(res => {
    this.products = res.data
  })
}

// 购物车组件
data() {
  return {
    products: []
  }
},
created() {
  fetchProducts().then(res => {
    this.products = res.data.filter(p => p.inCart)
  })
}

发现问题了吗?同样的数据,重复请求,状态不同步,维护起来简直要命!这就是我们需要状态管理的原因——让数据有且只有一份真相来源

二、Vuex数据流向全景图

先来看张我手绘的核心流程图(文字版):

组件 → (dispatch) → Action → (commit) → Mutation → (mutate) → State → (render) → 组件

简单来说,数据流动是单向闭环的,就像单行道,不会出现"逆行"的情况。

三、分步拆解数据旅程

1. 组件触发Action

当组件中需要修改状态时,不能直接改,而是要通过dispatch触发一个action:

// 在组件中
this.$store.dispatch('addToCart', productId)

// 或者使用mapActions
methods: {
  ...mapActions(['addToCart'])
}

2. Action处理异步操作

Action像是业务逻辑的"调度中心",可以处理异步操作:

actions: {
  async addToCart({ commit }, productId) {
    try {
      // 我是小杨,这里模拟API调用
      const res = await api.addToCart(productId)
      commit('ADD_TO_CART', res.data)
    } catch (error) {
      commit('SET_ERROR', error.message)
    }
  }
}

3. Mutation修改状态

只有mutation能直接修改state,而且必须是同步的:

mutations: {
  ADD_TO_CART(state, product) {
    state.cart.push(product)
    state.totalPrice += product.price
  }
}

4. State变化触发更新

State变化后,所有依赖该状态的组件都会自动更新:

// 组件中获取状态
computed: {
  cart() {
    return this.$store.state.cart
  },
  // 或者使用mapState
  ...mapState(['cart', 'totalPrice'])
}

四、Pinia的数据流向(Vue3推荐)

Pinia是新一代Vue状态管理工具,更简单直观:

组件 → (直接调用) → Action → (直接修改) → State → 组件

对比Vuex:

  1. 没有mutation了,action可以直接修改state
  2. 更灵活的TypeScript支持
  3. 模块化开箱即用
// store/cart.js
export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),
  actions: {
    async addToCart(productId) {
      const product = await api.getProduct(productId)
      this.items.push(product)
    }
  }
})

// 组件中使用
import { useCartStore } from '@/store/cart'
const cartStore = useCartStore()
cartStore.addToCart(123)

五、数据流动的黄金法则

根据我的踩坑经验,总结几条铁律:

  1. 组件不要直接修改state - 就像交通规则,破坏单向数据流迟早出bug
  2. 异步操作放action - mutation只做最简单的状态变更
  3. 保持state最小化 - 只存储必要数据,派生数据用getter
  4. 模块化设计 - 像整理衣柜一样组织你的store

六、实战:购物车数据流案例

让我们用Vuex实现一个完整的购物车流程:

// store/index.js
state: {
  cart: [],
  inventory: {}
},
getters: {
  cartTotal: state => {
    return state.cart.reduce((total, item) => total + item.price * item.quantity, 0)
  }
},
actions: {
  async checkout({ commit, state }) {
    // 我是小杨,这里处理结账逻辑
    const res = await api.checkout(state.cart)
    commit('CLEAR_CART')
    return res
  }
},
mutations: {
  ADD_ITEM(state, product) {
    const item = state.cart.find(i => i.id === product.id)
    item ? item.quantity++ : state.cart.push({ ...product, quantity: 1 })
  },
  CLEAR_CART(state) {
    state.cart = []
  }
}

组件中的使用:

// ProductItem.vue
methods: {
  addToCart() {
    this.$store.dispatch('addToCart', this.product)
  }
}

// ShoppingCart.vue
computed: {
  ...mapState(['cart']),
  ...mapGetters(['cartTotal'])
},
methods: {
  checkout() {
    this.$store.dispatch('checkout').then(() => {
      this.$router.push('/thank-you')
    })
  }
}

七、常见问题排查

1. 状态变了视图不更新?

  • 可能是直接修改了数组或对象,应该用Vue.set或展开运算符
  • 检查是否违反了单向数据流原则

2. 异步操作结果不一致?

  • 确保mutation是同步的
  • 考虑加loading状态和错误处理

3. 模块间如何共享状态?

  • 对于全局状态放在根store
  • 模块间通信可以通过rootState或派发其他模块的action

八、写在最后

理解数据流向就像掌握了交通规则,能让你的应用运行得更加顺畅。记住:

  1. Vuex是严格的单向数据流
  2. Pinia更灵活但也要遵循规范
  3. 良好的状态设计能大幅降低维护成本

⭐  写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

❌