Skip to content
On this page

状态管理

为什么需要状态管理

在复杂的Vue.js应用中,组件之间的状态管理变得复杂。当多个组件需要共享和修改相同的状态时,组件间通信变得困难,数据流变得难以追踪。状态管理提供了一个中央存储来管理所有组件的状态。

简单的状态管理

对于简单的情况,可以使用响应式数据和provide/inject:

vue
<!-- store.js -->
import { reactive, readonly } from 'vue'

// 创建共享状态
const state = reactive({
  count: 0,
  user: null,
  todos: []
})

// 创建修改状态的方法
const mutations = {
  increment() {
    state.count++
  },
  decrement() {
    state.count--
  },
  setUser(user) {
    state.user = user
  },
  addTodo(todo) {
    state.todos.push({ id: Date.now(), ...todo })
  },
  removeTodo(id) {
    state.todos = state.todos.filter(todo => todo.id !== id)
  }
}

// 导出状态和方法
export const store = {
  state: readonly(state), // 只读状态
  ...mutations
}
vue
<!-- 在组件中使用 -->
<script setup>
import { store } from './store'

function incrementCount() {
  store.increment()
}

function addNewTodo() {
  store.addTodo({ text: 'New todo', completed: false })
}
</script>

<template>
  <div>
    <p>Count: {{ store.state.count }}</p>
    <p>User: {{ store.state.user?.name || 'Not logged in' }}</p>
    <button @click="incrementCount">Increment</button>
    <ul>
      <li v-for="todo in store.state.todos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>
</template>

Vuex 状态管理

Vuex是Vue.js的官方状态管理库,适用于大型应用。

Vuex 基础结构

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

export default createStore({
  // 状态
  state() {
    return {
      count: 0,
      user: null,
      todos: []
    }
  },
  
  // 计算属性
  getters: {
    doubleCount: state => state.count * 2,
    completedTodos: state => state.todos.filter(todo => todo.completed),
    userHasTodos: (state, getters) => {
      return state.user && getters.todosCount > 0
    },
    todosCount: state => state.todos.length
  },
  
  // 同步修改状态的方法
  mutations: {
    increment(state) {
      state.count++
    },
    decrement(state) {
      state.count--
    },
    setUser(state, user) {
      state.user = user
    },
    setTodos(state, todos) {
      state.todos = todos
    },
    addTodo(state, todo) {
      state.todos.push({
        id: Date.now(),
        ...todo,
        completed: false
      })
    },
    toggleTodo(state, id) {
      const todo = state.todos.find(todo => todo.id === id)
      if (todo) {
        todo.completed = !todo.completed
      }
    },
    removeTodo(state, id) {
      const index = state.todos.findIndex(todo => todo.id === id)
      if (index !== -1) {
        state.todos.splice(index, 1)
      }
    }
  },
  
  // 异步操作
  actions: {
    async fetchUser({ commit }, userId) {
      try {
        const response = await fetch(`/api/users/${userId}`)
        const user = await response.json()
        commit('setUser', user)
      } catch (error) {
        console.error('Failed to fetch user:', error)
      }
    },
    
    async fetchTodos({ commit }) {
      try {
        const response = await fetch('/api/todos')
        const todos = await response.json()
        commit('setTodos', todos)
      } catch (error) {
        console.error('Failed to fetch todos:', error)
      }
    },
    
    async addTodo({ commit }, todoText) {
      try {
        const response = await fetch('/api/todos', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ text: todoText })
        })
        const todo = await response.json()
        commit('addTodo', todo)
      } catch (error) {
        console.error('Failed to add todo:', error)
      }
    }
  }
})

在组件中使用Vuex

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

const store = useStore()

// 使用计算属性访问状态
const count = computed(() => store.state.count)
const doubleCount = computed(() => store.getters.doubleCount)
const todos = computed(() => store.state.todos)

// 方法
function increment() {
  store.commit('increment')
}

function addTodo(text) {
  store.dispatch('addTodo', text)
}

function toggleTodo(id) {
  store.commit('toggleTodo', id)
}
</script>

<template>
  <div>
    <h2>Count: {{ count }} (Double: {{ doubleCount }})</h2>
    <button @click="increment">Increment</button>
    
    <div class="todos">
      <h3>Todos</h3>
      <ul>
        <li v-for="todo in todos" :key="todo.id">
          <input 
            type="checkbox" 
            :checked="todo.completed"
            @change="toggleTodo(todo.id)"
          >
          <span :class="{ completed: todo.completed }">{{ todo.text }}</span>
        </li>
      </ul>
    </div>
  </div>
</template>

<style>
.completed {
  text-decoration: line-through;
  color: #999;
}
</style>
</template>