Vue Router 不渲染正确的组件
Vue Router does not render correct component
我在vue-router
中有一些嵌套路由,可以在router.js
中找到。但是,每次我登录用户时,Header
中的 Query
和 Result
link 总是呈现 Register
和 Login
组件,但是我可以通过 vue 开发人员工具 .
进行调查,确认我的 Vuex
具有正确的令牌设置
所以我对 vue-router
工作的期望如下:
- 用户尝试在
/user/login
登录
- 登录成功,令牌设置为
Vuex
状态
- 用户被重定向到
/
- 用户点击
/query
或 result
links,<router-view>
在 App.vue
中渲染 Query
或 Result
组件.
- 用户点击
Logout
按钮,Vuex
状态被清除。
- 用户被重定向回
/user/login
。
但是我得到的结果是:
- 用户尝试在
/user/login
登录
- 登录成功,令牌设置为
Vuex
状态
- 用户被重定向到
/
- 用户在
App.vue
渲染的 Register
组件中点击 /query
link、<router-view>
。
- 用户在
App.vue
渲染的 Login
组件中单击 /result
link、<router-view>
。
- 用户使用
F5
刷新页面。
- 用户点击
/query
或 result
links,<router-view>
在 App.vue
中渲染 Query
或 Result
组件.
- 用户点击
Logout
按钮,Vuex
状态被清除。
- 用户被重定向回
/user/login
。
- 用户单击
Login
按钮,<router-view>
在 App.vue
渲染的 Result
组件中。
我试过的
- 移除路由上的令牌保护,
<router-view>
仍然无法呈现正确的组件。
- 从所有
<router-link>
中删除 v-if="auth"
,令牌保护仍然存在,问题似乎消失了。但我只想在用户登录时呈现 Query
和 Result
,反之亦然。
- 使用
v-show
而不是 v-if
解决问题,但我怀疑这是对用户隐藏 link 的正确方法。
- 命名所有路由,但问题仍然存在。
一些代码
这是我的router.js
import User from './components/user/User'
import Home from './components/Home'
import Login from './components/user/Login'
import Register from './components/user/Register'
import Query from './components/Query'
import Result from './components/result/Result'
import List from './components/result/List'
import Display from './components/result/Display'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
const router = [
{
path: '/', name: 'home', component: Home
},
{
path: '/user', component: User, children: [
{path: 'login', component: Login},
{path: 'register', component: Register},
]
},
{
path: '/query', name: 'query', component: Query
},
{
path: '/result', component: Result, children: [
{path: '', component: List},
{path: ':token', component: Display}
]
},
{path: '*', redirect: '/'},
];
export default new VueRouter({
routes: router,
mode: 'history',
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
}
if (to.hash) {
return {selector: to.hash};
}
return {x: 0, y: 0};
}
})
这是我的Header.vue
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light" id="header">
<a class="navbar-brand" href="#">CMAP</a>
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<router-link to="/user/register" active-class="active" tag="li" v-if="!auth">
<a class="nav-link">Register</a>
</router-link>
<router-link to="/user/login" active-class="active" tag="li" v-if="!auth">
<a class="nav-link">Login</a>
</router-link>
<router-link to="/query" active-class="active" tag="li" v-if="auth">
<a class="nav-link">Query</a>
</router-link>
<router-link to="/result" active-class="active" tag="li" v-if="auth">
<a class="nav-link">Result</a>
</router-link>
<li @click="logout" class="nav-item" v-if="auth">
<a class="nav-link">Logout</a>
</li>
</ul>
</div>
</nav>
</template>
<script>
import * as types from '../../stores/types'
export default {
methods: {
logout() {
// clear state.token and redirect user to '/user/login'
this.$store.dispatch(types.ACTION_USER_LOGOUT);
}
},
computed: {
auth() {
// return true if state.token is set in vuex
return this.$store.getters[types.GETTER_IS_AUTHENTICATED]
}
}
}
</script>
这是我的App.vue
<template>
<div>
<appHeader></appHeader>
<div class="container">
<router-view></router-view>
</div>
<appFooter></appFooter>
</div>
</template>
<script>
import Header from './components/layouts/Header'
import Footer from './components/layouts/Footer'
export default {
components: {
'appHeader': Header,
'appFooter': Footer
}
}
</script>
这是我的store/store.js
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
jwt: {
token: null,
expire: null
},
},
getters,
mutations,
actions,
});
这是我的store/actions.js
import * as types from './types';
import axios from '../axios';
import router from '../router'
export default {
[types.ACTION_USER_LOGIN]: ({commit, dispatch}, payload) => {
axios.post('/api/user/login/', {
email: payload.email,
password: payload.password,
})
.then(res => {
if (Object.keys(res.data).length === 0 && res.data.constructor === Object) {
return
}
let payload = {
'token': res.data.token,
'expire': new Date(res.data.expire_on * 1000)
};
dispatch(types.ACTION_SET_TOKEN, payload);
commit(types.MUTATE_UPDATE_JWT, payload);
router.push('/')
})
.catch(error => {
console.log(error);
})
},
[types.ACTION_USER_REGISTER]: ({commit, dispatch}, payload) => {
axios.post('/api/user/register/', {
email: payload.email,
password: payload.password
}).then(res => {
if (Object.keys(res.data).length === 0 && res.data.constructor === Object) {
return
}
let payload = {
'token': res.data.token,
'expire': new Date(res.data.expire_on * 1000)
};
dispatch(types.ACTION_SET_TOKEN, payload);
commit(types.MUTATE_UPDATE_JWT, payload);
router.push('/')
}).catch(error => {
console.log(error);
})
},
[types.ACTION_USER_LOGOUT]: ({commit, dispatch}) => {
dispatch(types.ACTION_CLEAR_TOKEN);
commit(types.MUTATE_CLEAR_AUTH);
router.push("/user/login");
},
[types.ACTION_CLEAR_TOKEN]: () => {
localStorage.removeItem('jwt_token');
localStorage.removeItem('jwt_expire');
},
[types.ACTION_SET_TOKEN]: ({commit}, payload) => {
localStorage.setItem("jwt_token", payload.token);
localStorage.setItem("jwt_expire", payload.expire);
}
};
这是我的store/getters.js
import * as types from './types';
export default {
[types.GETTER_IS_AUTHENTICATED]: state => {
return state.jwt.token !== null
}
};
这是我的store/mutations.js
从“./types”导入 * 作为类型;
export default {
[types.MUTATE_UPDATE_JWT]: (state, payload) => {
state.jwt = payload
},
[types.MUTATE_CLEAR_AUTH]: (state) => {
state.jwt.token = null;
state.jwt.expire = null;
},
};
这是我的 axios.js
自定义实例
import {store} from './stores/store'
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://127.0.0.1:8000',
withCredentials: true
});
instance.interceptors.request.use(config => {
if (store.state.jwt.token != null) {
config.headers.common['Authorization'] = 'Bearer ' + store.state.jwt.token;
}
return config;
});
export default instance
我的store/types.js
包含了所有的常量变量字符串,所以我觉得没必要在这里包含。
如果我需要提供有关该问题的更多信息,请告诉我。
我已经通过为 Header.vue
中的每个 router-link
分配一个唯一的 key
解决了这个问题。所以最终的 Header.vue
文件在修改后看起来像这样:
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light" id="header">
<a class="navbar-brand" href="#">CMAP</a>
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<router-link to="/user/register" active-class="active" tag="li" v-if="!auth" key="link_reg">
<a class="nav-link">Register</a>
</router-link>
<router-link to="/user/login" active-class="active" tag="li" v-if="!auth" key="link_login">
<a class="nav-link">Login</a>
</router-link>
<router-link to="/query" active-class="active" tag="li" v-if="auth" key="link_query">
<a class="nav-link">Query</a>
</router-link>
<router-link to="/result" active-class="active" tag="li" v-if="auth" key="link_result">
<a class="nav-link">Result</a>
</router-link>
<li @click="logout" class="nav-item" v-if="auth">
<a class="nav-link">Logout</a>
</li>
</ul>
</div>
</nav>
</template>
<script>
import * as types from '../../stores/types'
export default {
methods: {
logout() {
// clear state.token and redirect user to '/user/login'
this.$store.dispatch(types.ACTION_USER_LOGOUT);
}
},
computed: {
auth() {
// return true if state.token is set in vuex
return this.$store.getters[types.GETTER_IS_AUTHENTICATED]
}
}
}
</script>
如果 router-link
的每个 v-if
中没有关联的键,VueJS 似乎无法正确更新 DOM。
我在vue-router
中有一些嵌套路由,可以在router.js
中找到。但是,每次我登录用户时,Header
中的 Query
和 Result
link 总是呈现 Register
和 Login
组件,但是我可以通过 vue 开发人员工具 .
Vuex
具有正确的令牌设置
所以我对 vue-router
工作的期望如下:
- 用户尝试在
/user/login
登录
- 登录成功,令牌设置为
Vuex
状态 - 用户被重定向到
/
- 用户点击
/query
或result
links,<router-view>
在App.vue
中渲染Query
或Result
组件. - 用户点击
Logout
按钮,Vuex
状态被清除。 - 用户被重定向回
/user/login
。
但是我得到的结果是:
- 用户尝试在
/user/login
登录
- 登录成功,令牌设置为
Vuex
状态 - 用户被重定向到
/
- 用户在
App.vue
渲染的Register
组件中点击/query
link、<router-view>
。 - 用户在
App.vue
渲染的Login
组件中单击/result
link、<router-view>
。 - 用户使用
F5
刷新页面。 - 用户点击
/query
或result
links,<router-view>
在App.vue
中渲染Query
或Result
组件. - 用户点击
Logout
按钮,Vuex
状态被清除。 - 用户被重定向回
/user/login
。 - 用户单击
Login
按钮,<router-view>
在App.vue
渲染的Result
组件中。
我试过的
- 移除路由上的令牌保护,
<router-view>
仍然无法呈现正确的组件。 - 从所有
<router-link>
中删除v-if="auth"
,令牌保护仍然存在,问题似乎消失了。但我只想在用户登录时呈现Query
和Result
,反之亦然。 - 使用
v-show
而不是v-if
解决问题,但我怀疑这是对用户隐藏 link 的正确方法。 - 命名所有路由,但问题仍然存在。
一些代码
这是我的router.js
import User from './components/user/User'
import Home from './components/Home'
import Login from './components/user/Login'
import Register from './components/user/Register'
import Query from './components/Query'
import Result from './components/result/Result'
import List from './components/result/List'
import Display from './components/result/Display'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
const router = [
{
path: '/', name: 'home', component: Home
},
{
path: '/user', component: User, children: [
{path: 'login', component: Login},
{path: 'register', component: Register},
]
},
{
path: '/query', name: 'query', component: Query
},
{
path: '/result', component: Result, children: [
{path: '', component: List},
{path: ':token', component: Display}
]
},
{path: '*', redirect: '/'},
];
export default new VueRouter({
routes: router,
mode: 'history',
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
}
if (to.hash) {
return {selector: to.hash};
}
return {x: 0, y: 0};
}
})
这是我的Header.vue
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light" id="header">
<a class="navbar-brand" href="#">CMAP</a>
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<router-link to="/user/register" active-class="active" tag="li" v-if="!auth">
<a class="nav-link">Register</a>
</router-link>
<router-link to="/user/login" active-class="active" tag="li" v-if="!auth">
<a class="nav-link">Login</a>
</router-link>
<router-link to="/query" active-class="active" tag="li" v-if="auth">
<a class="nav-link">Query</a>
</router-link>
<router-link to="/result" active-class="active" tag="li" v-if="auth">
<a class="nav-link">Result</a>
</router-link>
<li @click="logout" class="nav-item" v-if="auth">
<a class="nav-link">Logout</a>
</li>
</ul>
</div>
</nav>
</template>
<script>
import * as types from '../../stores/types'
export default {
methods: {
logout() {
// clear state.token and redirect user to '/user/login'
this.$store.dispatch(types.ACTION_USER_LOGOUT);
}
},
computed: {
auth() {
// return true if state.token is set in vuex
return this.$store.getters[types.GETTER_IS_AUTHENTICATED]
}
}
}
</script>
这是我的App.vue
<template>
<div>
<appHeader></appHeader>
<div class="container">
<router-view></router-view>
</div>
<appFooter></appFooter>
</div>
</template>
<script>
import Header from './components/layouts/Header'
import Footer from './components/layouts/Footer'
export default {
components: {
'appHeader': Header,
'appFooter': Footer
}
}
</script>
这是我的store/store.js
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
jwt: {
token: null,
expire: null
},
},
getters,
mutations,
actions,
});
这是我的store/actions.js
import * as types from './types';
import axios from '../axios';
import router from '../router'
export default {
[types.ACTION_USER_LOGIN]: ({commit, dispatch}, payload) => {
axios.post('/api/user/login/', {
email: payload.email,
password: payload.password,
})
.then(res => {
if (Object.keys(res.data).length === 0 && res.data.constructor === Object) {
return
}
let payload = {
'token': res.data.token,
'expire': new Date(res.data.expire_on * 1000)
};
dispatch(types.ACTION_SET_TOKEN, payload);
commit(types.MUTATE_UPDATE_JWT, payload);
router.push('/')
})
.catch(error => {
console.log(error);
})
},
[types.ACTION_USER_REGISTER]: ({commit, dispatch}, payload) => {
axios.post('/api/user/register/', {
email: payload.email,
password: payload.password
}).then(res => {
if (Object.keys(res.data).length === 0 && res.data.constructor === Object) {
return
}
let payload = {
'token': res.data.token,
'expire': new Date(res.data.expire_on * 1000)
};
dispatch(types.ACTION_SET_TOKEN, payload);
commit(types.MUTATE_UPDATE_JWT, payload);
router.push('/')
}).catch(error => {
console.log(error);
})
},
[types.ACTION_USER_LOGOUT]: ({commit, dispatch}) => {
dispatch(types.ACTION_CLEAR_TOKEN);
commit(types.MUTATE_CLEAR_AUTH);
router.push("/user/login");
},
[types.ACTION_CLEAR_TOKEN]: () => {
localStorage.removeItem('jwt_token');
localStorage.removeItem('jwt_expire');
},
[types.ACTION_SET_TOKEN]: ({commit}, payload) => {
localStorage.setItem("jwt_token", payload.token);
localStorage.setItem("jwt_expire", payload.expire);
}
};
这是我的store/getters.js
import * as types from './types';
export default {
[types.GETTER_IS_AUTHENTICATED]: state => {
return state.jwt.token !== null
}
};
这是我的store/mutations.js
从“./types”导入 * 作为类型;
export default {
[types.MUTATE_UPDATE_JWT]: (state, payload) => {
state.jwt = payload
},
[types.MUTATE_CLEAR_AUTH]: (state) => {
state.jwt.token = null;
state.jwt.expire = null;
},
};
这是我的 axios.js
自定义实例
import {store} from './stores/store'
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://127.0.0.1:8000',
withCredentials: true
});
instance.interceptors.request.use(config => {
if (store.state.jwt.token != null) {
config.headers.common['Authorization'] = 'Bearer ' + store.state.jwt.token;
}
return config;
});
export default instance
我的store/types.js
包含了所有的常量变量字符串,所以我觉得没必要在这里包含。
如果我需要提供有关该问题的更多信息,请告诉我。
我已经通过为 Header.vue
中的每个 router-link
分配一个唯一的 key
解决了这个问题。所以最终的 Header.vue
文件在修改后看起来像这样:
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light" id="header">
<a class="navbar-brand" href="#">CMAP</a>
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<router-link to="/user/register" active-class="active" tag="li" v-if="!auth" key="link_reg">
<a class="nav-link">Register</a>
</router-link>
<router-link to="/user/login" active-class="active" tag="li" v-if="!auth" key="link_login">
<a class="nav-link">Login</a>
</router-link>
<router-link to="/query" active-class="active" tag="li" v-if="auth" key="link_query">
<a class="nav-link">Query</a>
</router-link>
<router-link to="/result" active-class="active" tag="li" v-if="auth" key="link_result">
<a class="nav-link">Result</a>
</router-link>
<li @click="logout" class="nav-item" v-if="auth">
<a class="nav-link">Logout</a>
</li>
</ul>
</div>
</nav>
</template>
<script>
import * as types from '../../stores/types'
export default {
methods: {
logout() {
// clear state.token and redirect user to '/user/login'
this.$store.dispatch(types.ACTION_USER_LOGOUT);
}
},
computed: {
auth() {
// return true if state.token is set in vuex
return this.$store.getters[types.GETTER_IS_AUTHENTICATED]
}
}
}
</script>
如果 router-link
的每个 v-if
中没有关联的键,VueJS 似乎无法正确更新 DOM。