从Vue.js 2.x
发布之后,陆陆续续做了七八个项目,摸索出来了一套自己的状态管理模式,我将之称为Vuet。它以规则来驱动状态更新,它带来的是开发效率上的飙升,它就像草原,而你是野马,任你随意驰骋,总之它是为敏捷开发而诞生。
在大型的 Vue 应用程序开发中,多组件通信、多页面通信,往往是跨不过的坎,一个页面组件中往往参杂着页面获取数据的代码和响应用户操作的代码,稍有不慎,就使得代码混乱不堪。A、B、C 三个页面中,都需要同样的数据,然后每一个页面都写一次、发送一次请求,不久之后,代码就十分臃肿了。因此我们就需要vuex
这样的第三方库来管理状态了
从列表点击进去到详情,从详情返回后,我们期望能显示回原来的位置,而不是整个页面重新初始化,重新请求数据,这样带来的是用户体验的极度糟糕的,我们期望能有一种规则来定义状态应该如何更新,这便是Vuet.js诞生的初衷。它以规则来定义状态的更新,它也是一种 Vue.js 全新的状态管理模式。天生的规则驱动,使得本次教程的主题,也将变得异常简单,因为我们只需要定义好页面更新的规则即可实现。
Vuex 和 Vuet 的出发点不一样,Vuex 不建议直接更新状态,而是通过提交mutation来更新状态,而 Vuet 则是允许的。因此 Vuex 和 Vuet 是可以配合使用的,并且有着不同的应用场景,该用
Vuex 的地方就用 Vuex,可用
Vuet 的地方,就可以使用 Vuet
上面废话了那么久,也是因为Vuet.js才刚刚诞生,急需大家的支持。嗯,接下来我们开始本次的主题!
|-- pages // 页面组件
| |-- topic // 主题模块
| |-- Detail.vue // 主题详情
| |-- List.vue // 主题列表
|-- router // router 相关
| |-- index.js // 入口文件
| |-- router.js // 实例化 VueRouter
|-- vuet // vuet 相关
| |-- index.js // 入口文件
| |-- topic-detail.js // 主题详情的状态
| |-- topic-list.js // 主题列表的状态
| |-- vuet.js // 实例化 Vuet
|- index.html // 程序页面入口文件
|- main.js // Vue 实例化入口文件
上面是我们本次项目的基本目录结构
npm install vue vue-router vuet --save
这些都是基本的模块,想必不用多说,大家都知道的。
先给出官方文档地址
本章的主题,核心就是在route
规则身上,它能帮你获取、更新、重置页面的状态,配合v-route-scroll指令就能帮你处理页面的全局滚动条和 div 元素自身的滚动条
import Vue from 'vue'
import router from './router/'
import vuet from './vuet/'
export default new Vue({
el: '#app',
vuet,
router,
render (h) {
return h('router-view')
}
})
import vuet from './vuet'
export default vuet
import Vue from 'vue'
import Vuet from 'vuet'
import topicList from './topic-list'
import topicDetail from './topic-detail'
Vue.use(Vuet)
const vuet = new Vuet({
data () {
return {
loading: true, // 请求中
loaderr: false // 请求失败
}
},
pathJoin: '-', // 父子模块的连接路径
modules: {
topic: {
list: topicList,
detail: topicDetail
}
}
})
vuet.beforeEach(({ path, params, state }) => {
state.loading = true
state.loaderr = false
})
vuet.afterEach((err, { path, params, state }) => {
state.loading = false
state.loaderr = !!err
})
export default vuet
export default {
routeWatch: 'query', // 定义页面的更新规则
data () {
return {
data: [],
tabs: [
{
label: '全部',
value: 'all'
},
{
label: '精华',
value: 'good'
},
{
label: '分享',
value: 'share'
},
{
label: '问答',
value: 'ask'
},
{
label: '招聘',
value: 'job'
}
]
}
},
async fetch ({ route }) {
const { tab = '' } = route.query
const { data } = await window.fetch(`https://cnodejs.org/api/v1/topics?mdrender=false&tab=${tab}`).then(response => response.json())
return {
data
}
}
}
export default {
routeWatch: 'params.id', // 定义页面的更新规则
data () {
return {
data: {
id: null,
author_id: null,
tab: null,
content: null,
title: null,
last_reply_at: null,
good: false,
top: false,
reply_count: 0,
visit_count: 0,
create_at: null,
author: {
loginname: null,
avatar_url: null
},
replies: [],
is_collect: false
}
}
},
async fetch ({ route }) {
const { data } = await window.fetch(`https://cnodejs.org/api/v1/topic/${route.params.id}`).then(response => response.json())
return {
data
}
}
}
import router from './router'
export default router
import Vue from 'vue'
import VueRouter from 'vue-router'
import TopicList from '../pages/topic/List'
import TopicDetail from '../pages/topic/Detail'
Vue.use(VueRouter)
const RouterView = {
render (h) {
return h('router-view')
}
}
const router = new VueRouter({
routes: [
{
path: '/',
component: RouterView,
children: [
{
path: '',
name: 'topic-list',
component: TopicList
},
{
path: '/:id',
name: 'topic-detail',
component: TopicDetail
}
]
}
]
})
export default router
<template>
<!--
设置指令监听全局滚动条,
注意了,光是设置指令可不行,还需要在组件中使用 route 规则,
来处理页面滚动的操作,
局部滚动条直接去掉.window 即可
如果需要同时记录全局滚动条和 div 滚动条直接设置.window.self 即可
它能做到 N 多个滚动位置记录,具体看官方文档喔!
注:记录 div 滚动的话,需要设置一个 name 来识别
v-route-scroll="{ path: 'topic-detail', name: 'xxx' }"
-->
<div v-route-scroll.window="{ path: 'topic-list' }">
<header>
<ul>
<li v-for="item in list.tabs">
<router-link :to="{ name: 'topic-list', query: { tab: item.value } }">{{ item.label }}</router-link>
</li>
</ul>
</header>
<ul class="list">
<li v-for="item in list.data">
<router-link :to="{ name: 'topic-detail', params: { id: item.id } }">{{ item.title }}</router-link>
</li>
</ul>
</div>
</template>
<script>
import { mapRules, mapModules } from 'vuet'
export default {
mixins: [
// 设置模块的更新规则
mapRules({
route: 'topic-list'
}),
// 连接模块的状态
mapModules({
list: 'topic-list'
})
]
}
</script>
<style scoped>
</style>
<template>
<div v-route-scroll.window="{ path: 'topic-detail' }">
<h3>{{ detail.data.title }}</h3>
<div v-html="detail.data.content"></div>
</div>
</template>
<script>
import { mapRules, mapModules } from 'vuet'
export default {
mixins: [
// 设置模块的更新规则
mapRules({
route: 'topic-detail'
}),
// 连接模块的状态
mapModules({
detail: 'topic-detail'
})
]
}
</script>
<style scoped>
</style>
咋的一看,Vuet 看起来也不是很复杂,只需要定义好模块状态,然后在组件中设置对应的规则来更新模块的状态即可。其实 vuet 自带的 route 规则能够支持同时记录全局滚动条、div 自身的滚动条,这样就能大大的提升了我们的用户体验
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.