学习:Vuex (1)

Vuex简介与核心概念

Vuex是Vue的官方状态管理库,专为Vue.js应用程序设计的状态管理模式。Vuex 采用单仓库加多级模块的树状结构,层级固定,依赖 mutations,整体更重。

Vuex的核心概念:

  • State: 存储应用状态数据
  • Getter: 用于从state中派生出一些状态
  • Mutation: 更改state中状态的唯一方法,必须是同步函数
  • Action: 可以包含异步操作,通过提交mutation来更改状态
  • Module: 将store分割成模块,每个模块拥有自己的state、mutation、action、getter

Vuex安装与配置

安装Vuex:

复制代码
# Vue 3.x
npm install vuex@next --save

# Vue 2.x
npm install vuex --save

# 使用yarn
yarn add vuex@next  # Vue 3.x
yarn add vuex       # Vue 2.x

基本配置 (Vue 3.x):

javascript 复制代码
// src/store/index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0
  },
  getters: {
    doubleCount: state => state.count * 2
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    increment({ commit }) {
      commit('increment')
    }
  }
})

在main.js中注册:

javascript 复制代码
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

const app = createApp(App)
app.use(store)
app.mount('#app')

State、Getter、Mutation、Action、Module详解

State详解

单一状态树:

javascript 复制代码
const store = createStore({
  state: {
    count: 0,
    user: {
      name: 'John Doe',
      email: 'john@example.com'
    },
    products: []
  }
})

在组件中访问State:

javascript 复制代码
// 方式1: 直接访问
this.$store.state.count

// 方式2: 使用计算属性
computed: {
  count() {
    return this.$store.state.count
  }
}

// 方式3: 使用辅助函数
import { mapState } from 'vuex'
computed: {
  ...mapState(['count']),
  // 或者使用对象形式
  ...mapState({
    counter: 'count',  // 映射 this.counter 到 store.state.count
    user: state => state.user  // 使用函数
  })
}

在Composition API中访问State:

javascript 复制代码
import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()
    
    return {
      count: computed(() => store.state.count),
      user: computed(() => store.state.user)
    }
  }
}
Getter详解

基础Getter:

javascript 复制代码
const store = createStore({
  state: {
    todos: [
      { id: 1, text: 'Learn Vuex', done: true },
      { id: 2, text: 'Learn Pinia', done: false }
    ]
  },
  getters: {
    // 基础getter
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    },
    
    // 带参数的getter - 返回函数
    getTodoById: (state) => (id) => {
      return state.todos.find(todo => todo.id === id)
    },
    
    // 使用其他getter
    doneTodosCount: (state, getters) => {
      return getters.doneTodos.length
    }
  }
})

在组件中使用Getter:

javascript 复制代码
// 方式1: 直接访问
this.$store.getters.doneTodos

// 方式2: 使用计算属性
computed: {
  doneTodos() {
    return this.$store.getters.doneTodos
  }
}

// 方式3: 使用辅助函数
import { mapGetters } from 'vuex'
computed: {
  ...mapGetters(['doneTodos', 'doneTodosCount']),
  
  // 使用对象形式
  ...mapGetters({
    done: 'doneTodos',  // 映射 this.done 到 store.getters.doneTodos
    todosById: 'getTodoById'
  })
}

// 使用带参数的getter
methods: {
  getTodo(id) {
    return this.$store.getters.getTodoById(id)
  }
}
Mutation详解

基础Mutation:

javascript 复制代码
const store = createStore({
  state: {
    count: 0,
    user: null
  },
  mutations: {
    // 基础mutation
    increment(state) {
      state.count++
    },
    
    // 带参数的mutation - 载荷(payload)
    incrementBy(state, payload) {
      state.count += payload.amount
    },
    
    // 对象风格的提交方式
    setUser(state, payload) {
      state.user = payload.user
    },
    
    // 使用常量定义mutation类型
    [MUTATION_TYPES.SET_USER](state, user) {
      state.user = user
    }
  }
})

// 定义mutation类型常量
const MUTATION_TYPES = {
  SET_USER: 'SET_USER',
  INCREMENT: 'INCREMENT'
}

提交Mutation:

javascript 复制代码
// 方式1: 对象风格提交
this.$store.commit({
  type: 'incrementBy',
  amount: 10
})

// 方式2: 直接提交
this.$store.commit('incrementBy', { amount: 10 })

// 方式3: 使用辅助函数
import { mapMutations } from 'vuex'
methods: {
  ...mapMutations([
    'increment',  // 映射 this.increment() 到 this.$store.commit('increment')
    
    // 使用对象形式
    incrementBy: 'incrementBy'  // 映射 this.incrementBy(amount) 到 this.$store.commit('incrementBy', amount)
  ]),
  
  // 或者直接定义
  increment() {
    this.$store.commit('increment')
  }
}

Mutation必须遵循Vue的响应规则:

javascript 复制代码
mutations: {
  // ✅ 正确: 添加新属性
  addItem(state, item) {
    Vue.set(state.items, item.id, item)
    // 或者
    state.items = { ...state.items, [item.id]: item }
  },
  
  // ✅ 正确: 删除属性
  removeItem(state, itemId) {
    Vue.delete(state.items, itemId)
    // 或者
    const { [itemId]: removed, ...rest } = state.items
    state.items = rest
  }
}
Action详解

基础Action:

javascript 复制代码
const store = createStore({
  state: {
    count: 0,
    user: null,
    isLoading: false
  },
  mutations: {
    INCREMENT(state) {
      state.count++
    },
    SET_USER(state, user) {
      state.user = user
    },
    SET_LOADING(state, status) {
      state.isLoading = status
    }
  },
  actions: {
    // 基础action
    increment({ commit }) {
      commit('INCREMENT')
    },
    
    // 异步action
    async fetchUser({ commit }, userId) {
      commit('SET_LOADING', true)
      try {
        const response = await api.getUser(userId)
        commit('SET_USER', response.data)
      } catch (error) {
        console.error('Error fetching user:', error)
      } finally {
        commit('SET_LOADING', false)
      }
    },
    
    // 组合多个mutation的action
    complexAction({ commit, state, dispatch }, payload) {
      // 可以访问state
      if (state.user) {
        // 提交mutation
        commit('INCREMENT')
        
        // 派发其他action
        dispatch('anotherAction', payload)
      }
    },
    
    // 返回Promise的action
    asyncAction({ commit }) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          commit('INCREMENT')
          resolve()
        }, 1000)
      })
    }
  }
})

在组件中派发Action:

javascript 复制代码
// 方式1: 直接派发
this.$store.dispatch('increment')
this.$store.dispatch('fetchUser', userId)

// 方式2: 对象风格派发
this.$store.dispatch({
  type: 'fetchUser',
  userId: 123
})

// 方式3: 使用辅助函数
import { mapActions } from 'vuex'
methods: {
  ...mapActions([
    'increment',  // 映射 this.increment() 到 this.$store.dispatch('increment')
    
    // 使用对象形式
    fetchUser: 'fetchUser'  // 映射 this.fetchUser(userId) 到 this.$store.dispatch('fetchUser', userId)
  ]),
  
  // 或者直接定义
  loadUser(userId) {
    this.$store.dispatch('fetchUser', userId)
      .then(() => {
        console.log('User loaded')
      })
  }
}
Module详解

基础模块:

javascript 复制代码
// modules/user.js
const user = {
  namespaced: true,  // 开启命名空间
  
  state: () => ({
    user: null,
    token: null
  }),
  
  getters: {
    isAuthenticated: state => !!state.token,
    userName: state => state.user ? state.user.name : ''
  },
  
  mutations: {
    SET_USER(state, user) {
      state.user = user
    },
    SET_TOKEN(state, token) {
      state.token = token
    }
  },
  
  actions: {
    login({ commit }, credentials) {
      return api.login(credentials)
        .then(response => {
          commit('SET_USER', response.user)
          commit('SET_TOKEN', response.token)
          return response
        })
    },
    
    logout({ commit }) {
      commit('SET_USER', null)
      commit('SET_TOKEN', null)
    }
  }
}

export default user

注册模块:

javascript 复制代码
import { createStore } from 'vuex'
import user from './modules/user'
import products from './modules/products'

export default createStore({
  state: {
    // 根级别state
    appVersion: '1.0.0'
  },
  
  modules: {
    user,
    products,
    
    // 动态注册模块
    dynamicModule: {
      namespaced: true,
      // ...模块定义
    }
  }
})

在命名空间模块中访问:

javascript 复制代码
// 在组件中访问命名空间模块
this.$store.state.user.user        // 访问user模块的state
this.$store.getters['user/isAuthenticated']  // 访问user模块的getter
this.$store.commit('user/SET_USER', userData) // 提交user模块的mutation
this.$store.dispatch('user/login', credentials) // 派发user模块的action

// 使用辅助函数
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'

computed: {
  ...mapState('user', ['user', 'token']),  // 访问user模块的state
  ...mapGetters('user', ['isAuthenticated', 'userName'])  // 访问user模块的getter
},
methods: {
  ...mapMutations('user', ['SET_USER', 'SET_TOKEN']),  // 访问user模块的mutation
  ...mapActions('user', ['login', 'logout'])  // 访问user模块的action
}

动态注册模块:

javascript 复制代码
// 注册模块
store.registerModule('newModule', {
  // 模块定义
})

// 注册嵌套模块
store.registerModule(['nested', 'newModule'], {
  // 模块定义
})

// 检查模块是否已注册
store.hasModule('newModule')

// 注销模块
store.unregisterModule('newModule')

Vuex在Vue3中的使用

Composition API中使用Vuex:

javascript 复制代码
import { computed, onMounted } from 'vue'
import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()
    
    // 访问state
    const count = computed(() => store.state.count)
    const user = computed(() => store.state.user.user)
    
    // 访问getters
    const doubleCount = computed(() => store.getters.doubleCount)
    const isAuthenticated = computed(() => store.getters['user/isAuthenticated'])
    
    // 提交mutations
    const increment = () => store.commit('increment')
    const setUser = (userData) => store.commit('user/SET_USER', userData)
    
    // 派发actions
    const fetchUser = (userId) => store.dispatch('user/fetchUser', userId)
    
    // 生命周期钩子
    onMounted(() => {
      fetchUser(123)
    })
    
    return {
      count,
      user,
      doubleCount,
      isAuthenticated,
      increment,
      setUser,
      fetchUser
    }
  }
}

创建可组合的Vuex函数:

javascript 复制代码
// composables/useUser.js
import { computed } from 'vue'
import { useStore } from 'vuex'

export function useUser() {
  const store = useStore()
  
  const user = computed(() => store.state.user.user)
  const token = computed(() => store.state.user.token)
  const isAuthenticated = computed(() => store.getters['user/isAuthenticated'])
  
  const login = (credentials) => store.dispatch('user/login', credentials)
  const logout = () => store.dispatch('user/logout')
  
  return {
    user,
    token,
    isAuthenticated,
    login,
    logout
  }
}

// 在组件中使用
import { useUser } from '@/composables/useUser'

export default {
  setup() {
    const { user, isAuthenticated, login, logout } = useUser()
    
    const handleLogin = async () => {
      try {
        await login({ email: 'test@example.com', password: 'password' })
        console.log('Login successful')
      } catch (error) {
        console.error('Login failed:', error)
      }
    }
    
    return {
      user,
      isAuthenticated,
      handleLogin,
      logout
    }
  }
}

Vuex与组件通信

父子组件通信:

html 复制代码
// 父组件
<template>
  <div>
    <h2>Parent Component</h2>
    <p>Count: {{ count }}</p>
    <child-component></child-component>
  </div>
</template>

<script>
import { mapState } from 'vuex'
import ChildComponent from './ChildComponent.vue'

export default {
  components: { ChildComponent },
  computed: {
    ...mapState(['count'])
  }
}
</script>

// 子组件
<template>
  <div>
    <h3>Child Component</h3>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'

export default {
  computed: {
    ...mapState(['count'])
  },
  methods: {
    ...mapMutations(['increment'])
  }
}
</script>

跨级组件通信:

html 复制代码
// 无论组件层级多深,都可以通过Vuex共享状态
// 深层嵌套组件
<template>
  <div>
    <h4>Deeply Nested Component</h4>
    <p>Global Theme: {{ theme }}</p>
    <button @click="changeTheme('dark')">Dark Theme</button>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex'

export default {
  computed: {
    ...mapState('ui', ['theme'])
  },
  methods: {
    ...mapActions('ui', ['changeTheme'])
  }
}
</script>

事件总线替代方案:

javascript 复制代码
// 传统方式: 使用事件总线进行组件间通信
// 使用Vuex: 通过共享状态和actions实现通信

// 组件A - 触发操作
export default {
  methods: {
    performAction() {
      // 派发action而不是发射事件
      this.$store.dispatch('performAction', { data: 'some data' })
    }
  }
}

// 组件B - 响应操作
export default {
  computed: {
    operationResult() {
      return this.$store.state.operationResult
    }
  },
  watch: {
    operationResult(newVal) {
      // 响应操作结果
      console.log('Operation result:', newVal)
    }
  }
}

辅助函数(mapState, mapGetters等)

mapState详解:

javascript 复制代码
import { mapState } from 'vuex'

export default {
  computed: {
    // 数组形式 - 直接映射同名state
    ...mapState(['count', 'user']),
    
    // 对象形式 - 自定义名称
    ...mapState({
      // 键为组件中的计算属性名,值为state中的路径
      counter: 'count',  // 映射 this.counter 到 store.state.count
      
      // 使用函数形式 - 可以访问组件实例(this)
      userName(state) {
        return state.user ? state.user.name : 'Guest'
      },
      
      // 使用箭头函数 - 不能访问this
      userEmail: state => state.user ? state.user.email : ''
    }),
    
    // 结合命名空间
    ...mapState('user', {
      userToken: 'token',
      userInfo: state => state.user
    })
  }
}

mapGetters详解:

javascript 复制代码
import { mapGetters } from 'vuex'

export default {
  computed: {
    // 数组形式 - 直接映射同名getter
    ...mapGetters(['doneTodos', 'doneTodosCount']),
    
    // 对象形式 - 自定义名称
    ...mapGetters({
      completedTodos: 'doneTodos',  // 映射 this.completedTodos 到 store.getters.doneTodos
      
      // 使用函数形式
      userStatus: (getters) => {
        return getters['user/isAuthenticated'] ? 'Logged In' : 'Logged Out'
      }
    }),
    
    // 结合命名空间
    ...mapGetters('user', ['isAuthenticated', 'userName']),
    ...mapGetters('user', {
      loggedIn: 'isAuthenticated',
      name: 'userName'
    })
  }
}

mapMutations详解:

javascript 复制代码
import { mapMutations } from 'vuex'

export default {
  methods: {
    // 数组形式 - 直接映射同名mutation
    ...mapMutations(['increment', 'decrement']),
    
    // 对象形式 - 自定义名称
    ...mapMutations({
      add: 'increment',  // 映射 this.add() 到 this.$store.commit('increment')
      
      // 使用函数形式 - 可以返回一个函数
      incrementBy: (commit) => (amount) => {
        commit('incrementBy', { amount })
      }
    }),
    
    // 结合命名空间
    ...mapMutations('user', ['SET_USER', 'SET_TOKEN']),
    ...mapMutations('user', {
      setUser: 'SET_USER',
      setToken: 'SET_TOKEN'
    })
  }
}

mapActions详解:

javascript 复制代码
import { mapActions } from 'vuex'

export default {
  methods: {
    // 数组形式 - 直接映射同名action
    ...mapActions(['fetchData', 'submitData']),
    
    // 对象形式 - 自定义名称
    ...mapActions({
      loadData: 'fetchData',  // 映射 this.loadData() 到 this.$store.dispatch('fetchData')
      
      // 使用函数形式 - 可以返回一个函数
      saveData: (dispatch) => (data) => {
        return dispatch('submitData', { data, timestamp: Date.now() })
      }
    }),
    
    // 结合命名空间
    ...mapActions('user', ['login', 'logout']),
    ...mapActions('user', {
      userLogin: 'login',
      userLogout: 'logout'
    })
  }
}

createNamespacedHelpers辅助函数:

javascript 复制代码
import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('user')

export default {
  computed: {
    // 自动使用user命名空间
    ...mapState(['user', 'token'])
  },
  methods: {
    // 自动使用user命名空间
    ...mapActions(['login', 'logout'])
  }
}

Vuex模块化

模块化结构:

javascript 复制代码
src/store/
  ├── index.js          # 主store文件
  ├── modules/
  │   ├── index.js      # 导出所有模块
  │   ├── user.js       # 用户模块
  │   ├── products.js   # 产品模块
  │   ├── cart.js       # 购物车模块
  │   └── ui.js         # UI状态模块
  └── plugins/
      ├── logger.js     # 日志插件
      └── persist.js    # 持久化插件

Vuex高级特性

插件系统:

javascript 复制代码
// plugins/logger.js
const logger = store => {
  // 初始化
  console.log('Initial state:', store.state)
  
  // 订阅mutation
  store.subscribe((mutation, state) => {
    console.log(`Mutation: ${mutation.type}`, mutation.payload)
  })
  
  // 订阅action
  store.subscribeAction({
    before: (action, state) => {
      console.log(`Action: ${action.type}`, action.payload)
    },
    after: (action, state) => {
      console.log(`Action finished: ${action.type}`)
    },
    error: (action, state, error) => {
      console.error(`Action error: ${action.type}`, error)
    }
  })
}

export default logger

严格模式:

javascript 复制代码
const store = createStore({
  // ...其他配置
  strict: process.env.NODE_ENV !== 'production'  // 只在开发环境启用
})

// 在严格模式下,状态变更只能通过mutation
// 以下代码在严格模式下会抛出错误
state.count++  // ❌ 错误:直接修改状态

// 必须通过mutation
commit('increment')  // ✅ 正确

热重载:

javascript 复制代码
// 在开发环境中启用热重载
if (process.env.NODE_ENV === 'development' && module.hot) {
  module.hot.accept(['./modules/user', './modules/products'], () => {
    // 获取新的模块
    const newUserModule = require('./modules/user').default
    const newProductsModule = require('./modules/products').default
    
    // 热重载模块
    store.hotUpdate({
      modules: {
        user: newUserModule,
        products: newProductsModule
      }
    })
  })
}

动态模块:

javascript 复制代码
// 注册动态模块
store.registerModule('dynamicModule', {
  namespaced: true,
  state: () => ({
    data: null
  }),
  mutations: {
    SET_DATA(state, data) {
      state.data = data
    }
  },
  actions: {
    fetchData({ commit }) {
      // 获取数据逻辑
    }
  }
})

// 注册保留状态的模块
store.registerModule('persistentModule', moduleConfig, { preserveState: true })

// 注册嵌套动态模块
store.registerModule(['nested', 'deeplyNested'], moduleConfig)

// 检查模块是否存在
const moduleExists = store.hasModule('dynamicModule')

// 卸载动态模块
store.unregisterModule('dynamicModule')

表单处理:

javascript 复制代码
// 使用v-model和Vuex
<input v-model="message">

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

// 或者使用辅助函数
computed: {
  ...mapState({
    message: state => state.message
  })
},
methods: {
  ...mapMutations({
    updateMessage: 'updateMessage'
  })
},
watch: {
  message(newVal) {
    this.updateMessage(newVal)
  }
}

Vuex最佳实践

1. 模块化设计:

  • 将应用状态划分为逻辑模块
  • 每个模块负责一个特定领域的状态管理
  • 使用命名空间避免命名冲突

2. 状态设计:

  • 保持state的扁平化,避免嵌套过深
  • 只在state中存储必要的数据,派生数据使用getters
  • 使用常量定义mutation类型
javascript 复制代码
// mutation-types.js
export const MUTATION_TYPES = {
  SET_USER: 'SET_USER',
  SET_TOKEN: 'SET_TOKEN',
  CLEAR_USER: 'CLEAR_USER',
  ADD_PRODUCT_TO_CART: 'ADD_PRODUCT_TO_CART',
  REMOVE_PRODUCT_FROM_CART: 'REMOVE_PRODUCT_FROM_CART'
}

3. 异步处理:

  • 所有异步操作放在actions中
  • 使用async/await处理异步流程
  • 在actions中进行错误处理
javascript 复制代码
actions: {
  async fetchProducts({ commit }, filters = {}) {
    commit('SET_LOADING', true)
    commit('SET_ERROR', null)
    
    try {
      const response = await api.getProducts(filters)
      commit('SET_PRODUCTS', response.data)
      return response.data
    } catch (error) {
      commit('SET_ERROR', error.message)
      throw error
    } finally {
      commit('SET_LOADING', false)
    }
  }
}

4. 组件中使用Vuex:

  • 优先使用辅助函数简化代码
  • 避免在组件中直接修改state,使用mutations
  • 对于大型组件,考虑使用mapState和mapGetters的对象形式
javascript 复制代码
computed: {
  // 使用对象形式,添加注释说明
  ...mapState({
    user: state => state.user.user,  // 当前用户信息
    isLoading: state => state.ui.isLoading  // 加载状态
  }),
  
  // 本地计算属性
  fullName() {
    return this.user ? `${this.user.firstName} ${this.user.lastName}` : ''
  }
}

5. 性能优化:

  • 使用懒加载注册模块
  • 避免在getters中进行计算密集型操作
  • 对于大型状态,考虑按需加载数据
javascript 复制代码
// 懒加载模块
const loadAdminModule = () => {
  if (!store.hasModule('admin')) {
    import('./modules/admin').then(adminModule => {
      store.registerModule('admin', adminModule.default)
    })
  }
}

Vuex与Pinia对比

特性 Vuex Pinia
API设计 基于选项式API 基于组合式API
状态修改 必须通过mutations 直接修改state
TypeScript支持 需要额外配置 原生支持,类型推断更好
模块化 基于modules 每个store是一个模块
调试工具 Vue Devtools Vue Devtools
服务端渲染 支持 支持
代码分割 支持懒加载模块 更简单,直接动态导入
热重载 支持 更简单的热重载
学习曲线 较陡峭 更平缓,更直观

从Vuex迁移到Pinia的基本步骤:

  1. 安装Pinia: npm install pinia
  2. 创建Pinia实例并注册到Vue应用
  3. 将Vuex模块转换为Pinia stores
  4. 更新组件中的状态访问方式
  5. 移除mutations,将逻辑移到actions
  6. 更新插件和中间件
相关推荐
GoogleDocs2 小时前
基于[api-football]数据学习示例
java·学习
李小星同志2 小时前
DPO,PPO,GRPO的学习
人工智能·深度学习·学习
weixin_409383123 小时前
简单四方向a*寻路学习记录7 实现了多个障碍绕行但是绕路
学习
林夕sama3 小时前
MySQL的学习笔记
笔记·学习·mysql
徐某人..4 小时前
网络编程学习--第一天
arm开发·单片机·学习·arm
TL滕4 小时前
从0开始学算法——第十一天(字符串基础算法)
笔记·学习·算法
Hello_Embed4 小时前
FreeRTOS 入门(二十六):队列创建与读写 API 实战解析
笔记·学习·操作系统·嵌入式·freertos
2201_757830874 小时前
JS的学习
前端·javascript·学习
xixixi777775 小时前
CRNN(CNN + RNN + CTC):OCR识别的经典之作
人工智能·rnn·学习·架构·cnn·ocr·图像识别