如何解决vue-vuerouter导航钩子中钩子函数data依赖于异步数据的问题

4659人阅读
Vue.js(83)
/a/7096?utm_source=tuicool&utm_medium=referral
初次接触vue,刷完了堪称经典的vue官网文档+vue-router文档+vuex文档+vue-cli文档,然后就开始刷项目了。这篇文章总结了项目实践的一些思路。
首先看下项目的总览图(mockplus制作)
这个项目我负责的部分是一个控制台的需求,用户可以在这个模块进行佣金、订单等项目的修改设置,可以看到第1张图相当于控制台首页,页面上有1、2、3、4、5个入口,其中1-4入口进入的页面对应4个页面,但是4个页面主要结构相同,中间商品列表样式信息不同,为了简略4图归为第二张图。入口5对应第三张图。
这几个页面需求并不复杂,大项目本身也是是基于zepto的,单单把这个模块拿出来抽离成单页形式主要为了两点考虑:
控制台毕竟涉及操作dom的需求会很多,为了兼容未来产品迭代的复杂需求。
控制台从产品最终的形态上更像是一个独立的应用,且用户进入控制台首页后所有的操作就在当前单页内进行操作,无需a链接跳转,在体验上也是很贴合用户的。
基于以上两点使用vue以及vue大礼包编写了这个单页模块。
由入口而知一共有6个页面(包含控制台首页),虽然是单页模块,也是必须要符合通过url地址直接进入对应分页的需求。在切换的过程中,留下url路由信息也方便用户进行后退操作。
首先是顶级路由的配置,六个页面(组件)分别设置六个顶级路由。
// 在App.vue文件中设置&
keep-alive :style=&{'padding-bottom': paddingBottom + 'rem'}&&&
router.map({
component: Index,
name: 'index'
'/commission': {
name: 'commission',
component: Commission
'/order': {
name: 'order',
component: Order
'/inventory': {
name: 'inventory',
component: Inventory
'/shop': {
name: 'shopList',
component: ShopList
'/qcode': {
name: 'qcode',
component: Qcode
由上图具体的佣金页面(组件)可以看到,这个页面有两个主要操作,一:搜索,根据搜索结果展示商品条目;二:翻页,根据页数展示商品条目。
原始的思路是搜索与翻页都在当前页面(组件)操作,将异步获取的数据替换当前页面的items数组。Vue会将变化的数据与view绑定,同步刷新view页面。
这样做有个缺陷,任何操作(搜索、翻页)都不会留下可追溯的路径,假设有个场景:
用户翻n页,发现了一款商品点击进入详情页(外链,属于大项目中的页面),用户返回佣金设置页面时发现又从第一页开始浏览了。
基于此类场景结合vue-router的路由查询参数功能可以换一种实现方式。
点击搜索不再在当前页面(组件)异步请求数据,而是通过$route对象进行路由跳转。
.$.router.go({
name: 'index',
keyWord: 'searchWorld',
根据上图,搜索n次,或者是点击翻页n次,都会改变当前的url的查询参数。实际上改变路由查询参数,就相当于重新进入一次当前页面(组件),Vue会识别计算是否重用当前组件,这种情况&router-view&并不会产生切换效果,因为即使查询参数变化,当前页面组件始终都是component:
Commission同一个组件。
这样设计就留下了一系列的路径,可供历史回退。
那么数据该如何获取呢?
vue-router有一个「」的概念,即在不同路由切换的过程中会有不同的钩子函数可以调用。
其中data钩子函数不管组件是否可以重用,在每次路由切换的时候都会触发。
data (transition) {
this.$http.get('/api/test/test', {
keyword: this.keyWord,
page: this.currentPage,
pageSize: this.numberPerPage
}).then((response) =& {
transition.next({
items: response.json().data.item.items,
listNumber: response.json().data.item.totalNum
}, (response) =& {
这样实现了数据的获取,参数部分依靠当前组件的$route对象获取。
上文已经提到的6大分页其实就是6个组件,但是为了在开发环境下区分资源,将这6个组件放在了views文件下内。
|-components
|-Index.vue
|-Commission.vue
|-Order.vue
由上图可知可以将1.title 2.搜索栏 3.confirm 4.toast 5.分页器 6.loading等待封装成全局组件。在main.js中进行注册。
import Toast from './components/common/Toast'
ponent('c-toast', Toast);
商品列表也可以抽离成组件,但是在每个分页里的商品列表是不同的,所以每个分页里的商品列表都独立抽离成组件,并注册在对应的分页组件里。
import ListCommission from '../components/ListCommission'
export default {
name: 'commission',
components: {
ListCommission
控制台这个模块状态并不复杂,多数状态的传递都只发生在父组件和子组件这种上下层级的关系之间。
比如Commission组件(佣金分页)中的ListCommission组件(佣金页的商品列表组件)之间的状态传递就只发生在这两级之间。
// Commission.vue
:visible=&listNumber & 0& v-if=&!$loadingRouteData& :items=&items&&&
其中items状态属于父组件,会传递到ListCommission组件内供其view展示。这种状态称为「组件本地状态」,组件本身管理自己的状态。
但是现在有这么一个功能点,生成二维码模块需要使用一个完整url路径,这个路径需要根据测试、线上环境的不同对应.net/.com。这个状态也许还有许多不同层级的组件需要使用,那么这样的状态就适合用vuex去管理。
1.首先在store中定义状态初始值
const state = {
suffix: '.net'
2.在根组件App.vue中触发action
let suffix = window.location.hostname.indexOf('showjoy.net') & -1 ? '.net' : '.com';
export const setGlobalSuffix = function ({dispatch}, suffix) {
dispatch('SET_GLOBAL_SUFFIX', suffix);
this.setGlobalSuffix(suffix);
3.触发dispatch
SET_GLOBAL_SUFFIX (state, suffix) {
state.suffix =
export function getSuffix (state) {
return state.
只要在相应组件中定义了getSuffix的getters,就可以在相应的组件中调用这个函数,获取suffix状态。此时suffix状态可称为「应用层级状态」。应用层级状态不属于任何特定的组件,但每个组件都可以监视其变化并响应式的更新DOM。
suffix状态只是一个简单的例子,像上图所示,当同级组件之间或者是不同直系关系的父子组件之间需要进行状态的变更,依赖「组件本地状态」将难以维护。
比如Commission组件需要改变Order组件的一个状态,如果不借助vuex,那么需要显示的编写事件将状态分发到上层组件App,Order组件需要监听这个事件。状态变更一多,那维护将是噩梦。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1241190次
积分:15209
积分:15209
排名:第722名
原创:115篇
转载:1660篇
评论:43条
(42)(101)(99)(137)(209)(392)(325)(117)(150)(5)(25)(88)(87)问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
我有两篇文章a和b,它们的链接分别是/post/a和post/b.但由于都属于文章,使用的是同一个组件,只是组件里的内容根据url的不同而不同。router是这样的:
'/post/:post_id': {
component: require('./components/post')
现在我在文章b里放置一个链接,点击链接访问文章a,这时会看到浏览器地址变了,但是页面并没有反应,必须再刷新一下页面(代码刷新也可)才能变成文章a。并且,在文章b和文章a之间的所有前进后退操作都无效,也是浏览器地址变了但是页面没变。我自己感觉是,因为是相同组件,实际上并没有触发vue-router. 每篇文章都是解析markdown然后传入./components/post这个组件来显示的,如果每篇文章都单独写一个组件,应该能解决问题,但是这么做得话,我就得维护markdown, vue component, router map 三个位置的文章列表,这会导致增删文章变得相当麻烦。想问下各位在我目前的结构下,有没有办法能够正常在两篇文章间切换?--------------更新-----------------抱歉,应该附加一下./components/post的内容的,组件结构为:
&template lang="jade"&
div#markdown {{{ post }}}
&/template&
import hljs from 'highlight.js'
import jq from 'jquery'
export default {
post: require('../components/articles/' + this.$route.params.post_id + '.md')
ready () { ... }
载入时根据router.params.post_id的值,改变post的值。
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
我认为你是不是把数据的读取和解析代码放错位置了。
vue的created、datavue-router的data、activated
这4个hook具体不知道哪一个比较合适,你都console.log看看吧。附文档
-------------------------方便大家查看,评论如下
多谢你的提醒,我去查了一下钩子,发现有一个canReuse方法,禁止该组件重用就行了~ almon000 · 1 天前回复 almon000:你这是quick and dirty的做法,更完美的做法是reuse之后,在一个合适的hook上load一下数据。可以减少运算量、增加用户体验。wusisu · 1 天前回复 wusisu:哦,我看到了,router 的 data 里面就专门举出了我这个例子,文档还是看的不够仔细啊。。。almon000 · 1 天前
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
楼主解决了吗 我也遇到一样的问题
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
这个数据放在computed里面啊,放在data里不行
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
相同组件,被重用。没有触发vue-router。&router-view :key="$route.path"&&/router-view&
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
赞同他说的。(声望不够,不能点赞,尴尬)
解决的思路呢,就是他说的这样。
不过具体操作的话,首先:vue-router的data、activated 这些都是vue-router 1.x的。
而vue-router 2.x 的解决方法是这个:
const Foo = {
template: `...`,
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
(我的问题的解决方案就是在这里重新get一下服务端的数据。)
该答案已被忽略,原因:
同步到新浪微博
分享到微博?
你好!看起来你挺喜欢这个内容,但是你还没有注册帐号。 当你创建了帐号,我们能准确地追踪你关注的问题,在有新答案或内容的时候收到网页和邮件通知。还能直接向作者咨询更多细节。如果上面的内容有帮助,记得点赞 (????)? 表示感谢。
明天提醒我
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:vue-router 实现分析 - CNode技术社区
这家伙很懒,什么个性签名都没有留下。
vue-router 是 Vue.js 官方的路由库,本着学习的目的,我对 vue-router 的源码进行了阅读和分析,分享出来给其他感兴趣的同学做个参考吧。
我们分别从不同的视角来看 vue-router。
从开发者角度来看,是这样的:
var router = new VueRouter({
{path: '/foo', component: {template: '&div&FOO&/div&'}},
{path: '/bar', component: {template: '&div&BAR&/div&'}}
var vm = new Vue({
el: '#app',
router: router
我们创建一个 router,传入的 routes 中的每一项即为一条路由(route)配置,表示在匹配给定的地址时,应该使用什么组件渲染视图。
将 router 传入 new Vue() 用于创建根组件,这样根组件中对应的视图区域,可以基于 router 中的配置,根据页面地址显示不同的内容。当然,这还需要在组件模板中使用 &router-view& 来定义区域。
从视图角度来看,是这样的:
&div id=&app&&
&router-view&&/router-view&
&/div&
页面地址变更后,&router-view& 对应的区域会更新为地址匹配的组件。例如,路径是 /foo 则对应区域显示 FOO,路径是 /bar 则显示 BAR,路径没有匹配的组件时,则不显示内容。
从数据角度来看,是这样的:
+ _router | $router
+ _route | $route
vm.$router 引用当前组件对应的 router 对象,该对象在初始化时(在 vm 创建过程中执行初始化),会启动对页面地址变更的监听,从而在变更时更新 vm 的数据($route),进而触发视图的更新。
如何实现对地址变更的监听?
对于缺省的 HashHistory 模式(也就是基于页面地址的 hash 部分来实现路由功能,如 /path#/foo、/path#/bar),是通过监听 hashChange 事件来实现:
window.addEventListener('hashchange', () =& {
// this.transitionTo(...)
这个动作是什么时候执行的呢?
是在 router.init()()中调用的,而 router.init() 则是在根组件创建时()调用的。
而 Vue 组件在创建时,又怎么会去调用 router.init() 呢?
这是由于 vue-router 将自身作为一个插件安装到了 Vue,通过 Vue.mixin() 注册了一个 beforeCreate() 钩子函数,从而在之后所有的 Vue 组件创建时都会调用该钩子函数,给了检查是否有 router 参数,从而进行初始化的机会。进而通过层层调用执行了监听 hashchange 事件的动作。
整理一下:
执行 vue-router 注入的 beforeCreate 钩子函数
执行 router.init(vm)
执行 history.setupListeners(),注册事件监听
地址变更如何通知到 vm?
这个过程比较简单,hashchange 时,执行 history.transitionTo(...),在这个过程中,会进行地址匹配,得到一个对应当前地址的 route,然后将其设置到对应的 vm._route 上。
只是进行了赋值,为什么 vm 就可以感知到路由的改变了呢?
答案在 vue-router 注入 Vue 的 beforeCreate 钩子函数中():
Vue.util.defineReactive(this, '_route', this._router.history.current)
采用与 Vue 本身数据相同的“数据劫持”方式,这样对 vm._route 的赋值会被 Vue 拦截到,并且触发 Vue 组件的更新渲染流程。
地址变更如果同步视图更新?
接上一步,vm._route 已经接收到路由的变更,从而触发视图更新。而当视图更新进一步调用到 &router-view& 的 render() 时,即进入了 &router-view& 的处理()。
&router-view& 的 render() 采用函数调用(h())模式,而非通过模板生成。这也是 Vue2 支持的定义组件渲染逻辑的方式,类似 React 的 render()。采用这种模式的好处是可以完全利用 JavaScript 的能力来编写逻辑,不必受制于 Vue 的类 HTML 模板语法。
这里的主要处理逻辑是从根组件中取出当前的路由对象(parent.$route),然后取得该路由下对应的组件,然后交由该组件进行渲染:
return h(component, data, children)
这其中还涉及 &router-view& 嵌套的处理,不过主要逻辑就是这样了。
其实 vue-router 从 &router-view& 的实现来看,就是一个具有特定功能的 Vue 组件而已,不过要配合根组件的 router 发挥作用。但整体还是很“响应式”的,也是蛮“Vue风格”的。
vue-router 以插件方式“侵入”Vue,从而支持一个额外的 router 属性,以提供监听并改变组件路由数据的能力。这样每次路由发生改变后,可以同步到数据,进而“响应式”地触发组件的更新。
&router-view& 作为根组件下的子组件,从根组件那里可以获取到当前的路由对象,进而根据路由对象的组件配置,取出组件并用其替换当前位置的内容。这样,也就完成整个路由变更到视图变更的过程。
路由变更到视图变更的过程整理为:
hashchange
match route
set vm._route
&router-view& render()
render matched component
实现过程中的技术点包括:
Vue 插件机制
响应式数据机制
Vue 渲染机制
地址变更监听
CNode 社区为国内最专业的 Node.js 开源技术社区,致力于 Node.js 的技术研究。
服务器赞助商为
,存储赞助商为
,由提供应用性能服务。
新手搭建 Node.js 服务器,推荐使用无需备案的问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
配置了vue-router前端路由,会直接载入.vue组件,组件内ready()部分从服务器拉数据。已经正确加载页面,尝试修改url最后一个参数(数字),希望按回车键后加载新的内容。发现要按2次回车才能正常请求。
前端路由格式为/newspost/:postid,地址栏现在为:
!/newspost/2
将2换成3,然后按回车:
!/newspost/3
则页面没有变化,chrome开f12的network发现并没有发起请求。再次到地址栏按回车(不修改数字参数),则发起请求并加载内容。如果第二次也修改了url参数,那么回车后依然不发起请求。
//detail.vue
&template&
&div id="detail"&
&h1&{{post.title}}&/h1&
&span&文章类型:[{{post.type}}]&span&
&span&正文:&/span&
{{post.content}}
&/template&
export default{
ready () {
this.$http({
url: 'http://mysite/rsite/post/' + this.$route.params.postid,
method: 'get'
}).then((response) =& {
var data = JSON.parse(response.data)
console.log(data)
if (data.code === 0) {
this.post = data.msg
路由配置文件config_route.js:
export function configRouter (router) {
router.map({
'/index': {
name: 'index',
title: '全部',
component: (resolve) =& require(['./components/index/index.vue'], resolve)
'/newspost': {
name: 'newspost',
title: '新闻资讯',
component: (resolve) =& require(['./components/newspost/index.vue'], resolve)
'/newspost/:postid': {
name: 'newspostOne',
title: '新闻资讯单条具体内容',
component: (resolve) =& require(['./components/newspost/detail.vue'], resolve)
router.redirect({
'/': 'index'
route-view组件的使用,App.vue:
&template&
&div id="app"&
&elonxi-headnav :is="signed"&&/elonxi-headnav&
&router-view&&/router-view&
&/template&
import headnav from './common/headnav'
export default {
components: {
'elonxi-headnav': headnav,
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
找到问题所在了, 自问自答。是vue-router默认重用组件导致的,要在组件内设定canReuse的返回值:
export default{
canReuse: function (transition) {
return false
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
我觉得重用组件会更好一点,因为你ready中做的东西只不过是数据的更新,所以这个组件是可以复用的,正确的使用route的钩子就好了,例如,你可以这样子:
export default{
data({ next }) {
// 异步更新数据
// 请求成功之后使用next()来继续流水线
在data钩子函数里面处理数据也是官方提倡的方法。个人认为没有必要把canReuse钩子给关掉,因为这样子性能应该会更好一点。
分享到微博?
Hi,欢迎来到 SegmentFault 技术社区!⊙▽⊙ 在这里,你可以提出编程相关的疑惑,关注感兴趣的问题,对认可的回答投赞同票;大家会帮你解决编程的问题,和你探讨技术更新,为你的回答投上赞同票。
明天提醒我
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:}

我要回帖

更多关于 vue router导航钩子 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信