Vuex返回非预期状态问题:存在交易却异常显示无交易弹窗
问题背景
我做了一个弹窗模态框,逻辑是用户无交易记录时显示,有交易记录(depositHistory数组长度>0)时隐藏。但现在测试用的明明有2条交易记录的账户,弹窗还是异常显示了;控制台打印depositHistory是空数组,可其他同事用同一个depositHistory getter的组件却能正常拿到数据。
组件代码如下:
<template> <div v-if='renderModal'> <p>Fund your account</p> </div> </template> <script> const DepositGetter = namespace("deposit").Getter const AppProps = Vue.extend({ data: () => { return { renderModal: false } }, beforeMount() { // 当用户无交易记录时渲染弹窗 if (this.depositHistory.length === 0) this.renderModal = true console.log(JSON.parse(JSON.stringify(this.depositHistory))) // 通常返回 [__ob__: Observer] // 添加JSON方法后现在打印 [] } }) export default class HomeGameBanner extends AppProps { @DepositGetter depositHistory } </script>
Store相关代码(供排查):
type DepositGetter = GetterTree<DepositState, RootState>; export const getters: DepositGetter = { depositHistory: state => state.content }; export const deposit: Module<DepositState, RootState> = { getters }
排查思路与解决方案
其他组件能用同一个getter拿到数据,说明Store里的state.content其实是有值的,问题大概率出在你的组件获取数据的时机不对,下面是具体的排查和解决方法:
1. 异步数据加载的时机问题
beforeMount钩子执行的时候,Store里的depositHistory可能还没完成异步加载!其他同事的组件可能用了watch监听数据变化,或者直接在模板里绑定响应式数据,所以能自动更新。而你只在beforeMount里判断了一次,此时数据还没加载好,导致renderModal被设为true,之后数据加载完成也不会自动更新这个状态。
推荐解决办法:用计算属性替代手动维护状态
直接把弹窗的显示逻辑写成计算属性,让Vue自动响应depositHistory的变化,省得手动维护变量:
<template> <div v-if='shouldShowModal'> <p>Fund your account</p> </div> </template> <script> // ... 其他代码不变 export default class HomeGameBanner extends AppProps { @DepositGetter depositHistory get shouldShowModal() { return this.depositHistory.length === 0; } } </script>
如果一定要手动维护renderModal,也可以用watch监听数据变化:
// 在组件类中添加watch监听 @Watch('depositHistory') onDepositHistoryChange(newVal: any[]) { this.renderModal = newVal.length === 0; } // 同时在created钩子中初始化一次状态 created() { this.renderModal = this.depositHistory.length === 0; }
2. 检查Getter的命名空间是否正确
你用了namespace("deposit"),要确认Store里的deposit模块确实开启了命名空间。如果模块注册时没加namespaced: true,那你的@DepositGetter depositHistory可能拿到的是全局getter,而非目标模块的。
检查Store模块注册代码,确保是这样的:
export default new Vuex.Store({ modules: { deposit: { namespaced: true, // 必须开启命名空间! state, getters, // ...其他actions/mutations } } })
3. 验证数据加载的时机
可以做个小测试,在beforeMount里加个延时,看看延时后depositHistory是否有数据:
beforeMount() { setTimeout(() => { console.log(this.depositHistory); // 如果延时后有数据,说明是异步加载时机问题 }, 1000); }
如果测试结果符合预期,那用前面的计算属性或watch方案就能彻底解决问题。
内容的提问来源于stack exchange,提问作者Mark

