pushState criando entradas de histórico duplicadas e substituindo as anteriores

15

Criei um aplicativo da Web que utiliza os métodos history pushStatee replaceStatepara navegar pelas páginas e também atualizar o histórico.

O script em si funciona quase perfeitamente; ele carrega as páginas corretamente e gera erros de página quando eles precisam ser lançados. No entanto, notei um problema estranho no qual pushStateenviará várias entradas duplicadas (e substituirá as entradas anteriores) ao histórico.

Por exemplo, digamos que eu faça o seguinte (em ordem):

  1. Carregue o index.php (o histórico será: Index)

  2. Navegue até profile.php (o histórico será: Profile, Index)

  3. Navegue para search.php (o histórico será: Pesquisa, Pesquisa, Índice)

  4. Navegue para dashboard.php

Finalmente, é isso que vai aparecer na minha história (na ordem do mais recente ao mais antigo):

Painel de
controle Índice de pesquisa de
painel


O problema é que, quando um usuário clica nos botões de avançar ou voltar, ele será redirecionado para a página incorreta ou precisará clicar várias vezes para voltar uma vez. Isso, e não fará sentido se eles verificarem sua história.

Isto é o que eu tenho até agora:

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

Toda a ajuda é apreciada,
Saúde.

GROVER.
fonte
Então você só deseja propagar uma alteração no histórico se o destino for diferente da última entrada?
Aluan Haddad 30/10/19
Isso está acontecendo aparentemente aleatoriamente? Ou são páginas específicas sempre aquelas que causam entradas duplicadas no histórico.
SamVK 30/10/19
@AluanHaddad não, eu só quero propagar uma alteração no histórico se houver uma alteração no histórico (por exemplo, quando um usuário estiver navegando pelo site). Semelhante ao que aconteceria em uma situação normal ... (passar do índice para o perfil para pesquisar no StackOverflow retornaria exatamente isso na minha história)
GROVER.
@SamVK Não importa em que página, depois de navegar pela página inicial de carregamento (por exemplo, index.php), as entradas serão duplicadas.
GROVER.
11
Bem, não tenho muita certeza disso, então escreva nos comentários. Vejo que você está adicionando coisas do histórico do navegador em sua updateHistoryfunção. Agora, updateHistorypode ser chamado duas vezes, primeiro, quando você está inicializando o Traveler ( window.Traveller = new Traveller();, constructor-> init-> send-> render-> updateHistory), e também a redirectpartir do clickeventListener. Eu não testei, apenas adivinhação, então adicioná-lo como um comentário e não uma resposta.
Akshit Arora 02/11/19

Respostas:

3

Você tem um manipulador onpopstate ?

Se sim, verifique também se você não está empurrando para o histórico. O fato de algumas entradas serem removidas / substituídas na lista de histórico pode ser um grande sinal. De fato, veja esta resposta SO :

Lembre-se de que history.pushState () define um novo estado como o estado mais recente do histórico. E window.onpopstate é chamado ao navegar (para trás / para frente) entre os estados que você definiu.

Portanto, não pressione pushState quando o window.onpopstate for chamado, pois isso definirá o novo estado como o último estado e, portanto, não há nada para o qual avançar.

Certa vez, tive exatamente o mesmo problema que você descreve, mas foi realmente causado por eu voltar e avançar para tentar entender o bug, o que acabaria por acionar o manipulador popState. A partir desse manipulador, ele chamaria history.push. Então, no final, eu também tinha algumas entradas duplicadas e algumas ausentes, sem nenhuma explicação lógica.

Eu removi a chamada para history.push, substituí-a por history.replace depois de verificar algumas condições e depois funcionou como um encanto :)

EDITAR -> DICA

Se você não conseguir localizar qual código está chamando history.pushState:

Tente substituir as funções history.pushState e replaceState pelo seguinte código:

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

Então, sempre que os pontos de interrupção forem acionados, observe a pilha de chamadas.

No console, se você deseja impedir um pushState, basta digitar allowPush = false;ou allowReplace = false;antes de continuar. Dessa forma, você não perderá nenhum history.pushState e poderá subir e encontrar o código que o chama :)

Bonjour123
fonte
Sim, mas não empurra nem substitui a história: / apenas renderiza a página
GROVER.
Muito estranho. Tente substituir a função history.push pelo seguinte código: const hP = history.pushState history.pushState = (loc) => {depurador; hP (loc)} e faça o mesmo para history.replaceState. Em seguida, cada vez que os pontos de interrupção são trigerred, ter um olhar para a pilha de chamadas
Bonjour123
Eu verifiquei a pilha de chamadas e tudo parece ter sido feito corretamente - mas o histórico não quer funcionar. Por que a Mozilla deve dificultar tudo :(
GROVER.
Obrigado :) E se você experimenta o Chrome, que resultados você tem?
precisa saber é o seguinte