1. 现代前端开发中的数据流挑战
在Vue.js项目开发中,数据获取与状态管理一直是困扰开发者的核心问题。我接手过不少从jQuery时代迁移过来的项目,发现很多团队在数据流处理上存在典型误区:要么过度依赖组件内部状态导致数据混乱,要么滥用全局状态造成性能瓶颈。
最近一个电商后台项目让我印象深刻:他们用Vuex存储了所有异步获取的数据,包括省份列表这种低频变更的数据,导致每次页面刷新都要重新请求。更糟的是,不同组件对同一数据的重复请求没有做任何缓存处理,这让我意识到很多开发者对Vue中的数据流管理缺乏系统认知。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Ajax请求在Vue中的正确姿势
2.1 选择适合的HTTP客户端
在Vue生态中,axios依然是大多数项目的首选。但很多人不知道的是,axios实例的配置可以极大提升开发效率:
javascript复制// 在src/utils/request.js中创建定制化实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 10000,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
// 请求拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['Authorization'] = `Bearer ${store.getters.token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
return Promise.reject(error)
}
)
这种封装方式可以统一处理鉴权、错误提示等通用逻辑,避免在每个请求中重复编写。
2.2 组件内请求的最佳实践
在组件中使用Ajax请求时,常见的问题是未处理加载状态和错误情况:
javascript复制export default {
data() {
return {
list: [],
loading: false,
error: null
}
},
methods: {
async fetchData() {
this.loading = true
this.error = null
try {
const { data } = await getList(params)
this.list = data
} catch (err) {
this.error = err.message
// 实际项目中这里应该根据错误类型进行差异化处理
} finally {
this.loading = false
}
}
},
created() {
this.fetchData()
}
}
重要提示:永远不要忘记在finally中重置loading状态,否则可能导致界面一直显示加载中
2.3 请求缓存与重复请求取消
对于相同参数的重复请求,应该实现请求缓存和取消机制:
javascript复制// 请求缓存Map
const requestCache = new Map()
function getCacheKey(config) {
return `${config.method}-${config.url}-${JSON.stringify(config.params)}`
}
// 在axios拦截器中添加缓存逻辑
service.interceptors.request.use(config => {
if (config.cache) {
const key = getCacheKey(config)
if (requestCache.has(key)) {
return Promise.resolve(requestCache.get(key))
}
}
return config
})
service.interceptors.response.use(response => {
if (response.config.cache) {
const key = getCacheKey(response.config)
requestCache.set(key, response)
}
return response
})
对于正在进行的相同请求,可以使用axios的CancelToken来取消前一个请求:
javascript复制const pendingRequests = new Map()
function addPendingRequest(config) {
const key = `${config.method}-${config.url}`
if (pendingRequests.has(key)) {
pendingRequests.get(key).cancel()
}
config.cancelToken = new axios.CancelToken(cancel => {
pendingRequests.set(key, { cancel })
})
}
function removePendingRequest(config) {
const key = `${config.method}-${config.url}`
if (pendingRequests.has(key)) {
pendingRequests.delete(key)
}
}
3. Vue状态管理的进阶策略
3.1 何时该使用状态管理
很多开发者存在一个误区:认为所有共享数据都应该放入Vuex。实际上,应该根据数据特性来决定存储位置:
- 组件内部状态:仅在单个组件内使用的数据
- props/事件:父子组件间传递的数据
- provide/inject:跨多层级组件共享的数据
- Vuex/Pinia:真正需要全局管理或需要响应式追踪的数据
我总结了一个简单的决策流程图:
code复制是否需要跨路由共享? → 是 → 使用Vuex/Pinia
↓否
是否涉及复杂的数据变更逻辑? → 是 → 使用Vuex/Pinia
↓否
是否超过3层组件传递? → 是 → 考虑provide/inject
↓否
使用props/事件通信即可
3.2 Vuex的核心模式优化
传统的Vuex使用方式会导致store文件臃肿。我推荐采用模块化+类型化的组织方式:
javascript复制// store/modules/user.js
const state = () => ({
info: null,
permissions: []
})
const mutations = {
SET_INFO(state, payload) {
state.info = payload
}
}
const actions = {
async fetchUserInfo({ commit }) {
const { data } = await getUserInfo()
commit('SET_INFO', data)
return data
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
然后在主store文件中动态导入模块:
javascript复制// store/index.js
const modules = {}
const files = require.context('./modules', false, /\.js$/)
files.keys().forEach(key => {
modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
})
export default new Vuex.Store({
modules
})
3.3 Pinia的现代化实践
Pinia作为Vue官方推荐的状态管理库,提供了更简洁的API。这是一个典型的Pinia store示例:
javascript复制// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
info: null,
permissions: []
}),
actions: {
async fetchUserInfo() {
const { data } = await getUserInfo()
this.info = data
return data
}
},
getters: {
isAdmin: state => state.permissions.includes('admin')
}
})
在组件中使用时更加直观:
javascript复制import { useUserStore } from '@/stores/user'
export default {
setup() {
const userStore = useUserStore()
return {
userInfo: computed(() => userStore.info),
fetchUserInfo: userStore.fetchUserInfo
}
}
}
4. 数据请求与状态管理的联合作战
4.1 请求数据的存储策略
对于从后端获取的数据,应该根据其使用场景决定存储方式:
- 全局配置数据:如省市列表、枚举值等 → 存入Vuex/Pinia并设置长期缓存
- 业务数据:如用户信息、订单列表等 → 根据业务需求决定缓存时间
- 临时数据:如表单选项、弹窗内容等 → 保持在组件内部
一个实用的数据缓存中间件实现:
javascript复制// utils/cacheMiddleware.js
export function createCacheMiddleware(store) {
return async (key, fetcher, options = {}) => {
const { ttl = 300000 } = options // 默认缓存5分钟
if (store.state.cache[key]) {
const { data, timestamp } = store.state.cache[key]
if (Date.now() - timestamp < ttl) {
return data
}
}
const data = await fetcher()
store.commit('cache/SET_DATA', { key, data })
return data
}
}
4.2 数据一致性保障
在大型应用中,确保不同组件中的数据一致性是个挑战。我推荐采用以下模式:
- 单一数据源:相同数据只从一个store获取
- 数据规范化:将嵌套数据转换为扁平结构
- 变更广播:通过事件总线或store订阅通知数据变更
示例:使用Vuex实现数据变更通知
javascript复制// store/modules/products.js
const actions = {
async updateProduct({ commit }, payload) {
const { data } = await api.updateProduct(payload)
commit('UPDATE_PRODUCT', data)
// 广播变更事件
this._vm.$emit('product-updated', data.id)
}
}
在组件中监听变更:
javascript复制export default {
created() {
this.$on('product-updated', this.handleProductUpdate)
},
methods: {
handleProductUpdate(productId) {
if (this.product.id === productId) {
this.refreshProduct()
}
}
}
}
4.3 性能优化技巧
- 请求合并:对于同页面多个组件需要的相同数据,应该合并请求
- 数据分片:大数据集采用分页加载或虚拟滚动
- 内存管理:及时清理不再使用的store数据
一个请求合并的实用方案:
javascript复制// utils/requestBatcher.js
class RequestBatcher {
constructor() {
this.pending = new Map()
}
batch(key, requestFn) {
if (this.pending.has(key)) {
return this.pending.get(key)
}
const promise = requestFn().finally(() => {
this.pending.delete(key)
})
this.pending.set(key, promise)
return promise
}
}
// 在Vue插件中全局注册
const batcher = new RequestBatcher()
Vue.prototype.$batcher = batcher
使用方式:
javascript复制this.$batcher.batch('user-data', () => this.$store.dispatch('user/fetchInfo'))
5. 实战中的疑难问题解析
5.1 身份认证的时效处理
在实现自动刷新token时,常见的问题是并发请求导致多次刷新。解决方案:
javascript复制// utils/refreshTokenLock.js
let isRefreshing = false
let subscribers = []
function subscribeTokenRefresh(cb) {
subscribers.push(cb)
}
function onRrefreshed(token) {
subscribers.forEach(cb => cb(token))
subscribers = []
}
async function refreshToken() {
if (isRefreshing) {
return new Promise(resolve => {
subscribeTokenRefresh(resolve)
})
}
isRefreshing = true
try {
const { data } = await refreshTokenApi()
onRrefreshed(data.token)
return data.token
} finally {
isRefreshing = false
}
}
5.2 复杂表单的状态管理
对于包含多个步骤的复杂表单,推荐采用以下架构:
javascript复制// store/modules/form.js
const state = () => ({
steps: {
basic: { valid: false, data: null },
detail: { valid: false, data: null }
}
})
const actions = {
validateStep({ commit }, { step, data }) {
return validateApi(data).then(valid => {
commit('SET_STEP_VALID', { step, valid, data })
return valid
})
}
}
在组件中使用:
javascript复制export default {
computed: {
currentStep() {
return this.$store.state.form.steps[this.stepName]
}
},
methods: {
async validate() {
const valid = await this.$store.dispatch(
'form/validateStep',
{ step: this.stepName, data: this.formData }
)
if (valid) {
this.$router.push('/next-step')
}
}
}
}
5.3 服务端渲染(SSR)的特殊处理
在SSR项目中,需要特别注意:
- store状态序列化:确保客户端能正确注水(hydrate)
- 请求实例隔离:每个请求应该有独立的axios实例
- 内存泄漏预防:及时清理请求缓存
示例配置:
javascript复制// store/index.js
export function createStore() {
return new Vuex.Store({
// ...配置
})
}
// 在entry-server.js中
export default context => {
return new Promise((resolve, reject) => {
const store = createStore()
// 执行数据预取逻辑
Promise.all([
store.dispatch('fetchUserInfo'),
store.dispatch('fetchGlobalData')
]).then(() => {
context.state = store.state
resolve(app)
}).catch(reject)
})
}
6. 工程化与架构设计
6.1 API层的抽象设计
良好的API层应该具备:
- 领域模型映射:将API响应转换为前端领域模型
- 错误统一处理:标准化错误格式
- TypeScript支持:完整的类型定义
示例架构:
code复制src/
api/
modules/
user.js
product.js
client.js # axios实例配置
adapter.js # 数据转换层
error.js # 错误处理
types/ # TypeScript类型定义
6.2 监控与性能分析
实现请求监控的关键点:
javascript复制// utils/monitor.js
export function trackRequest(config) {
const startTime = Date.now()
return {
end: () => {
const duration = Date.now() - startTime
const metric = {
url: config.url,
method: config.method,
duration,
status: 'success'
}
sendToAnalytics(metric)
},
fail: (error) => {
const metric = {
url: config.url,
method: config.method,
status: 'error',
error: error.message
}
sendToAnalytics(metric)
}
}
}
// 在axios拦截器中使用
service.interceptors.request.use(config => {
config.meta = config.meta || {}
config.meta.requestTracker = trackRequest(config)
return config
})
service.interceptors.response.use(
response => {
response.config.meta.requestTracker.end()
return response
},
error => {
if (error.config) {
error.config.meta.requestTracker.fail(error)
}
return Promise.reject(error)
}
)
6.3 测试策略
针对数据流层的测试应该包含:
- 单元测试:验证store mutations/actions
- 集成测试:测试组件与store的交互
- E2E测试:完整流程验证
使用Jest测试store action的示例:
javascript复制// tests/unit/store/user.spec.js
describe('user store', () => {
let store
beforeEach(() => {
store = new Vuex.Store(cloneDeep(userModule))
jest.clearAllMocks()
})
describe('actions', () => {
it('fetchUserInfo commits SET_INFO on success', async () => {
const mockData = { name: 'test' }
getUserInfo.mockResolvedValue({ data: mockData })
await store.dispatch('fetchUserInfo')
expect(store.state.info).toEqual(mockData)
})
})
})
7. 从Vue2到Vue3的平滑迁移
7.1 Composition API下的数据请求
使用Composition API重构传统组件:
javascript复制// 传统Options API方式
export default {
data() {
return {
list: [],
loading: false
}
},
methods: {
async fetchData() {
this.loading = true
try {
this.list = await getList()
} finally {
this.loading = false
}
}
},
created() {
this.fetchData()
}
}
// Composition API方式
import { ref, onMounted } from 'vue'
export default {
setup() {
const list = ref([])
const loading = ref(false)
const fetchData = async () => {
loading.value = true
try {
list.value = await getList()
} finally {
loading.value = false
}
}
onMounted(fetchData)
return { list, loading }
}
}
7.2 Vuex到Pinia的迁移路径
迁移过程可以分为几个阶段:
- 并行运行:在项目中同时使用Vuex和Pinia
- 模块迁移:按功能模块逐步迁移
- 移除Vuex:当所有模块迁移完成后移除Vuex依赖
迁移工具函数示例:
javascript复制// utils/migrateStore.js
export function createPiniaFromVuex(vuexModule) {
return defineStore(vuexModule.namespace || 'module', {
state: () => ({ ...vuexModule.state() }),
getters: vuexModule.getters,
actions: Object.keys(vuexModule.actions).reduce((acc, name) => {
acc[name] = async function(...args) {
return this.$vuexStore.dispatch(
`${vuexModule.namespace}/${name}`,
...args
)
}
return acc
}, {})
})
}
7.3 新型状态管理方案探索
除了Pinia,还可以考虑:
- useState:简单的组合式函数状态共享
- Villus:基于GraphQL的状态管理
- RxJS集成:响应式编程范式
使用useState实现跨组件共享:
javascript复制// composables/useSharedState.js
import { ref, readonly } from 'vue'
export function useState(initialValue) {
const state = ref(initialValue)
const setState = (newValue) => {
state.value = newValue
}
return [readonly(state), setState]
}
// 在组件中使用
const [count, setCount] = useState(0)
8. 性能优化深度实践
8.1 请求节流与防抖
对于高频触发的数据请求,必须实施控制策略:
javascript复制// utils/optimizeRequest.js
export function debounceRequest(requestFn, delay = 300) {
let timer = null
return function(...args) {
clearTimeout(timer)
return new Promise(resolve => {
timer = setTimeout(async () => {
const result = await requestFn.apply(this, args)
resolve(result)
}, delay)
})
}
}
export function throttleRequest(requestFn, interval = 1000) {
let lastExecTime = 0
return function(...args) {
const now = Date.now()
if (now - lastExecTime >= interval) {
lastExecTime = now
return requestFn.apply(this, args)
}
}
}
8.2 数据懒加载模式
实现滚动加载的完整方案:
javascript复制// directives/lazyLoad.js
export default {
mounted(el, binding) {
const callback = binding.value
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
callback()
}
}, {
threshold: 0.1
})
observer.observe(el)
el._observer = observer
},
unmounted(el) {
if (el._observer) {
el._observer.disconnect()
}
}
}
在组件中使用:
vue复制<template>
<div v-lazy-load="loadMore" class="sentinel"></div>
</template>
<script>
export default {
methods: {
async loadMore() {
if (!this.loading && this.hasNextPage) {
await this.fetchNextPage()
}
}
}
}
</script>
8.3 内存优化策略
对于大型数据集,推荐采用虚拟滚动方案。以vue-virtual-scroller为例:
vue复制<template>
<RecycleScroller
class="scroller"
:items="list"
:item-size="56"
key-field="id"
>
<template v-slot="{ item }">
<div class="item">
{{ item.name }}
</div>
</template>
</RecycleScroller>
</template>
<script>
import { RecycleScroller } from 'vue-virtual-scroller'
export default {
components: { RecycleScroller },
data() {
return {
list: [] // 大数据集
}
}
}
</script>
9. 异常处理与监控体系
9.1 全局错误捕获
Vue的全局错误处理机制:
javascript复制// main.js
app.config.errorHandler = (err, vm, info) => {
console.error('Vue error:', err, info)
trackError(err)
if (isNetworkError(err)) {
showNetworkErrorToast()
}
}
axios的全局错误处理增强:
javascript复制// utils/errorHandler.js
export function createErrorHandler(router, store) {
return (error) => {
if (error.response) {
switch (error.response.status) {
case 401:
router.push('/login')
break
case 403:
showPermissionDenied()
break
case 500:
notifyServerError()
break
}
}
return Promise.reject(error)
}
}
9.2 请求重试机制
对于不稳定的网络请求,实现指数退避重试:
javascript复制// utils/retryRequest.js
export function withRetry(requestFn, options = {}) {
const { retries = 3, delay = 100 } = options
return async function(...args) {
let lastError = null
for (let i = 0; i < retries; i++) {
try {
return await requestFn.apply(this, args)
} catch (error) {
lastError = error
if (i < retries - 1) {
await new Promise(resolve =>
setTimeout(resolve, delay * Math.pow(2, i))
)
}
}
}
throw lastError
}
}
9.3 性能监控指标
关键性能指标采集:
javascript复制// utils/perfMonitor.js
export function trackPagePerf() {
const timing = window.performance.timing
const metrics = {
dns: timing.domainLookupEnd - timing.domainLookupStart,
tcp: timing.connectEnd - timing.connectStart,
ttfb: timing.responseStart - timing.requestStart,
domReady: timing.domContentLoadedEventEnd - timing.navigationStart,
load: timing.loadEventEnd - timing.navigationStart
}
sendMetrics(metrics)
}
export function trackApiPerf(startTime, url) {
const duration = Date.now() - startTime
const metric = {
url,
duration,
timestamp: new Date().toISOString()
}
if (duration > 1000) {
metric.slow = true
}
sendApiMetrics(metric)
}
10. 微前端架构下的状态管理
10.1 跨应用状态共享
使用CustomEvent实现应用间通信:
javascript复制// 主应用
window.dispatchEvent(new CustomEvent('global-state-update', {
detail: { key: 'user', value: userInfo }
}))
// 子应用
window.addEventListener('global-state-update', (event) => {
console.log('Received state:', event.detail)
})
10.2 状态隔离方案
确保子应用状态独立:
javascript复制// 子应用入口文件
let storeInstance = null
export async function mount({ container, sharedState }) {
storeInstance = createStore(sharedState)
app.use(storeInstance).mount(container)
}
export async function unmount() {
storeInstance = null
}
10.3 共享依赖管理
处理Vue和Vuex的版本冲突:
javascript复制// webpack配置
module.exports = {
externals: {
vue: 'Vue',
vuex: 'Vuex'
}
}
// 在主应用中加载共享依赖
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.47/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex@4.1.0/dist/vuex.global.js"></script>
11. TypeScript深度集成
11.1 类型化Vuex
为Vuex添加完整的类型支持:
typescript复制// store/types.ts
interface UserState {
info: UserInfo | null
permissions: string[]
}
interface RootState {
user: UserState
}
// store/modules/user.ts
const mutations = {
SET_INFO(state: UserState, payload: UserInfo) {
state.info = payload
}
} as MutationTree<UserState>
const actions = {
async fetchUserInfo({ commit }: ActionContext<UserState, RootState>) {
const { data } = await getUserInfo<UserInfo>()
commit('SET_INFO', data)
}
} as ActionTree<UserState, RootState>
11.2 类型安全请求
定义API响应类型:
typescript复制// api/types.ts
interface ApiResponse<T> {
code: number
message?: string
data: T
}
// api/user.ts
export async function getUserInfo() {
return axios.get<ApiResponse<UserInfo>>('/api/user/info')
}
11.3 组合式API类型
为Composition API提供类型支持:
typescript复制// composables/useUser.ts
interface UserComposable {
userInfo: ComputedRef<UserInfo | null>
fetchUserInfo: () => Promise<void>
}
export function useUser(): UserComposable {
const store = useStore()
const userInfo = computed(() => store.state.user.info)
const fetchUserInfo = async () => {
await store.dispatch('user/fetchUserInfo')
}
return { userInfo, fetchUserInfo }
}
12. 移动端特殊考量
12.1 弱网环境处理
实现离线优先策略:
javascript复制// utils/offlineManager.js
export function createOfflineManager(store) {
const queue = []
let isOnline = navigator.onLine
window.addEventListener('online', () => {
isOnline = true
processQueue()
})
window.addEventListener('offline', () => {
isOnline = false
})
function processQueue() {
while (queue.length && isOnline) {
const action = queue.shift()
store.dispatch(action.type, action.payload)
}
}
return {
dispatchOffline(type, payload) {
if (isOnline) {
return store.dispatch(type, payload)
} else {
queue.push({ type, payload })
return Promise.resolve()
}
}
}
}
12.2 数据持久化
使用localForage实现本地存储:
javascript复制// plugins/persistence.js
import localForage from 'localforage'
export async function persistStore(store) {
// 从本地加载状态
const savedState = await localForage.getItem('vuex-state')
if (savedState) {
store.replaceState(merge(store.state, savedState))
}
// 订阅store变化
store.subscribe((mutation, state) => {
localForage.setItem('vuex-state', pick(state, ['user', 'settings']))
})
}
12.3 图片懒加载优化
实现自适应图片加载:
vue复制<template>
<img
v-lazy="imageSrc"
:data-srcset="`${small} 300w, ${medium} 600w, ${large} 1200w`"
sizes="(max-width: 600px) 300px, (max-width: 1200px) 600px, 1200px"
>
</template>
<script>
export default {
directives: {
lazy: {
mounted(el) {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
const src = el.dataset.src
if (src) {
el.src = src
if (el.dataset.srcset) {
el.srcset = el.dataset.srcset
}
observer.unobserve(el)
}
}
})
observer.observe(el)
el._observer = observer
},
unmounted(el) {
if (el._observer) {
el._observer.disconnect()
}
}
}
}
}
</script>
13. 测试策略全覆盖
13.1 Store单元测试
使用Jest测试Vuex store:
javascript复制// tests/unit/store/user.spec.js
describe('user store', () => {
let store
beforeEach(() => {
store = new Vuex.Store(cloneDeep(userModule))
jest.clearAllMocks()
})
describe('mutations', () => {
it('SET_INFO should update state', () => {
const mockUser = { name: 'test' }
store.commit('SET_INFO', mockUser)
expect(store.state.info).toEqual(mockUser)
})
})
describe('actions', () => {
it('fetchUserInfo should commit SET_INFO', async () => {
const mockUser = { name: 'test' }
getUserInfo.mockResolvedValue({ data: mockUser })
await store.dispatch('fetchUserInfo')
expect(store.state.info).toEqual(mockUser)
})
})
})
13.2 组件集成测试
测试组件与store的交互:
javascript复制// tests/integration/UserProfile.spec.js
describe('UserProfile', () => {
let store
beforeEach(() => {
store = createStore()
})
it('should display user info when loaded', async () => {
const mockUser = { name: 'test' }
store.commit('user/SET_INFO', mockUser)
const wrapper = mount(UserProfile, {
global: { plugins: [store] }
})
expect(wrapper.find('.user-name').text()).toBe(mockUser.name)
})
})
13.3 E2E测试场景
使用Cypress测试完整流程:
javascript复制// tests/e2e/user.cy.js
describe('User Authentication', () => {
it('should login and display user info', () => {
cy.intercept('POST', '/api/login', { fixture: 'user.json' }).as('login')
cy.visit('/login')
cy.get('#username').type('testuser')
cy.get('#password').type('password')
cy.get('button[type=submit]').click()
cy.wait('@login')
cy.location('pathname').should('eq', '/dashboard')
cy.contains('.user-name', 'testuser')
})
})
14. 安全防护最佳实践
14.1 XSS防护
在显示用户输入时自动转义:
javascript复制// directives/safe-html.js
import DOMPurify from 'dompurify'
export default {
mounted(el, binding) {
el.innerHTML = DOMPurify.sanitize(binding.value, {
ALLOWED_TAGS: ['b', 'i', 'a'],
ALLOWED_ATTR: ['href']
})
},
updated(el, binding) {
el.innerHTML = DOMPurify.sanitize(binding.value)
}
}
14.2 CSRF防护
自动处理CSRF token:
javascript复制// utils/csrf.js
function getCookie(name) {
const value = `; ${document.cookie}`
const parts = value.split(`; ${name}=`)
if (parts.length === 2) return parts.pop().split(';').shift()
}
axios.interceptors.request.use(config => {
const token = getCookie('XSRF-TOKEN')
if (token) {
config.headers['X-XSRF-TOKEN'] = token
}
return config
})
14.3 敏感数据保护
对敏感数据进行混淆处理:
javascript复制// utils/dataProtect.js
export function protectData(data, fields) {
return new Proxy(data, {
get(target, prop) {
if (fields.includes(prop) && !isDev) {
console.warn(`Access to sensitive field "${prop}" is restricted`)
return null
}
return target[prop]
}
})
}
// 在store中使用
const state = () => ({
user: protectData({
name: 'test',
token: 'secret'
}, ['token'])
})
15. 国际化与本地化处理
15.1 多语言状态管理
将语言包存入Vuex:
javascript复制// store/modules/i18n.js
const state = () => ({
locale: 'zh-CN',
messages: {
'zh-CN': require('@/locales/zh-CN.json'),
'en-US': require('@/locales/en-US.json')
}
})
const mutations = {
SET_LOCALE(state, locale) {
state.locale = locale
}
}
const getters = {
t: state => key => {
return state.messages[state.locale][key] || key
}
}
15.2 请求头语言设置
自动添加Accept-Language头:
javascript复制// utils/request.js
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
headers: {
'Accept-Language': store.getters['i18n/locale']
}
})
// 监听语言变化
store.watch(
state => state.i18n.locale,
(locale) => {
service.defaults.headers['Accept-Language'] = locale
}
)
15.3 日期时间本地化
基于store状态格式化日期:
javascript复制// filters/date.js
import { format, utcToZonedTime } from 'date-fns-tz'
import { zhCN, enUS } from 'date-fns/locale'
const locales = { 'zh-CN': zhCN, 'en-US': enUS }
export function formatDate(value, pattern = 'PPpp') {
const locale = store.getters['i18n/locale']
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
const zonedDate = utcToZonedTime(value, timeZone)
return format(zonedDate, pattern, {
timeZone,
locale: locales[locale]
})
}
16. 调试技巧与开发工具
16.1 Vue DevTools高级用法
利用DevTools进行状态调试:
- 时间旅行调试:回放mutations追踪状态变化
- 组件注入:在控制台访问组件实例
- 自定义插件:扩展DevTools功能
示例插件:
javascript复制// plugins/devtools.js
export default {
install(app) {
app.config.globalProperties.$logState = () => {
console.log('Current state:', JSON.parse(JSON.stringify(store.state)))
}
}
}
16.2 请求日志记录
开发环境请求调试工具:
javascript复制// utils/requestLogger.js
export function createRequestLogger(axiosInstance) {
if (process.env.NODE_ENV === 'development') {
axiosInstance.interceptors.request.use(config => {
console.log('Request:', config.method.toUpperCase(), config.url)
return config
})
axiosInstance.interceptors.response.use(response => {
console.log('Response:', response.config.url, response.data)
return response
}, error => {
console.error('Error:', error.config
