Анимированное переключение маршрутов с загрузкой данных, Vue
Использую Vue, Vuex, Vue-router. Есть список из элементов router-link. По клику должен переключатся с анимацией компонент в области router-view и меняться url адрес маршрута. Те старый компонент должен полностью исчезнуть, затем идет запрос на сервер за данными для нового компонента, а потом плавно появляется новый компонент. Но так не происходит. После клика по router-link данные нового маршрута сразу быстро загружаются и плавно исчезает уже новый компонент, затем он же плавно появляется.
Что я делаю не так, что у меня исчезает уже новый компонент? Запрос за данными делаю в хуках beforeRouteEnter и beforeRouteUpdate.
LessonsList.vue
<template>
<section>
<ul>
<router-link v-for="(lesson,index) in lessons"
v-bind:key="index"
:to="{
name: 'lesson',
params: {
id: index+1
}
}"
tag="li">
<h2>№{{index+1}}</h2>
</router-link>
</ul>
<transition name="slide" mode="out-in" appear>
<router-view :key="$route.path"></router-view>
</transition>
</section>
Lesson.vue
import {store} from '../../vuex/store'
beforeRouteEnter(to,from,next) {
let sendData = {};
sendData.lessonNumber = Number(to.params.id);
sendData.userId = store.getters['login/userId'];
store.dispatch('lesson/changeCurrentLesson', sendData).then(()=> next());
//пробрасываю action во vuex, где идет запрос на сервер за данными
},
beforeRouteUpdate(to,from,next){
let sendData = {};
sendData.lessonNumber = Number(to.params.id);
sendData.userId = store.getters['login/userId'];
store.dispatch('lesson/changeCurrentLesson', sendData).then(()=> next());
}
Ответы (1 шт):
Согласно документации Vue Router - Выборка данных :
Иногда вам нужно получить данные с сервера, когда маршрут активирован. Например, перед рендерингом профиля пользователя вам необходимо получить данные пользователя с сервера. Мы можем достичь этого двумя разными способами:
Выборка после навигации : сначала выполните навигацию и выберите данные в хуке жизненного цикла входящего компонента.
Выборка перед навигацией : выберите данные перед навигацией по маршруту, выполните навигацию после получения данных.
Технически оба варианта являются допустимыми - в конечном итоге это зависит от того, к какому вы стремитесь.
Чтобы второй вариант работал, как следует нужно иметь ввиду как работают хуки beforeRouteEnter и beforeRouteUpdate Здесь об этом подробнее можно почитать.
beforeRouteEnter (to, from, next) {
// вызывается до подтверждения пути, соответствующего этому компоненту.
// НЕ ИМЕЕТ доступа к контексту экземпляра компонента `this`,
// так как к моменту вызова экземпляр ещё не создан!
},
beforeRouteUpdate (to, from, next) {
// вызывается когда маршрут, что рендерит этот компонент изменился,
// но этот компонент будет повторно использован в новом маршруте.
// Например, для маршрута с динамическими параметрами `/foo/:id`, когда мы
// перемещаемся между `/foo/1` и `/foo/2`, экземпляр того же компонента `Foo`
// будет использован повторно, и этот хук будет вызван когда это случится.
// Также имеется доступ в `this` к экземпляру компонента.
},
И рабочий пример (Vue, Vuex, Vue Router):
const UsersList = Vue.component('UsersList', {
template: `
<transition name="slide-fade" mode="out-in">
<router-view :key="$route.params.id"></router-view>
</transition>`
})
const User = Vue.component('User', {
template: `
<section v-if="user">
<h1>{{ user.name }}</h1>
<h3>{{ user.email }}</h3>
<p>{{ user.address.city }}/{{ user.address.street }}</p>
</section>
`,
computed: {
user() {
return this.$store.getters.getCurrentUser
}
},
beforeRouteEnter(to, from, next) {
next(vm => {
vm.$store.dispatch('getUserFromApi', to.params.id)
})
},
beforeRouteUpdate(to, from, next) {
this.$store.commit('setUsers', null)
this.$store.dispatch('getUserFromApi', to.params.id)
next()
}
})
const E404 = Vue.component('E404', {
template: `
<section>
<h4>Error component</h4>
</section>
`
})
const routes = [{
path: '/',
redirect: {
name: 'userslist'
}
},
{
name: 'userslist',
path: '/userslist',
component: UsersList,
children: [{
name: 'user',
path: 'user:id',
component: User
}]
},
{
path: '*',
component: E404
}
]
const router = new VueRouter({
routes
})
const store = new Vuex.Store({
state: () => {
return {
users: null,
currentUser: null
}
},
getters: {
getCurrentUser: (state, getters) => {
return state.currentUser
}
},
mutations: {
setUsers(state, user) {
state.currentUser = user
}
},
actions: {
async getUserFromApi({
commit
}, id) {
await fetch('https://jsonplaceholder.typicode.com/users/' + id)
.then(response => response.json())
.then((user) => {
commit('setUsers', user)
})
}
}
})
const app = new Vue({
router,
store,
data: {
users: []
},
async created() {
await fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then((json) => {
this.users = json
})
}
}).$mount('#app')
.box {
overflow: hidden;
display: flex;
flex-direction: row;
}
hr {
background: #eee;
height: 1px;
border: 0;
}
.left {
max-width: 40%;
}
.right {
max-width: 60%;
}
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter,
.slide-fade-leave-to
/* .slide-fade-leave-active до версии 2.1.8 */
{
transform: translateY(40px);
opacity: 0;
}
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.5.1/vuex.min.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
Текущий URL - <b>{{ $route.path }}</b>
<hr>
<div class="box">
<div class="left">
<ul>
<li v-for="(user,index) in users" :key="index">
<router-link :to="{
name: 'user',
params: {
id: user.id
}
}" tag="a">{{ user.name }}</router-link>
</li>
</ul>
</div>
<div class="right">
<router-view></router-view>
</div>
</div>
</div>