- PVSM.RU - https://www.pvsm.ru -
Есть много преимуществ для централизации состояния вашего приложения в Vuex store. Одним из преимуществ является то, что все транзакции записываются. Это позволяет использовать удобные функции, такие как отладка по времени выполнения, где вы можете переключаться между предыдущими состояниями, чтобы отделять задачи выполнения.
В этой статье я покажу, как создать функцию Undo/Redo далее Отката/Возврата с помощью Vuex, которая работает аналогично отладке во время дебага. Эта функция может использоваться в различных сценариях, от сложных форм до игр на основе браузера.
Вы можете проверить готовый код [1] здесь, на Github [2], и попробовать демо в этом Codepen [1]. Я также создал плагин как модуль NPM под названием vuex-undo-redo [2], если вы хотите использовать его в проекте.
Примечание: эта статья была первоначально размещена здесь, в блоге разработчиков Vue.js [3] 2017/11/13
Чтобы сделать эту функцию многократно используемой, мы создадим ее как плагин Vue. Эта функция требует от нас добавления некоторых методов и данных в экземпляр Vue, поэтому мы структурируем плагин как миксин.
module.exports = {
install(Vue) {
Vue.mixin({
// Code goes here
});
}
};
Чтобы использовать его в проекте, мы можем просто импортировать плагин и подключить его:
import VuexUndoRedo from './plugin.js';
Vue.use(VuexUndoRedo);
Работа фичи будет заключаться в откате последней мутации, если пользователь хочет отменить, а затем повторно применить ее, если он хочет повторить. Как мы это осуществим?
Первый возможный подход заключается в том, чтобы делать «снимки» состояние хранилища после каждой мутации и помещать снимок в массив. Чтобы отменить/повторить, мы можем получить правильный снимок и заменить его состоянием хранилища.
Проблема с этим подходом состоит в том, что состояние хранилища является объектом JavaScript. Когда вы помещаете объект JavaScript в массив, вы просто помещаете ссылку на объект. Наивная реализация, как и следующая, не будет работать:
var state = { ... };
var snapshot = [];
// Push the first state
snapshot.push(state);
// Push the second state
state.val = "new val";
snapshot.push(state);
// Both snapshots are simply a reference to state
console.log(snapshot[0] === snapshot[1]); // true
Подход моментального снимка потребует, чтобы вы сначала сделали клон состояния перед push. Учитывая, что состояние Vue становится реактивным благодаря автоматическому добавлению функций get и set, оно не очень хорошо работает с клонированием.
Другой возможный подход заключается в регистрации каждой зафиксированной мутации. Чтобы отменить, мы сбрасываем хранилище в его начальное состояние и затем снова запускаем мутации; все кроме последней. Возврат аналогичная концепция.
Учитывая принципы Flux, повторный запуск мутаций из одного и того же начального состояния должен идеально воссоздать состояние. Поскольку это более чистый подход, чем первый, давайте продолжим.
Vuex предлагает метод API для подписки на мутации, который мы можем использовать для их регистрации. Мы установим это на хук created
. В обратном вызове мы просто помещаем мутацию в массив, который позже можно будет повторно запустить.
Vue.mixin({
data() {
return {
done: []
}
},
created() {
this.$store.subscribe(mutation => {
this.done.push(mutation);
}
}
});
Чтобы отменить мутацию, мы очистим хранилище, а затем повторно запустим все мутации, кроме последней. Вот как работает код:
pop
метод массива, чтобы удалить последнюю мутациюEMPTY_STATE
(объяснено ниже)pop
.const EMPTY_STATE = 'emptyState';
Vue.mixin({
data() { ... },
created() { ... },
methods() {
undo() {
this.done.pop();
this.$store.commit(EMPTY_STATE);
this.done.forEach(mutation => {
this.$store.commit(`${mutation.type}`, mutation.payload);
this.done.pop();
});
}
}
});
Всякий раз, когда этот плагин используется, разработчик должен реализовать мутацию в своем хранилище под названием emptyState. Задача состоит в том, чтобы вернуть store обратно в исходное состояние, чтобы он был готов к восстановлению с нуля.
Разработчик должен сделать это самостоятельно, потому что плагин, который мы создаем, не имеет доступа к store, только к экземпляру Vue. Вот пример реализации:
new Vuex.Store({
state: {
myVal: null
},
mutations: {
emptyState() {
this.replaceState({ myval: null });
}
}
});
Возвращаясь к нашему плагину, emptyState
мутация не должна быть добавлена в наш список done
, так как мы не хотим повторно фиксировать это в процессе отката. Предотвратим это с помощью следующей логики:
Vue.mixin({
data() { ... },
created() {
this.$store.subscribe(mutation => {
if (mutation.type !== EMPTY_STATE) {
this.done.push(mutation);
}
});
},
methods() { ... }
});
Давайте создадим новое свойство данных, undone
которое будет массивом. Когда мы удаляем последнюю мутацию из done
в процессе отката, мы помещаем ее в этот массив:
Vue.mixin({
data() {
return {
done: [],
undone: []
}
},
methods: {
undo() {
this.undone.push(this.done.pop());
...
}
}
});
Теперь мы можем создать redo
метод, который будет просто брать последнюю добавленную мутацию undone
и повторно фиксировать ее.
methods: {
undo() { ... },
redo() {
let commit = this.undone.pop();
this.$store.commit(`${commit.type}`, commit.payload);
}
}
Если пользователь инициирует отмену один или несколько раз, а затем делает новую новую фиксацию, содержимое undone
будет признано недействительным. Если это произойдет, мы должны опустошить undone
.
Мы можем обнаружить новые коммиты из нашего обратного вызова подписки при добавлении коммита. Однако логика хитрая, так как обратный вызов не имеет никакого очевидного способа узнать, что такое новый коммит и что такое отмена / повтор.
Самый простой подход — установить флаг newMutation. Он будет true по умолчанию, но методы отката и возврата временно установят для него значение false. Если при фиксации мутации установлено значение true, обратный вызов subscribe
очистит массив undone
.
module.exports = {
install(Vue) {
Vue.mixin({
data() {
return {
done: [],
undone: [],
newMutation: true
};
},
created() {
this.$store.subscribe(mutation => {
if (mutation.type !== EMPTY_STATE) {
this.done.push(mutation);
}
if (this.newMutation) {
this.undone = [];
}
});
},
methods: {
redo() {
let commit = this.undone.pop();
this.newMutation = false;
this.$store.commit(`${commit.type}`, commit.payload);
this.newMutation = true;
},
undo() {
this.undone.push(this.done.pop());
this.newMutation = false;
this.$store.commit(EMPTY_STATE);
this.done.forEach(mutation => {
this.$store.commit(`${mutation.type}`, mutation.payload);
this.done.pop();
});
this.newMutation = true;
}
}
});
},
}
Основной функционал теперь завершен! Добавьте плагин в свой собственный проект или в мою демку [4], чтобы протестировать его.
В моей демонстрации вы заметите, что кнопки отмены и возврата отключены, если их функциональность в данное время не возможна. Например, если еще не было коммитов, вы, очевидно, не можете отменить или повторить. Разработчик, использующий этот плагин, может захотеть реализовать подобную функциональность.
Чтобы разрешить это, плагин может предоставлять два вычисляемых свойства canUndo
и canRedo
как часть публичного API. Это тривиально для реализации:
module.exports = {
install(Vue) {
Vue.mixin({
data() { ... },
created() { ... },
methods: { ... },
computed: {},
computed: {
canRedo() {
return this.undone.length;
},
canUndo() {
return this.done.length;
}
},
});
},
}
Автор: bad4iz
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/303067
Ссылки в тексте:
[1] готовый код: https://codepen.io/anthonygore/pen/NwGmqJ
[2] на Github: https://github.com/anthonygore/vuex-undo-redo
[3] блоге разработчиков Vue.js: https://vuejsdevelopers.com/2017/11/13/vue-js-vuex-undo-redo/?utm_source=medium-vjd&utm_medium=article&utm_campaign=vur
[4] мою демку: https://github.com/anthonygore/vuex-undo-redo-example
[5] Источник: https://habr.com/post/433986/?utm_campaign=433986
Нажмите здесь для печати.