pushState membuat entri riwayat duplikat dan menimpa yang sebelumnya

15

Saya telah membuat aplikasi web yang menggunakan history pushStatedanreplaceState metode untuk menavigasi halaman sambil memperbarui sejarah.

Script itu sendiri hampir berfungsi dengan sempurna; itu akan memuat halaman dengan benar, dan membuang kesalahan halaman ketika mereka perlu dilempar. Namun, saya perhatikan ada masalah aneh di mana pushStateakan mendorong banyak entri, duplikat (dan mengganti entri sebelumnya) ke riwayat.

Sebagai contoh, katakanlah saya melakukan hal berikut (secara berurutan):

  1. Muat index.php (sejarah akan menjadi: Indeks)

  2. Navigasikan ke profile.php (riwayatnya adalah: Profil, Indeks)

  3. Arahkan ke search.php (riwayatnya adalah: Cari, Cari, Indeks)

  4. Arahkan ke dashboard.php

Lalu akhirnya, inilah yang akan muncul dalam sejarah saya (dalam urutan terbaru hingga tertua):

Dashboard
Dashboard
Dashboard
Cari
Indeks

Masalahnya adalah ketika pengguna mengklik tombol maju atau mundur, mereka akan diarahkan ke halaman yang salah, atau harus mengklik beberapa kali untuk kembali sekali. Itu, dan tidak masuk akal jika mereka pergi dan memeriksa sejarah mereka.

Inilah yang saya miliki sejauh ini:

var Traveller = function(){
    this._initialised = false;

    this._pageData = null;
    this._pageRequest = null;

    this._history = [];
    this._currentPath = null;
    this.abort = function(){
        if(this._pageRequest){
            this._pageRequest.abort();
        }
    };
    // initialise traveller (call replaceState on load instead of pushState)
    return this.init();
};

/*1*/Traveller.prototype.init = function(){
    // get full pathname and request the relevant page to load up
    this._initialLoadPath = (window.location.pathname + window.location.search);
    this.send(this._initialLoadPath);
};
/*2*/Traveller.prototype.send = function(path){
    this._currentPath = path.replace(/^\/+|\/+$/g, "");

    // abort any running requests to prevent multiple
    // pages from being loaded into the DOM
    this.abort();

    return this._pageRequest = _ajax({
        url: path,
        dataType: "json",
        success: function(response){
            // render the page to the dom using the json data returned
            // (this part has been skipped in the render method as it
            // doesn't involve manipulating the history object at all
            window.Traveller.render(response);
        }
    });
};
/*3*/Traveller.prototype.render = function(data){
    this._pageData = data;
    this.updateHistory();
};
/*4*/Traveller.prototype.updateHistory = function(){
    /* example _pageData would be:
    {
        "page": {
            "title": "This is a title",
            "styles": [ "stylea.css", "styleb.css" ],
            "scripts": [ "scripta.js", "scriptb.js" ]
        }
    }
    */
    var state = this._pageData;
    if(!this._initialised){
        window.history.replaceState(state, state.title, "/" + this._currentPath);
        this._initialised = true;
    } else {
        window.history.pushState(state, state.title, "/" + this._currentPath);  
    }
    document.title = state.title;
};

Traveller.prototype.redirect = function(href){
    this.send(href);
};

// initialise traveller
window.Traveller = new Traveller();

document.addEventListener("click", function(event){
    if(event.target.tagName === "a"){
        var link = event.target;
        if(link.target !== "_blank" && link.href !== "#"){
            event.preventDefault();
            // example link would be /profile.php
            window.Traveller.redirect(link.href);
        }
    }
});

Semua bantuan dihargai,
Ceria.

GROVER.
sumber
Jadi, Anda hanya ingin menyebarkan perubahan riwayat jika target berbeda dari entri terakhir?
Aluan Haddad
Apakah ini terjadi secara acak? Atau apakah halaman tertentu selalu yang menyebabkan entri riwayat duplikat.
SamVK
@AluanHaddad tidak, saya hanya ingin menyebarkan perubahan riwayat jika ada perubahan riwayat (mis. Ketika pengguna menavigasi melalui situs). Mirip dengan apa yang akan terjadi dalam situasi normal ... (mulai dari indeks ke profil untuk mencari di StackOverflow akan mengembalikan persis seperti itu dalam riwayat saya)
GROVER.
@SamVK Tidak masalah halaman apa, setelah menavigasi dari halaman pemuatan awal (mis. Index.php), entri akan menggandakan diri.
GROVER.
1
Yah, saya tidak begitu yakin tentang itu sehingga menulis komentar. Saya melihat bahwa Anda menambahkan hal-hal riwayat browser di updateHistoryfungsi Anda . Sekarang, updateHistorymungkin dipanggil dua kali, 1, ketika Anda menginisialisasi Traveler ( window.Traveller = new Traveller();, constructor-> init-> send-> render-> updateHistory), kemudian juga oleh redirectdari clickeventListener. Saya belum mengujinya, hanya menebak-nebak, jadi menambahkannya sebagai komentar dan bukan jawaban.
Akshit Arora

Jawaban:

3

Apakah Anda memiliki pengendali onpopstate ?

Jika ya, periksa juga di sana jika Anda tidak mendorong ke sejarah. Bahwa beberapa entri dihapus / diganti dalam daftar riwayat mungkin merupakan pertanda besar. Memang, lihat jawaban SO ini :

Ingatlah bahwa history.pushState () menetapkan status baru sebagai status riwayat terbaru. Dan window.onpopstate dipanggil saat menavigasi (mundur / maju) antara status yang telah Anda atur.

Jadi jangan pushState ketika window.onpopstate dipanggil, karena ini akan menetapkan negara baru sebagai negara terakhir dan kemudian tidak ada yang maju ke depan.

Saya pernah memiliki masalah yang sama persis seperti yang Anda gambarkan, tetapi sebenarnya disebabkan oleh saya bolak-balik untuk mencoba memahami bug, yang pada akhirnya akan memicu popState handler. Dari pengendali itu, ia akan memanggil history.push. Jadi pada akhirnya, saya juga memiliki beberapa entri yang digandakan, dan beberapa hilang, tanpa penjelasan logis.

Saya menghapus panggilan ke history.push, menggantinya dengan history.replace setelah memeriksa beberapa kondisi, dan setelah itu berfungsi seperti pesona :)

EDIT -> TIP

Jika Anda tidak dapat menemukan kode mana yang memanggil history.pushState:

Coba timpa fungsi history.pushState dan gantiState dengan kode berikut:

window.pushStateOriginal = window.history.pushState.bind(window.history);
window.history.pushState = function () {
    var args = Array.prototype.slice.call(arguments, 0);
    let allowPush  = true;
    debugger;
    if (allowPush ) {
        window.pushStateOriginal(...args);
    }
}
//the same for replaceState
window.replaceStateOriginal = window.history.replaceState.bind(window.history);
window.history.replaceState = function () {
    var args = Array.prototype.slice.call(arguments, 0);
    let allowReplace  = true;
    debugger;
    if (allowReplace) {
        window.replaceStateOriginal(...args);
    }
}

Kemudian setiap kali breakpoint dipicu, lihat ke tumpukan panggilan.

Di konsol, jika Anda ingin mencegah pushState, cukup masukkan allowPush = false;atau allowReplace = false;sebelum melanjutkan. Dengan cara ini, Anda tidak akan melewatkan history.pushState, dan dapat naik dan menemukan kode yang memanggilnya :)

Bonjour123
sumber
Ya, tapi itu tidak mendorong atau mengganti sejarah sama sekali: / hanya membuat halaman
GROVER.
Sangat aneh. Cobalah untuk menimpa fungsi history.push dengan kode berikut: const hP = history.pushState history.pushState = (loc) => {debugger; hP (loc)} dan lakukan hal yang sama untuk history.replaceState. Kemudian setiap kali breakpoint dipicu, lihat panggilan stack
Bonjour123
Saya memeriksa tumpukan panggilan dan semuanya tampaknya dilakukan dengan benar - namun sejarah tidak mau bekerja. Mengapa Mozilla harus membuat semuanya sangat sulit :(
GROVER.
Terima kasih :) Dan jika Anda mencoba di Chrome, hasil apa yang Anda miliki?
Bonjour123