最近在写一个课程管理类的Web SPA(单页应用)的工具,在最初的时候想着要用到vue全家桶,即Vue2 + Vuex + VueRouter。但是仅仅看完文档没有任何敏感的直觉(还是代码敲的少),只是拿来用。如今在数据流上出现点问题,在此总结归纳一下。
Talk is cheap. Show me the code.(神翻译:屁话少说,放码过来。)
代码
下面是部分代码:
app.js
// ...
import App from './components/Home.vue'
import store from './store.js'
import routes from './routes.js'
Vue.use(Router)
const router = new Router({
mode: 'history',
routes,
})
new Vue({
el: '#default',
store,
router,
components: { App },
render: h => h(App),
beforeCreate () {
this.$store.dispatch('fetchCourses')
this.$store.dispatch('fetchChapters')
this.$store.dispatch('fetchLessons')
// ...
},
// ...
});
Vue.store = store;
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import api from './api'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
coursesList: [],
chaptersList: [],
lessonsList: [],
// ...
},
mutations: {
SET_COURSESLIST (state, val) {
state.coursesList = val
},
SET_CHAPTERSLIST (state, val) {
state.chaptersList = val
},
SET_LESSONSLIST (state, val) {
state.lessonsList = val
},
// ...
},
actions: {
// 调用api获取初始值。
fetchCourses (context) {
api.courses(context)
},
fetchChapters (context) {
api.chapters(context)
},
fetchLessons (context) {
api.lessons(context)
},
// ...
},
getters: {
getCurrentCourse: (state, getters) => (id) => {
// 此处根据传入的 id 值将 state.courseList、 state.chaptersList、 state.lessonsList 拼装成需要的数据格式
let course_id = id
let current_course = coursesList.find(course =>course.id === course_id)
let chapters = state.chaptersList.filter(chapter => chap.course_id === course_id);
current_course.chapter = chapters
current_course.chapter.forEach(chap => {
chap.lesson = [...state.lessonsList.filter(lesson => lesson.chapter_id === chap.id)]
})
},
// ...
},
})
根据vuex文档Action的描述,将异步请求在Action中进行,得到返回值后再提交给Mutation,进行同步赋值。
上面代码中store.getters.getCurrentCourse(id)
拼装当前正在查看的课程,最终得到的大致结构如下:
{
id: 1,
course_title: '测试课程',
course_content: '课程内容:这是一个测试课程',
chapters: [
{
id: 1,
course_id: 1,
chapter_title: '第一章',
lessons: [
{ id: 1, chapter_id: 1, lesson_title: '第一节', lesson_content: '章节内容:这是第一节课。'},
{ id: 2, chapter_id: 1, lesson_title: '第二节', lesson_content: '章节内容:这是第二节课。'},
// ...
],
},
{
id: 2,
course_id: 1,
chapter_title: '第二章',
lessons: [
{ id: 1, chapter_id: 1, lesson_title: '第一节', lesson_content: '章节内容:这是第一节课。'},
{ id: 2, chapter_id: 1, lesson_title: '第二节', lesson_content: '章节内容:这是第二节课。'},
// ...
],
},
],
}
api.js
const APP_ROOT = 'http://my.website.com'
export default ({
courses: function (store) {
axios.get(APP_ROOT+'/get_courses').then((response) => {
store.commit('SET_COURSESLIST', response.data)
})
},
chapters: function (store) {
axios.get(APP_ROOT+'/get_chapters').then((response) => {
store.commit('SET_CHAPTERSLIST', response.data)
})
},
lessons: function (store) {
axios.get(APP_ROOT+'/get_lessons').then((response) => {
store.commit('SET_LESSONSLIST', response.data)
})
},
})
上面代码将获取到的返回值提交给mutation。
由于是在客户端是浏览器,用户是可以在任何路由下刷新页面,所以为了保持数据,在生命周期beforeCreate
去抓取数据。每次刷新先拿到数据,再进行拼装。
问题
思路是这样,但是实际中,尤其是在当前某个课时的页面刷新有时会出现问题,会经常在store.getters.getCurrentCourse(id)
处报错,报错提示没有获取到chaptersList
,或者提示没有获取到lessonsList
。但是看到 Devtool 中 Vue 的state.chaptersList
和state.lessonsList
都是有值存在的。
经过排查发现刷新页面后,尤其是在查看当前课时的时候,需要展示完整的课程数据结构,当coursesList获取到并且被赋值,但是chaptersList、lesonsList有时候在还没有被赋值的时候,就已经开始调用store.getters.getCurrentCourse(id)
去拼装我们需要的结构了,那这时候肯定会出错了。
本来以为是异步的问题,还在纠结如何能让异步的Action能做到按course->chapter->lesson
顺序拿到数据,这样使用完全本末倒置没有理解Action的用法。
解决方法
解决起来其实并不难,只需要将这三个值在同一次请求获取到就解决这个问题了。
但是一次请求的数据量及请求速度,相比之前三次请求的数据量及请求速度,肯定会增大量降低速度,由于现在没有那么大的数据量,差异没有很明显,而且解决了现有的问题,当以后有了需求的时候,就需要对数据量和访问速度进行权衡。
传送门:Vuex中文文档、扩展运算符、对象的扩展运算符