pushStateは重複する履歴エントリを作成し、以前のエントリを上書きします


15

history pushStatereplaceStateメソッドを使用して、ページを移動しながら履歴を更新するWebアプリを作成しました。

スクリプト自体はほぼ完全に機能します。ページを正しくロードし、スローする必要がある場合はページエラーをスローします。しかし、pushStateが複数の重複したエントリを履歴にプッシュする(そしてその前のエントリを置き換える)とは、奇妙な問題に気づきました。

たとえば、次のことを(順番に)実行するとします。

  1. index.phpをロードします(履歴はIndexになります)

  2. profile.phpに移動します(履歴はプロファイル、インデックスになります)。

  3. search.phpに移動します(履歴は検索、検索、インデックスになります)

  4. dashboard.phpに移動します

そして最後に、これは私の履歴に現れるものです(最も新しいものから最も古いものの順に):

ダッシュ
ボード
ダッシュボードダッシュボード
検索
インデックス

この問題は、ユーザーが進むボタンまたは戻るボタンをクリックすると、誤ったページにリダイレクトされるか、1回戻るために複数回クリックする必要があることです。それは、彼らが行って履歴を確認しても意味がありません。

これは私がこれまでに持っているものです:

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);
        }
    }
});

すべてのヘルプは、高く評価され
乾杯。


したがって、ターゲットが最後のエントリと異なる場合にのみ、履歴の変更を伝播したいですか?
Aluan Haddad

これは一見ランダムに起こっていますか?または、特定のページが常に履歴エントリの重複を引き起こすページです。
SamVK

@AluanHaddadいいえ履歴の変更がある場合(つまり、ユーザーがサイト内を移動している場合)にのみ、履歴の変更を伝達します。通常の状況で発生するのと同様に...(インデックスからプロファイルに移動してStackOverflowで検索すると、履歴に正確に返されます)
GROVER。

@SamVKどのページでもかまいません。最初のロードアップページ(つまり、index.php)から移動した後、エントリが重複します。
GROVER。

1
まあ、私はそれについてはよくわからないのでコメントで書く。updateHistory関数にブラウザの履歴を追加しているようですね。これで、updateHistoryTravelerを初期化しているときに(1つ目は2回)、2つ目は呼び出される可能性があります(window.Traveller = new Traveller();constructor-> init-> send-> render-> updateHistory)、さらにeventListener redirectからもclick呼び出されます。私はそれをテストしていません、単なる推測なので、答えではなくコメントとして追加しました。
Akshit Arora

回答:


3

あなたが持っていますかonpopstateハンドラを?

もしそうなら、あなたが歴史にプッシュしていないかどうかもそこを確認してください。一部のエントリが履歴リストで削除/置換されていることは大きな兆候である可能性があります。確かに、このSOの答えを見てください:

history.pushState()は新しい状態を最新の履歴状態として設定することに注意してください。そして、window.onpopstateは、設定した状態間を(後方/前方に)移動するときに呼び出されます。

したがって、window.onpopstateが呼び出されたときにpushStateを実行しないでください。これにより、新しい状態が最後の状態として設定され、次に進むべきものがなくなります。

私はかつてあなたが説明したのとまったく同じ問題を抱えていましたが、実際には、私がバグを理解しようとして前後に進み、最終的にpopStateハンドラーをトリガーすることによって引き起こされました。そのハンドラーから、history.pushを呼び出します。そのため、最後には、論理的な説明なしに、重複したエントリと欠落したエントリがありました。

history.pushへの呼び出しを削除し、いくつかの条件をチェックした後、history.replaceに置き換えました。

編集 -> ヒント

history.pushStateを呼び出しているコードが見つからない場合:

history.pushState関数とreplaceState関数を次のコードで上書きしてみてください。

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);
    }
}

次に、ブレークポイントがトリガーされるたびに、呼び出しスタックを調べます。

コンソールで、pushStateを防止する場合は、入力するallowPush = false;allowReplace = false;再開する前に入力します。このようにして、history.pushStateを見逃すことはなく、それを呼び出してそれを呼び出すコードを見つけることができます。


私はそうしますが、それは履歴をまったくプッシュしたり置き換えたりしません:/ページをレンダリングするだけです
GROVER。

本当に変。次のコードでhistory.push関数を上書きしてみてください。const hP = history.pushState history.pushState =(loc)=> {debugger; hP(loc)}と同様に、history.replaceStateに対して同じことを行います。次に、ブレークポイントがトリガーされるたびに、コールスタックを確認します
Bonjour123

私はコールスタックを確認しましたが、すべてが正しく行われているようですが、履歴は機能しません。なぜ必見Mozillaのメークすべて困難:(そう
。GROVER

ありがとう:)そして、Chromeを試した場合、どのような結果になりますか?
Bonjour123
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.