返回

Vue 3 用户状态管理: 解决 Vuex 更新难题

vue.js

Vue.js 3 用户状态管理问题排查与解决

在Vue.js 3应用程序中使用Vuex进行用户状态管理时,可能遇到状态无法正确更新或组件无法响应状态变化的情况。特别是当涉及从后端(例如Laravel Blade)传递初始数据时,这类问题变得更为棘手。本篇文章旨在分析此类问题的常见原因并提供相应的解决方案。

问题

当你在 Vue.js 3 应用中尝试使用 Vuex 来管理用户状态时,可能会遇到以下情况:

  1. 初始状态(比如是否显示用户菜单)无法正确反映后端传递的用户信息。
  2. mainapp.vue 组件中,尝试访问 $store.state.user 时发现值为 nullundefined,即使后端已经通过Blade模板传递了用户数据。
  3. 在组件的created生命周期钩子中使用this.$store.commit('updateUser', this.user)尝试更新Vuex用户状态时,收到的 dataundefined

以上问题通常会导致用户界面不符合预期,例如菜单不显示、页面无法加载等。

原因分析

此类问题通常由以下原因导致:

  1. 异步数据问题 : 从 Blade 模板传递的数据通常是通过 props 传递给 Vue 组件。然而,在 created 生命周期钩子中,props 有时可能还未完全解析完成,这会使 this.user 在生命周期方法中返回 undefined, 导致传递给 updateUser mutation 的数据丢失,并最终在 Vuex state 中存入 undefined。
  2. Vuex Store 状态管理错误: 直接在mutation中修改 state, 没有利用 state的响应式,会导致 组件视图无法响应state的变化

解决方案

解决方案一:利用 watch 属性监听 props 变化

此方法核心思想是在props数据变更后在进行store操作。避免在created阶段获取不到props值。

  1. mainapp.vue 组件修改 :使用 watch 监听 props 中的 user 属性,并在值发生变化时触发 commit 来更新 Vuex store。

    <template>
      <div>
        <div v-if="$store.state.user">
          <!-- 用户菜单代码 -->
        </div>
        <router-view />
      </div>
    </template>
    
    <script>
    export default {
      props: ['user'],
      watch: {
        user: {
           handler(newUser){
              console.log("watching", newUser)
               this.$store.commit('updateUser', newUser)
           },
          immediate:true
        }
      },
    
    };
    </script>
    

此修改的核心是将 $store.commit 从 created 移动到 watch 的 handler 函数内,这样确保只有 this.user (props)有值的时候 commit mutation 才被调用,从而解决了props加载异步的问题。 immediate: true 代表在初始加载的时候执行一次handler,实现首次用户数据同步到Vuex state

解决方案二: 使用onMounted钩子代替created

在 Vue 3 中, created 钩子是在组件实例初始化时执行,这时候 props 可能还没有被完全处理完毕。onMounted 钩子在组件挂载后执行,这时候props的数据一定完成初始化,更合适用来读取props,并触发相关的store 操作

  1. ** mainapp.vue 组件修改**

      <template>
       <div>
         <div v-if="$store.state.user">
           <!-- 用户菜单代码 -->
         </div>
         <router-view />
       </div>
     </template>
    
     <script>
       import { onMounted } from 'vue';
    
       export default {
         props: ['user'],
          setup(props) {
    
         const { user } = props
    
             const store =  this.$store
               onMounted(()=>{
                   console.log("onMounted user:", user);
                 store.commit("updateUser", user);
             })
    
              return {}; 
    
         }
    
     };
     </script>
    
    

此方案使用 setup() 来访问 props , 用 onMounted 来处理同步操作。 确保组件挂载后再尝试读取 user prop 值,从而同步store。

安全建议:

  • 使用 JSON.parse(JSON.stringify(data)) 将props中的对象转化为新的对象,而不是简单的直接赋值。确保对象引用不会被vue监测
  • 当用户状态比较敏感时候,需要避免使用 window 对象传递到前端,可能出现XSS注入的风险,可以通过 api 进行获取。

代码示例

(以下代码请在已有代码基础上按上述步骤操作)

mainapp.vue 文件的 script 标签内,删除 created() 生命周期钩子函数,使用上方 解决方案 一 或者 解决方案二 的代码替代:

  • 方案一:
    props: ['user'],
   watch: {
     user: {
        handler(newUser){
           console.log("watching", newUser)
            this.$store.commit('updateUser', newUser)
        },
       immediate:true
     }
   },
  • 方案二:
    ```js
    import { onMounted } from 'vue';
    export default {
    props: ['user'],
    setup(props) {

      const { user } = props
    
       const store =  this.$store
         onMounted(()=>{
           console.log("onMounted user:", user);
           store.commit("updateUser", user);
         })
         return {};
    }
    

    };

```

** store.js文件,无须进行修改**

 import { createStore } from 'vuex';

 const store = createStore({
     state : {
         conuter : 1000, 
         deleteModalObj : {
             showDeleteModal: false, 
             deleteUrl : '', 
             data : null, 
             deletingIndex: -1, 
             isDeleted : false,

         }, 
         user: null, 
         
     }, 
     getters: {
         getCounter(state){
             return state.conuter
         }, 
         getDeleteModalObj(state){
             return state.deleteModalObj;
         },

     },

     mutations: {
         changeTheCounter(state, data){
             state.conuter += data
         }, 

         setDeleteModal(state, data){
             const deleteModalObj = {
                 showDeleteModal: false, 
                 deleteUrl : '', 
                 data : null, 
                 deletingIndex: -1, 
                 isDeleted : data,
             }
             state.deleteModalObj = deleteModalObj
         },
         setDeletingModalObj(state, data){

         console.log('setDeletingModalObj mutation called');
         console.log('Data received:', JSON.parse(JSON.stringify(data))); // Convert Proxy to plain object for logging


         state.deleteModalObj = data

          console.log('Updated deleteModalObj state:', JSON.parse(JSON.stringify(state.deleteModalObj)));
       },
         updateUser(state, data){
              console.log('Updating user in Vuex:', data);
             state.user = data
        },
   }, 
   actions :{
         changeCounterAction({commit}, data){
             commit('changeTheCounter', data)
         }
     }

  });

  export default store;

通过以上任何一种方法,都可以解决 Vue.js 3 中用户状态管理的问题,确保从 Blade 模板传递的数据能够正确地同步到 Vuex store 中。并保证页面能正常响应用户登录状态。