
Ada banyak keuntungan untuk memusatkan status aplikasi Anda di toko Vuex. Satu keuntungan adalah bahwa semua transaksi dicatat. Ini memungkinkan Anda untuk menggunakan fungsi yang mudah digunakan, seperti debugging runtime , di mana Anda dapat beralih di antara status sebelumnya untuk memisahkan tugas dari eksekusi.
Pada artikel ini, saya akan menunjukkan cara membuat fungsi Undo / Redo Rollback / Return lanjut menggunakan Vuex, yang berfungsi mirip dengan debugging selama debugging. Fitur ini dapat digunakan dalam berbagai skenario, dari bentuk kompleks hingga game berbasis browser.
Anda dapat memeriksa kode yang sudah selesai di sini di Github dan mencoba demo di Codepen ini. Saya juga membuat plugin sebagai modul NPM yang disebut vuex-undo-redo jika Anda ingin menggunakannya dalam suatu proyek.
Catatan: artikel ini awalnya diposting di sini di blog pengembang Vue.js 2017/11/13.
Konfigurasi plugin
Untuk membuat fitur ini dapat digunakan kembali, kami akan membuatnya sebagai plugin Vue. Fungsi ini mengharuskan kami untuk menambahkan beberapa metode dan data ke instance Vue, jadi kami menyusun plugin sebagai mixin.
module.exports = { install(Vue) { Vue.mixin({
Untuk menggunakannya dalam suatu proyek, kita cukup mengimpor plugin dan menghubungkannya:
import VuexUndoRedo from './plugin.js'; Vue.use(VuexUndoRedo);
Ide
Fitur ini akan bekerja dengan memutar kembali mutasi terakhir jika pengguna ingin membatalkan, dan kemudian menerapkannya kembali jika ia ingin mengulanginya. Bagaimana kita melakukan ini?
Pendekatan No. 1
Pendekatan pertama yang mungkin adalah mengambil "snapshots" dari keadaan repositori setelah setiap mutasi dan menempatkan snapshot dalam array. Untuk membatalkan / mengulang, kita bisa mendapatkan snapshot yang benar dan menggantinya dengan kondisi penyimpanan.
Masalah dengan pendekatan ini adalah bahwa keadaan repositori adalah objek JavaScript. Saat Anda meletakkan objek JavaScript dalam array, Anda cukup meletakkan referensi ke objek tersebut. Implementasi naif, seperti berikut, tidak akan berfungsi:
var state = { ... }; var snapshot = [];
Pendekatan snapshot akan mengharuskan Anda pertama kali membuat klon keadaan sebelum mendorong. Mengingat bahwa keadaan Vue menjadi reaktif dengan secara otomatis menambahkan fungsi get and set, itu tidak berfungsi dengan baik dengan kloning.
Pendekatan No. 2
Pendekatan lain yang mungkin adalah mendaftarkan setiap mutasi tetap. Untuk membatalkan, kami mengatur ulang toko ke keadaan awal dan kemudian menjalankan mutasi lagi; semua kecuali yang terakhir. Pengembalian konsep serupa.
Dengan prinsip-prinsip Flux, memulai kembali mutasi dari kondisi awal yang sama idealnya membuat kembali keadaan. Karena ini adalah pendekatan yang lebih bersih daripada yang pertama, mari kita lanjutkan.
Pendaftaran mutasi
Vuex menawarkan metode API untuk berlangganan mutasi, yang dapat kita gunakan untuk mendaftarkannya. Kami akan mengatur ini ke hook yang created
. Dalam panggilan balik, kita cukup meletakkan mutasi dalam array yang nantinya dapat dijalankan kembali.
Vue.mixin({ data() { return { done: [] } }, created() { this.$store.subscribe(mutation => { this.done.push(mutation); } } });
Metode rollback
Untuk membatalkan mutasi, kami menghapus repositori dan kemudian menjalankan kembali semua mutasi kecuali yang terakhir. Berikut cara kerjanya:
- Gunakan metode larik
pop
untuk menghapus mutasi terakhir. - Hapus status penyimpanan menggunakan mutasi khusus
EMPTY_STATE
(dijelaskan di bawah) - Ulangi setiap mutasi yang tersisa, perbaiki lagi di toko baru. Harap dicatat bahwa metode berlangganan masih aktif selama proses ini, yaitu, setiap mutasi akan ditambahkan lagi. Hapus segera dengan
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(); }); } } });
Toko pembersih
Setiap kali plugin ini digunakan, pengembang harus mengimplementasikan mutasi dalam repositori yang disebut emptyState. Tantangannya adalah mengembalikan toko ke keadaan semula sehingga siap untuk pemulihan dari awal.
Pengembang harus melakukan ini sendiri, karena plugin yang kami buat tidak memiliki akses ke toko, hanya ke instance Vue. Berikut ini contoh implementasi:
new Vuex.Store({ state: { myVal: null }, mutations: { emptyState() { this.replaceState({ myval: null }); } } });
Kembali ke plugin kami, mutasi emptyState
tidak boleh ditambahkan ke daftar kami yang sudah done
, karena kami tidak ingin komit kembali selama proses rollback. Cegah ini dengan logika berikut:
Vue.mixin({ data() { ... }, created() { this.$store.subscribe(mutation => { if (mutation.type !== EMPTY_STATE) { this.done.push(mutation); } }); }, methods() { ... } });
Metode pengembalian
Mari kita buat properti data baru, undone
yang akan menjadi array. Saat kami menghapus mutasi terakhir yang done
selama proses rollback, kami memasukkannya ke dalam array ini:
Vue.mixin({ data() { return { done: [], undone: [] } }, methods: { undo() { this.undone.push(this.done.pop()); ... } } });
Sekarang kita dapat membuat metode redo
yang hanya akan mengambil mutasi undone
terakhir ditambahkan dan undone
kembali.
methods: { undo() { ... }, redo() { let commit = this.undone.pop(); this.$store.commit(`${commit.type}`, commit.payload); } }
Tidak mungkin kembali
Jika pengguna memulai pembatalan satu kali atau lebih dan kemudian membuat komit baru, konten yang undone
akan dibatalkan. Jika ini terjadi, kita harus mengosongkan yang undone
.
Kami dapat menemukan komitmen baru dari panggilan balik berlangganan kami saat menambahkan komit. Namun, logikanya rumit, karena callback tidak memiliki cara yang jelas untuk mengetahui apa komit baru dan apa yang dibatalkan / diulang.
Pendekatan termudah adalah dengan mengatur benderaMutasi baru. Itu akan benar secara default, tetapi rollback dan kembali metode sementara akan mengaturnya ke false. Jika mutasi disetel ke true, subscribe
panggilan balik akan menghapus larik yang 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; } } }); }, }
Fungsi utama sekarang lengkap! Tambahkan plugin ke proyek Anda sendiri atau ke demo saya untuk mengujinya.
API publik
Dalam demo saya, Anda akan melihat bahwa tombol batal dan kembali dinonaktifkan jika fungsinya saat ini tidak memungkinkan. Misalnya, jika belum ada komitmen, Anda jelas tidak dapat membatalkan atau mengulang. Pengembang yang menggunakan plugin ini mungkin ingin menerapkan fungsi serupa.
Untuk mengaktifkan ini, plugin dapat menyediakan dua properti yang dihitung, canUndo
dan canRedo
sebagai bagian dari API publik. Ini sepele untuk diterapkan:
module.exports = { install(Vue) { Vue.mixin({ data() { ... }, created() { ... }, methods: { ... }, computed: {}, computed: { canRedo() { return this.undone.length; }, canUndo() { return this.done.length; } }, }); }, }