← Назад к вопросам

В чём разница между Action и Mutation?

2.2 Middle🔥 181 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Action vs Mutation в Vuex

Это концепция из Vuex (state management для Vue.js). Разница критична для правильной архитектуры приложения.

Основное различие

Mutation — синхронно изменяет state (как setter) Action — асинхронно выполняет логику и коммитит mutations

const store = new Vuex.Store({
  state: {
    count: 0,
    user: null
  },

  // MUTATIONS — синхронные, изменяют state напрямую
  mutations: {
    incrementCount(state, payload) {
      state.count += payload; // Синхронно
    },

    setUser(state, user) {
      state.user = user;      // Синхронно
    }
  },

  // ACTIONS — асинхронные, коммитят mutations
  actions: {
    async fetchUser({ commit }, userId) {
      const user = await api.getUser(userId); // Асинхронно
      commit('setUser', user);                 // Коммитит mutation
    },

    incrementByAsync({ commit }, amount) {
      setTimeout(() => {
        commit('incrementCount', amount); // Асинхронно коммитит
      }, 1000);
    }
  }
});

Правило ВАЖНОЕ

Mutations НИКОГДА не должны содержать асинхронный код

// НЕПРАВИЛЬНО (anti-pattern)
mutations: {
  fetchUser(state, userId) {
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(user => {
        state.user = user; // Асинхронно?
      });
  }
}

// ПРАВИЛЬНО
mutations: {
  setUser(state, user) {
    state.user = user; // Только синхронно
  }
}

actions: {
  async fetchUser({ commit }, userId) {
    const user = await fetch(`/api/users/${userId}`).then(r => r.json());
    commit('setUser', user); // Коммитим mutation
  }
}

Почему это важно?

1. Отладка с Vue DevTools

// Mutation с асинхронностью = невозможно отследить когда изменился state
mutations: {
  badMutation(state) {
    setTimeout(() => state.count++, 1000); // ЛОВУШКА!
  }
}

// Правильно: все синхронные изменения видны в DevTools
mutations: {
  incrementCount(state) {
    state.count++;
  }
}

actions: {
  async incrementLater({ commit }) {
    await delay(1000);
    commit('incrementCount'); // Видно в DevTools когда commitment произойдёт
  }
}

2. Контроль состояния (state management)

Mutations должны быть:

  • Синхронными
  • Атомарными (одна mutation = одно изменение state)
  • Отслеживаемыми
  • Предсказуемыми

Actions могут быть:

  • Асинхронными
  • Сложными (несколько операций)
  • Содержать side effects (API запросы, логирование)

Практический пример

const store = new Vuex.Store({
  state: {
    posts: [],
    loading: false,
    error: null
  },

  mutations: {
    setLoading(state, isLoading) {
      state.loading = isLoading;
    },

    setPosts(state, posts) {
      state.posts = posts;
    },

    setError(state, error) {
      state.error = error;
    }
  },

  actions: {
    async loadPosts({ commit }) {
      commit('setLoading', true);
      commit('setError', null);

      try {
        const response = await fetch('/api/posts');
        const posts = await response.json();
        commit('setPosts', posts);
      } catch (error) {
        commit('setError', error.message);
      } finally {
        commit('setLoading', false);
      }
    },

    async addPost({ commit, state }, newPost) {
      const response = await fetch('/api/posts', {
        method: 'POST',
        body: JSON.stringify(newPost)
      });
      const post = await response.json();
      
      // Изменяем state через mutation
      commit('setPosts', [...state.posts, post]);
    },

    deletePost({ commit, state }, postId) {
      // Асинхронный запрос
      return fetch(`/api/posts/${postId}`, { method: 'DELETE' })
        .then(() => {
          // Коммитим mutation ПОСЛЕ успеха
          commit('setPosts', state.posts.filter(p => p.id !== postId));
        });
    }
  }
});

Использование в компоненте

import { useStore } from 'vuex';

export default {
  setup() {
    const store = useStore();

    // Диспетчим action (асинхронно)
    const loadPosts = async () => {
      await store.dispatch('loadPosts');
    };

    // Напрямую читаем state
    const posts = computed(() => store.state.posts);
    const loading = computed(() => store.state.loading);
    const error = computed(() => store.state.error);

    return { loadPosts, posts, loading, error };
  }
};

В контексте Redux (React)

В Redux концепция похожа:

// Redux reducer (аналог mutation)
const userReducer = (state, action) => {
  switch (action.type) {
    case 'SET_USER':
      return { ...state, user: action.payload }; // Синхронно
    default:
      return state;
  }
};

// Redux action creator (аналог action)
const fetchUser = (userId) => async (dispatch) => {
  const user = await api.getUser(userId); // Асинхронно
  dispatch({ type: 'SET_USER', payload: user }); // Диспетчим
};

Видимая часть (Vue 3 Pinia — новый подход)

В современной Vue используют Pinia вместо Vuex:

import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useUserStore = defineStore('user', () => {
  const user = ref(null);
  const loading = ref(false);

  // Асинхронная функция (аналог action)
  const fetchUser = async (userId) => {
    loading.value = true;
    try {
      user.value = await api.getUser(userId);
    } finally {
      loading.value = false;
    }
  };

  // Синхронная функция (аналог mutation)
  const setUser = (newUser) => {
    user.value = newUser;
  };

  return { user, loading, fetchUser, setUser };
});

Практический результат тот же: асинхронность в actions/async functions, синхронность в mutations/sync functions.

Главное правило

Mutations = изменение state (синхронно) Actions = логика и асинхронность, коммитят mutations

Это делает state management предсказуемым и отлаживаемым.

В чём разница между Action и Mutation? | PrepBro