第一个Vuex Store #

目标 #

创建一个完整的计数器 Store,包含以下功能:

  • 基本计数功能
  • 异步增加功能
  • 派生状态(双倍计数)

创建 Store #

1. 定义状态 #

javascript
// src/store/index.js
import { createStore } from 'vuex'

export default createStore({
  // 定义状态
  state: {
    count: 0,
    user: null,
    loading: false
  }
})

2. 定义变更 #

javascript
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0,
    user: null,
    loading: false
  },
  
  // 定义变更
  mutations: {
    // 增加计数
    INCREMENT(state) {
      state.count++
    },
    
    // 减少计数
    DECREMENT(state) {
      state.count--
    },
    
    // 设置计数
    SET_COUNT(state, value) {
      state.count = value
    },
    
    // 设置用户
    SET_USER(state, user) {
      state.user = user
    },
    
    // 设置加载状态
    SET_LOADING(state, loading) {
      state.loading = loading
    }
  }
})

3. 定义动作 #

javascript
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0,
    user: null,
    loading: false
  },
  
  mutations: {
    INCREMENT(state) {
      state.count++
    },
    DECREMENT(state) {
      state.count--
    },
    SET_COUNT(state, value) {
      state.count = value
    },
    SET_USER(state, user) {
      state.user = user
    },
    SET_LOADING(state, loading) {
      state.loading = loading
    }
  },
  
  // 定义动作
  actions: {
    // 同步增加
    increment({ commit }) {
      commit('INCREMENT')
    },
    
    // 同步减少
    decrement({ commit }) {
      commit('DECREMENT')
    },
    
    // 异步增加
    async incrementAsync({ commit }) {
      commit('SET_LOADING', true)
      
      // 模拟异步操作
      await new Promise(resolve => setTimeout(resolve, 1000))
      
      commit('INCREMENT')
      commit('SET_LOADING', false)
    },
    
    // 获取用户信息
    async fetchUser({ commit }) {
      commit('SET_LOADING', true)
      
      try {
        const response = await fetch('/api/user')
        const user = await response.json()
        commit('SET_USER', user)
      } catch (error) {
        console.error('获取用户失败:', error)
      } finally {
        commit('SET_LOADING', false)
      }
    }
  }
})

4. 定义派生状态 #

javascript
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0,
    user: null,
    loading: false
  },
  
  mutations: {
    INCREMENT(state) {
      state.count++
    },
    DECREMENT(state) {
      state.count--
    },
    SET_COUNT(state, value) {
      state.count = value
    },
    SET_USER(state, user) {
      state.user = user
    },
    SET_LOADING(state, loading) {
      state.loading = loading
    }
  },
  
  actions: {
    increment({ commit }) {
      commit('INCREMENT')
    },
    decrement({ commit }) {
      commit('DECREMENT')
    },
    async incrementAsync({ commit }) {
      commit('SET_LOADING', true)
      await new Promise(resolve => setTimeout(resolve, 1000))
      commit('INCREMENT')
      commit('SET_LOADING', false)
    },
    async fetchUser({ commit }) {
      commit('SET_LOADING', true)
      try {
        const response = await fetch('/api/user')
        const user = await response.json()
        commit('SET_USER', user)
      } catch (error) {
        console.error('获取用户失败:', error)
      } finally {
        commit('SET_LOADING', false)
      }
    }
  },
  
  // 定义派生状态
  getters: {
    // 双倍计数
    doubleCount: state => state.count * 2,
    
    // 计数是否为正数
    isPositive: state => state.count > 0,
    
    // 计数描述
    countDescription: state => {
      if (state.count === 0) return '零'
      if (state.count > 0) return `正数: ${state.count}`
      return `负数: ${state.count}`
    },
    
    // 用户是否登录
    isLoggedIn: state => state.user !== null,
    
    // 用户名
    userName: state => state.user?.name || '游客'
  }
})

在 Vue 中注册 #

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')

在组件中使用 #

方式一:直接访问 #

vue
<template>
  <div>
    <p>Count: {{ $store.state.count }}</p>
    <p>Double: {{ $store.getters.doubleCount }}</p>
    <button @click="$store.commit('INCREMENT')">+1</button>
    <button @click="$store.dispatch('incrementAsync')">
      Async +1
    </button>
  </div>
</template>

方式二:使用辅助函数(推荐) #

vue
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <p>Status: {{ countDescription }}</p>
    <p>User: {{ userName }}</p>
    
    <div v-if="loading">Loading...</div>
    
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <button @click="incrementAsync" :disabled="loading">
      Async +1
    </button>
  </div>
</template>

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

export default {
  computed: {
    // 映射状态
    ...mapState(['count', 'loading']),
    
    // 映射派生状态
    ...mapGetters([
      'doubleCount',
      'countDescription',
      'userName'
    ])
  },
  
  methods: {
    // 映射动作
    ...mapActions([
      'increment',
      'decrement',
      'incrementAsync'
    ])
  }
}
</script>

方式三:组合式 API(Vue 3) #

vue
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()
    
    return {
      count: computed(() => store.state.count),
      doubleCount: computed(() => store.getters.doubleCount),
      increment: () => store.commit('INCREMENT')
    }
  }
}
</script>

完整示例 #

Store 完整代码 #

javascript
// src/store/index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0,
    user: null,
    loading: false,
    error: null
  },
  
  mutations: {
    INCREMENT(state) {
      state.count++
    },
    
    DECREMENT(state) {
      state.count--
    },
    
    SET_COUNT(state, value) {
      state.count = value
    },
    
    SET_USER(state, user) {
      state.user = user
    },
    
    SET_LOADING(state, loading) {
      state.loading = loading
    },
    
    SET_ERROR(state, error) {
      state.error = error
    },
    
    CLEAR_ERROR(state) {
      state.error = null
    }
  },
  
  actions: {
    increment({ commit }) {
      commit('INCREMENT')
    },
    
    decrement({ commit }) {
      commit('DECREMENT')
    },
    
    async incrementAsync({ commit }) {
      commit('SET_LOADING', true)
      commit('CLEAR_ERROR')
      
      try {
        await new Promise(resolve => setTimeout(resolve, 1000))
        commit('INCREMENT')
      } catch (error) {
        commit('SET_ERROR', error.message)
      } finally {
        commit('SET_LOADING', false)
      }
    },
    
    async fetchUser({ commit }) {
      commit('SET_LOADING', true)
      commit('CLEAR_ERROR')
      
      try {
        const response = await fetch('/api/user')
        if (!response.ok) throw new Error('获取用户失败')
        const user = await response.json()
        commit('SET_USER', user)
      } catch (error) {
        commit('SET_ERROR', error.message)
      } finally {
        commit('SET_LOADING', false)
      }
    },
    
    async login({ commit }, credentials) {
      commit('SET_LOADING', true)
      commit('CLEAR_ERROR')
      
      try {
        const response = await fetch('/api/login', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(credentials)
        })
        
        if (!response.ok) throw new Error('登录失败')
        const user = await response.json()
        commit('SET_USER', user)
      } catch (error) {
        commit('SET_ERROR', error.message)
      } finally {
        commit('SET_LOADING', false)
      }
    },
    
    logout({ commit }) {
      commit('SET_USER', null)
    }
  },
  
  getters: {
    doubleCount: state => state.count * 2,
    isPositive: state => state.count > 0,
    countDescription: state => {
      if (state.count === 0) return '零'
      if (state.count > 0) return `正数: ${state.count}`
      return `负数: ${state.count}`
    },
    isLoggedIn: state => state.user !== null,
    userName: state => state.user?.name || '游客',
    hasError: state => state.error !== null
  }
})

组件完整代码 #

vue
<!-- src/components/Counter.vue -->
<template>
  <div class="counter">
    <h2>计数器示例</h2>
    
    <div class="display">
      <p class="count">Count: {{ count }}</p>
      <p class="double">Double: {{ doubleCount }}</p>
      <p class="status">{{ countDescription }}</p>
    </div>
    
    <div v-if="loading" class="loading">
      Loading...
    </div>
    
    <div v-if="hasError" class="error">
      {{ error }}
    </div>
    
    <div class="buttons">
      <button @click="decrement" :disabled="loading">-1</button>
      <button @click="increment" :disabled="loading">+1</button>
      <button @click="incrementAsync" :disabled="loading">
        Async +1
      </button>
    </div>
  </div>
</template>

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

export default {
  name: 'Counter',
  
  computed: {
    ...mapState(['count', 'loading', 'error']),
    ...mapGetters([
      'doubleCount',
      'countDescription',
      'hasError'
    ])
  },
  
  methods: {
    ...mapActions([
      'increment',
      'decrement',
      'incrementAsync'
    ])
  }
}
</script>

<style scoped>
.counter {
  max-width: 400px;
  margin: 0 auto;
  padding: 20px;
  text-align: center;
}

.display {
  margin: 20px 0;
}

.count {
  font-size: 24px;
  font-weight: bold;
}

.buttons {
  display: flex;
  gap: 10px;
  justify-content: center;
}

button {
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.loading {
  color: blue;
  margin: 10px 0;
}

.error {
  color: red;
  margin: 10px 0;
}
</style>

调试技巧 #

使用 Vue DevTools #

  1. 安装 Vue DevTools 浏览器扩展
  2. 打开开发者工具,切换到 Vue 标签
  3. 选择 Vuex 选项卡

功能说明 #

text
Vue DevTools Vuex 功能
├── State ──────── 查看当前状态
├── Getters ────── 查看派生状态
├── Mutations ──── 查看变更历史
├── Time Travel ── 回到任意历史状态
└── Export ─────── 导出状态快照

总结 #

创建一个 Vuex Store 的步骤:

  1. 定义 State - 存储应用数据
  2. 定义 Mutations - 同步修改状态
  3. 定义 Actions - 处理异步操作
  4. 定义 Getters - 计算派生状态
  5. 注册 Store - 在 Vue 应用中注册
  6. 使用 Store - 在组件中访问和修改

继续学习 State状态,深入了解状态管理。

最后更新:2026-03-28