В чём разница между Action и Mutation?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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 предсказуемым и отлаживаемым.