Membuat Vuex Undo / Redo Plugin untuk VueJS

gambar


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({ // Code goes here }); } }; 

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 = []; // 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 

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:


  1. Gunakan metode larik pop untuk menghapus mutasi terakhir.
  2. Hapus status penyimpanan menggunakan mutasi khusus EMPTY_STATE (dijelaskan di bawah)
  3. 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; } }, }); }, } 

Source: https://habr.com/ru/post/id433986/


All Articles