NavigationDuplicated Navigating to current location ("/search") is not allowed

当我想做一个搜索多次,它显示我的 NavigationDuplicated错误。我的搜索是在导航栏中,我配置搜索的方法是使用模型获取值,然后将值作为参数传递给 ContentSearch 组件,然后接收该组件中的搜索值。

我知道正确的方法是使用发射器,但我仍然不知道如何学会使用它。访问发射是 context.emit('', someValue)

NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated", message: "Navigating to current location ("/search") is not allowed", stack: "Error↵    at new NavigationDuplicated (webpack-int…node_modules/vue/dist/vue.runtime.esm.js:1853:26)"}

NavBar.vue

<template>
<nav class="navbar navbar-expand-lg navbar-dark bg-nav" v-bind:class="{'navbarOpen': show }">
<div class="container">
<router-link to="/" class="navbar-brand">
<img src="../assets/logo.png" alt="Horizon Anime" id="logo">
</router-link>


<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" v-on:click.prevent="toggleNavbar">
<span class="navbar-toggler-icon"></span>
</button>


<div class="collapse navbar-collapse" id="navbarSupportedContent" v-bind:class="{'show': show }">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<router-link class="nav-link" to="/" ><i class="fas fa-compass"></i> Series</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'EpisodesSection'}" ><i class="fas fa-compact-disc"></i> Episodios</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'MovieSection'}" ><i class="fas fa-film"></i> Peliculas</router-link>
</li>
</ul>
<div class="search-bar">
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" v-model="query" type="search" placeholder="Buscar películas, series ..." aria-label="Search">
<button class="btn btn-main my-2 my-sm-0" @click.prevent="goto()" type="submit"><i class="fas fa-search"></i></button>
</form>
</div>
</div>
</div>
</nav>
</template>


<script>
import {value} from 'vue-function-api';
import {useRouter} from '@u3u/vue-hooks';


export default {
name: "NavBar",
setup(context){
const {router} = useRouter();
const query = value("");


let show = value(true);
const toggleNavbar = () => show.value = !show.value;
      

const goto = () =>{
let to = {name: 'ContentSearched' , params:{query: query}}
router.push(to);
};
        

return{
show,
toggleNavbar,
goto,
query
}
}
}
</script>

ContentSearched.vue

<template>
<div class="container">
<BoxLink/>
<main class="Main">
<div class="alert alert-primary" role="alert">
Resultados para "{{query}}"
</div>
<div v-if="isLoading">
<!-- <img class="loading" src="../assets/loading.gif" alt="loading"> -->
</div>
<div v-else>
<ul class="ListEpisodios AX Rows A06 C04 D02">
<li v-for="(content, index) in contentSearched" :key="index">
<div v-if="content.type === 'serie'">
<Series :series="content"/>
</div>
<div v-if="content.type === 'pelicula'">
<Movies :movies="content"/>
</div>
</li>
</ul>
</div>
</main>
</div>
</template>




<script>
import {onCreated} from "vue-function-api"
import {useState , useRouter , useStore} from '@u3u/vue-hooks';
import BoxLink from "../components/BoxLink";
import Movies from "../components/Movies";
import Series from "../components/Series";


export default{
name: 'ContentSearched',
components:{
BoxLink,
Movies,
Series
},
setup(context){
const store = useStore();
const {route} = useRouter();


const state = {
...useState(['contentSearched' , 'isLoading'])
};


const query = route.value.params.query;


onCreated(() =>{
store.value.dispatch('GET_CONTENT_SEARCH' , query.value);
});
return{
...state,
query,
}
}
};
</script>
132657 次浏览

这里混合了多种概念,从 router-link到编程导航,再到查询参数到状态存储。这使得帮助您并告诉您什么是“正确的”解决方案变得有点困难。

尽管如此,我认为对你来说最好的方法是:
1)定义路线为:

{
path: "/search/:searchString",
component: MySearchComponent,
props: true
}

2)使用响应式 <router-link>代替 router.push

<input type="text" v-model="searchString">
<router-link :to="'/search/'+searchString" tag="button">search</router-link>

3) access the searchString in your search component via props: ['searchString'] and this.searchString

props: ['searchString'],
...
computed: {
msg() {
return `Searching for, ${this.searchString}!`;
}
}

完整例子: https://codesandbox.io/s/vue-routing-example-9zc6g
注意,我刚刚分叉了第一个代码和框与 router我可以找到,相应调整。

如果你在你的代码中使用 router.push,并且你不在乎导航失败,你应该使用 catch 来捕捉它:

router.push('/location').catch(err => {})

我认为,如果我们不打算进一步使用 Router.push作为异步调用,那么这个问题的最佳解决方案可以在根级实现。

import Router from 'vue-router';


const originalPush = Router.prototype.push;
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
};


Vue.use(Router);

当我有一个指向同一条路线的 router-link时,这种情况就发生了。

用户可以单击产品,但是如果已经单击了产品(并且已经加载了组件视图)和用户尝试再次单击它,错误/警告显示在控制台中。

你可以学习更多的 关于 Github 的问题。

Posva, one of the main contributors of vue-router suggests:

Push (‘ your-path’) . catch (err = > {})

However, if you don't want to have a catch block which does nothing, in order to solve the issue you can compare the router navigation with the current route 只有在不同的情况下才能导航:

const path = `/products/${id}`
if (this.$route.path !== path) this.$router.push(path)

注意: $route是由 vue-router 提供给每个组件的对象。更多信息请参见 路由对象

如果您觉得捕捉所有类型的错误不太舒服,那么我认为这种实现更为周到:

this.$router.push("path").catch(error => {
if (error.name != "NavigationDuplicated") {
throw error;
}
});

下面是一个简单而有效的解决方案:

if(from.fullPath === to.fullPath){
return
}

手册:

router.push(location, onComplete?, onAbort?)

你可以使用更简单的

router.push("/", () => {});

我在搜索时也遇到了同样的问题。我的解决方案是将 timestamp添加到搜索页面的 this.$route.query参数中。

this.$router.push({
path: "/search",
query: {
q: this.searchQuery,
t: new Date().getTime(),
}
});

Hope it helps you.

截至2021年,全球配置:

我只是想消除 NavigationDuplicated错误,一个空捕获可能是危险的。所以我这样做:

const router = new VueRouter({/* ... */})


const originalPush = router.push
router.push = function push(location, onResolve, onReject)
{
if (onResolve || onReject) {
return originalPush.call(this, location, onResolve, onReject)
}
 

return originalPush.call(this, location).catch((err) => {
if (VueRouter.isNavigationFailure(err)) {
return err
}
   

return Promise.reject(err)
})
}

初始化 vue-router 时插入一次。
感谢 @ Oleg Abrazhaev的更新。

我的解决方案是混合扩展 prototype和检查 Navigation Duplicated Error。其他错误和警告应该是可见的。经过一个星期的生产-没有 NavigationDuplicated和一切工作。

import { equals } from 'ramda'


export function register(Vue) {
const routerPush = Router.prototype.push
const routerReplace = Router.prototype.push


const isNavigationDuplicated = (currentRoute, nextRoute) => {
const { name: nextName, params: nextParams = {}, query: nextQuery = {} } = nextRoute
const { name, params, query } = currentRoute


return equals(nextQuery, query) && equals(nextParams, params) && equals(nextName, name)
}


Router.prototype.push = function push(location) {
if (!isNavigationDuplicated(this.currentRoute, location)) {
return routerPush.call(this, location)
}
}


Router.prototype.replace = function replace(location) {
if (!isNavigationDuplicated(this.currentRoute, location)) {
return routerReplace.call(this, location)
}
}


Vue.use(Router)
}

我已经迟到了,但是我想我应该把我的解决方案添加到这个问题中,因为它没有被列出来: 我只是简单地放置了一个中间搜索页面作为搜索结果视图的传递。我现在使用这个网页做一些搜索条件的预处理。

页面模板简单如下:

<template>
<div>searching ...</div>
</template>

NavigationDuplated 错误现在已经消失了,由于我在这个中间页面中执行了提取操作,所以附加的好处是,错误处理的责任从搜索栏和结果视图中分离出来。

我在这里张贴我发现的解决方案,因为我无法找到它很好的文档的地方,我通过试验和错误通过它。 它可能对某些人有用,或者某些人可能会修正我对 Vue-router 警卫的误解。

它利用了 V4.x 视频路由器和一个全局 beforeEach保护。

用例如下:

  1. 未经授权的用户请求 https://app.com/;
  2. 用户要求 https://app.com/已被授权;
  3. 用户请求任何可用的路由,这需要授权与否。

路线:

const routes = [
/**
* Routes not requiring auth
*/
{
path: '/',
component: () => import('layouts/NotAuthorizedLayout.vue'),
children: [
{
path: 'login',
name: 'LOGIN',
component: () => import('pages/Login.vue')
},
{
path: 'emailsignup',
component: () => import('pages/EmailSignup.vue')
},
{
path: 'forgottenpassword',
component: () => import('pages/ForgottenPassword.vue')
}
]
},


/**
* Routes requiring auth
*/
{
path: '/',
component: () => import('layouts/AuthorizedLayout.vue'),
meta: { requiresAuth: true },
children: [
{
path: 'authors',
name: 'AUTHORS',
component: () => import('pages/Authors.vue')
},
{ path: 'profile', component: () => import('pages/userProfile/index.vue') }
]
}
];

每个国际卫队:

  const redirectToLogin = route => {
const LOGIN = 'LOGIN';
if (route.name != LOGIN) {
return { name: LOGIN, replace: true, query: { redirectFrom: route.fullPath } };
}
};


const redirectToHome = route => {
const DEFAULT = 'AUTHORS';
return { name: DEFAULT, replace: true };
};


Router.beforeEach((to, from) => {
const userIsAuthenticated = store.getters['authentication/userIsAuthenticated'];
const requiresAuth = to.matched.some((route) => route.meta && route.meta.requiresAuth);


if (!userIsAuthenticated && to.fullPath === '/') {
return redirectToLogin(to);
}


if (!userIsAuthenticated && requiresAuth) {
return redirectToLogin(to);
}


if (to.fullPath === '/') {
return redirectToHome(to);
}


return true;
});

你的问题太老套了。

您的错误是“@click”。预防”。此语句不起作用,因为您的按钮是一个提交按钮(因此您的事件被调用了两次)。

应该可以使用“@subit.Prevention”(或者更改按钮的类型)。

我注意到,当我尝试用相同的值替换 url 查询参数时会出现错误。

I have select filters and url query string params are in sync with their values. It works well as long as you change to a new value. If value remains the same (for example coming back from history) and thus replacing query string parameter with same value, error pops out.

解决方案是检查值是否改变,然后在路由器中替换查询参数:

let newValue = 'foo'; // new query value for parameter
let qcopy = { ...this.$route.query }; // clone current query
// prevent NavigationDuplicated: Avoided redundant navigation to current location
if (qcopy['your_param'] != newValue){
qcopy['your_param'] = newValue;
this.$router.replace({query: qcopy});
}

For TypeScript it worked like this

const superPush = VueRouter.prototype.push


VueRouter.prototype.push = async function push(loc:RawLocation):Promise<Route> {
try {
return await superPush.bind(this)(loc)
} catch (e) {
if (e?.name === 'NavigationDuplicated') {
console.warn(e)
return e
} else {
throw e
}
}
}

Stop the click propagation from hitting the router action

我在这里有一个高层次的答案,可能对其他人有用。它可能不会直接回答 OP,但是这种思想是适用的。

我对此的看法是,不要乱搞全局配置,也不要试图捕捉路由器逻辑。在应用程序中到处发生异常是没有意义的。

In the case of a results view with filters. If the filters happen to be presented as router links (for various reasons), but we don't want actual router logic to occur when they're clicked.

因此,捕捉点击之前,它到达路由器的行动!

然后你会得到两个世界的最好结果:

Search filters (as a link) that...

  1. 在视图中执行逻辑
  2. 仍然提供作为链接呈现的好处 (对机器人扫描、用户方便性和可访问性等的好处)

技巧:

在子 在里面router-link上使用 @click.prevent,来捕捉和阻止点击路由器。

例如:

以前: 路由器逻辑发生,即使我们已经在路由中

<router-link class="nav-link" :to="{name: 'MovieSection'}" >
<i class="fas fa-film"></i>Peliculas
</router-link>

之后: Router logic inhibited, we run other logic (applyFilter)

<router-link class="nav-link" :to="{name: 'MovieSection'}" >
<div @click.prevent="myFunc(something)">
<i class="fas fa-film"></i>Peliculas
</div>
</router-link>

使用这种方法,您可以避免应用程序中混乱的高级异常。

最佳做法是:

import { isNavigationFailure, NavigationFailureType } from 'vue-router/src/util/errors';
this.$router.push(...).catch((error) => {
if (!isNavigationFailure(error, NavigationFailureType.duplicated))
throw error
);
}

看看 Vue 路由器文档

这在路由器/index.js init 中对我很有用。您可以在 call ()方法中处理所需的异常类型或消息,以忽略某些条件或引发某些条件。

你看

//处理路由错误

评论

import Vue from "vue";
import VueRouter from "vue-router";


import routes from "./routes";


Vue.use(VueRouter);


/*
* If not building with SSR mode, you can
* directly export the Router instantiation;
*
* The function below can be async too; either use
* async/await or return a Promise which resolves
* with the Router instance.
*/


export default function(/* { store, ssrContext } */) {
const router = new VueRouter({
scrollBehavior: () => ({ x: 0, y: 0 }),
routes,


// Leave these as they are and change in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
mode: "history"
});


router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
var token = window.localStorage.getItem("token");
if (token != null) {
var decodedtoken = JSON.parse(atob(token.split(".")[1]));
var role =
decodedtoken[
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
];
if (role != "Admin") {
next("/accessdenied");
} else {
next();
}
} else {
next("/login");
}
} else {
next();
}


if (to.meta.requiresLogin) {
var token = window.localStorage.getItem("token");
if (token != null) {
next();
} else {
next("/login");
}
}
});




//HANDLE ROUTE ERRORS HERE
const originalPush = router.push;
router.push = function push(location) {
return originalPush.call(this, location).catch(err => {
console.warn("router>index.js>push()", err);
});
};


return router;
}

基于@Abhishek Shastri 的回答