vue相关官方文档记录

# vue-cli

Vue 提供了一个官方的 CLI (opens new window),为单页面应用 (SPA) 快速搭建繁杂的脚手架。

App.vue

<template>
  <div id="app">
    <transition name="fade">
      <router-view></router-view>
    </transition>
  </div>
</template>

<script>
export default {
  name: 'app',
  created () {
    this.$store.dispatch('init')
  }
}
</script>

<style lang="scss">
@import '../assets/scss/variable.scss';

#app {
  position: absolute;
  top: 0px;
  bottom: 0px;
  width: 100%;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

main.js

import Vue from 'vue'
import App from './layout/App.vue'
import router from './router'
import store from './store'
import ElementUi from 'element-ui'
import * as filters from './filters'
import '../static/theme/index.css'
import './assets/scss/index.scss'
//import 'normalize.css/normalize.css' // A modern alternative to CSS resets
//import '@/styles/index.scss' // global css

Vue.use(ElementUi)
Object.keys(filters).forEach(k => Vue.filter(k, filters[k]))
Vue.config.productionTip = false

const app = new Vue({
  el: '#app',
  router,
  store,
  ...App
})
export default app
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# vue.cli项目目录结构

  • assets文件夹是放静态资源;
  • components是放组件;
  • router是定义路由相关的配置;
  • view视图;
  • app.vue是一个应用主组件;
  • main.js是入口文件;

# vue-router

# 简介

Vue Router 是 Vue.js (opens new window) 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级; Vue 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器 (opens new window)
  • 自定义的滚动条行为
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
1
2
3

# vue-router单页面应用的切换

在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。路由模块的本质 就是建立起url和页面之间的映射关系

# 为什么不能用a标签?

至于为啥不能用a标签,这是因为用vue做的都是单页应用,就相当于只有一个主的index.html页面,所以你写的标签是不起作用的,必须使用vue-router来进行管理

# 从零开始简单的路由

如果你只需要非常简单的路由而不想引入一个功能完整的路由库,结合 HTML5 History API,你可以建立一个麻雀虽小五脏俱全的客户端路由器。可以像这样动态渲染一个页面级的组件: 完整示例 (opens new window)

window.history.pushState( null, routes[this.href], this.href )

const NotFound = { template: '<p>Page not found</p>' }
const Home = { template: '<p>home page</p>' }
const About = { template: '<p>about page</p>' }
const routes = {
  '/': Home,
  '/about': About
}
new Vue({
  el: '#app',
  data: {
    currentRoute: window.location.pathname
  },
  computed: {
    ViewComponent () {
      return routes[this.currentRoute] || NotFound
 	  //const matchingView = routes[this.currentRoute]
      //return matchingView
        ? require('./pages/' + matchingView + '.vue')
        : require('./pages/404.vue')
    }
  },
  render (h) { return h(this.ViewComponent) }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

VLink.vue

<template>
  <a
    v-bind:href="href"
    v-bind:class="{ active: isActive }"
    v-on:click="go" >
    <slot></slot>
  </a>
</template>
<script>
  import routes from '../routes'
  export default {
    props: {
      href: {
        type:String,
        required: true 
      }
    },
    computed: {
      isActive () {
        return this.href === this.$root.currentRoute
      }
    },
    methods: {
      go (event) {
        event.preventDefault()
        this.$root.currentRoute = this.href
        window.history.pushState(
          null,
          routes[this.href],
          this.href
        )
      }
    }
  }
</script>
<style scoped>
  .active {
    color: cornflowerblue;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 官方路由

对于大多数单页面应用,都推荐使用官方支持的 vue-router 库 (opens new window)

import Vue from'vue'
import VueRouter from'vue-router'
Vue.use(VueRouter)//可以利用vue的过渡属性来渲染出切换页面的效果。
1
2
3

router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import NProgress from 'nprogress'  // 页面顶部进度条
import 'nprogress/nprogress.css'
Vue.use(Router)
const routes = [{
    path: '/',
    name: '我的面板',
    component: index,
    leaf: true,
    icon: 'icon-home',
    redirect: '/home',
    children: [
      { path: '/home', component: home, name: '我的面板', meta: { page: 'home', requiresAuth: true } }
    ]
  }]
const router = new Router({
  mode: 'history',
  base: __dirname,
  routes
})
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!loginIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})
router.afterEach(transition => {
  NProgress.done()
})
export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 基础

# 起步

基础示例;

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
  <h1>Hello App!</h1>
  <p>
    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <router-link to="/foo">Go to Foo</router-link>
    <router-link to="/bar">Go to Bar</router-link>
  </p>
  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//js部分
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写) 相当于 routes: routes
})
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
  router
}).$mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

通过注入路由器,可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由: 要注意,当 <router-link> 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active

export default {// Home.vue
  computed: {
    username() {
      return this.$route.params.username// 我们很快就会看到 `params` 是什么
    }
  },
  methods: {
    goBack() {
      window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/')
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

该文档通篇都常使用 router 实例。留意一下 this.$routerrouter 使用起来完全一样。我们使用 this.$router 的原因是我们并不想在每个独立需要封装路由的组件中都导入路由

# 动态路由匹配

可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params 中。例如:

模式 匹配路径 $route.params
/user/:username /user/evan { username: 'evan' }
/user/:username/post/:post_id /user/evan/post/123 { username: 'evan', post_id: '123' }

除了 $route.params 外,$route 对象还提供了其它有用的信息,例如,$route.query (如果 URL 中有查询参数)、$route.hash 等等。你可以查看 API 文档 (opens new window) 的详细说明

常用方法

  • $route.params.id

  • route.query.q

     computed: {
        username() {
          return this.$route.params.username
        }
      }
    
    1
    2
    3
    4
    5
const User = {
 // template: '<div>User</div>'
  template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }// 动态路径参数 以冒号开头
  ]
})
1
2
3
4
5
6
7
8
9

现在呢,像 /user/foo/user/bar 都将映射到相同的路由。一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。于是,我们可以更新 User 的模板,输出当前用户的 ID:

# 响应路由参数的变化

提醒一下,当使用路由参数时,例如从 /user/foo 导航到 /user/bar原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象: 也可以beforeRouteUpdate

const User = {
  template: '...',
  watch: {
    '$route' (to, from) {
      // 对路由变化作出响应...
    }
  }
}
//或者使用 2.2 中引入的 beforeRouteUpdate 导航守卫:
const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes... don't forget to call next()
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 模式匹配

常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*):

{
  path: '*' // 会匹配所有路径
},
{
  path: '/user-*' // 会匹配以 `/user-` 开头的任意路径
}
1
2
3
4
5
6

当使用通配符路由时,请确保路由的顺序是正确的,也就是说含通配符的路由应该放在最后。路由 { path: '*' } 通常用于客户端 404 错误。如果你使用了History 模式,请确保正确配置你的服务器 (opens new window)

匹配优先级:有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。

location / {
  try_files $uri $uri/ /index.html;
}
1
2
3

当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:

// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
// 给出一个路由 { path: '*' }
this.$router.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'
1
2
3
4
5
6
# 高级模式匹配

vue-router 使用 path-to-regexp (opens new window) 作为路径匹配引擎,所以支持很多高级的匹配模式,例如:可选的动态路径参数、匹配零个或多个、一个或多个,甚至是自定义正则匹配。完整示例 (opens new window)

# 嵌套路由

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:

/user/foo/profile                     /user/foo/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+
1
2
3
4
5
6
7
8

借助 vue-router,使用嵌套路由配置,就可以很简单地表达这种关系。 要在嵌套的出口中渲染组件,需要在 VueRouter参数中使用 children 配置

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [{
          // 当 /user/:id/profile 匹配成功 // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {// 当 /user/:id/posts 匹配成功 // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        },
         // 当 /user/:id 匹配成功,// UserHome 会被渲染在 User 的 <router-view> 中
        { path: '', component: UserHome },
      ]
    }
  ]
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

要注意,以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。

当你访问 /user/foo 时,User 的出口是不会渲染任何东西,这是因为没有匹配到合适的子路由。如果你想要渲染点什么,可以提供一个 空的 子路由:

# 编程式的导航(router.xxx)

# router.push

# router.push(location, onComplete?, onAbort?)

声明式 编程式
<router-link :to="..."> router.push(...)

注意:在 Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push 这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。

当你点击 <router-link> 时,这个方法会在内部调用,所以说,点击 <router-link :to="..."> 等同于调用 router.push(...)

# 导航传递参数
router.push('home') // 字符串
router.push({ path: 'home' })// 对象
router.push({ name: 'user', params: { userId: '123' }})// 命名的路由
router.push({ path: 'register', query: { plan: 'private' }})// 变成 /register?plan=private
1
2
3
4

注意:如果提供了 pathparams 会被忽略,上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path:只有前面两种才有效;

const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
router.push({ path: '/user', params: { userId }}) // -> /user //跟上面比較 这里的params不生效
1
2
3
4

示例:跳转路由的方式:(要链接到一个命名路由) 下面两种方式都会把路由导航到 /user/123 路径完整示例 (opens new window)

方式一:可以给 router-linkto 属性传一个对象:

<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
1

方式二:这跟代码调用 router.push() 是一回事:

router.push({ name: 'user', params: { userId: 123 }})
1

# 路由传值(传参)方式

  • 组件传值; 跟rest接口请求方式的比较,方便记忆;

    {path: '/detail/:id', //路由设置;
     name: 'Detail',
     component: Detail
    }
    this.$router.push({ path:`/detail/${id}` })//携带参数跳转:
    this.$route.params.id //注:是route,而不是router
    
    {path: '/detail',
     name: 'Detail',
     component: Detail
    }
    this.$router.push({
      name: 'Detail',
      params: {
    	id: id
      }
    })
    this.$route.params.id
    
    {//对应的路由配置:
    	 path: '/detail',
    	 name: 'Detail',
    	 component: Detail
    }
    this.$router.push({//携带参数跳转:
    	path:'/detail',
    	query:{
    		id:id
    	}
    })
    this.$route.query.id //然后在子组件中进行接收:
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
  • axios传值; 跟route方式的比较;

    //前端中调用后端接口的代码:
    this.$axios.get(`http://127.0.0.1:3000/api/user/find/${id}`)
    //在后端中进行接收:
    router.get('/find/:id',(req,res)=>{
    	let id = req.params.id//接收
    })
    
    this.$axios.get(`http://127.0.0.1:3000/api/user/delete?name=${name}&age=${age}`)
    router.get('/delete'(req,res)=>{
    	let name = req.query.name;
    	let age = req.query.age;
    })
    
    this.$axios.get(`http://127.0.0.1:3000/api/user/add`,{name=this.name,age=this.age`})
    router.post('/add',(req,res)=>{
    	let name = req.body.name;
    	let age = req.body.age;
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

# 怎么定义vue-router的动态路由以及如何获取传过来的动态参数?

在router目录下的index.js文件中,对path属性加上/:id。使用route对象的params.id。

# router.replace

# router.replace(location, onComplete?, onAbort?)

router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

声明式 编程式
<router-link :to="..." replace> router.replace(...)
# router.go(n)

这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)
1
2
3
4
5
6
7
8
9
# 操作 History

你也许注意到 router.pushrouter.replacerouter.gowindow.history.pushStatewindow.history.replaceStatewindow.history.go (opens new window)好像, **实际上它们确实是效仿 window.history API 的。**因此,如果你已经熟悉 Browser History APIs (opens new window),那么在 Vue Router 中操作 history 就是超级简单的。

还有值得提及的,Vue Router 的导航方法 (pushreplacego) 在各类路由模式 (historyhashabstract) 下表现一致。

Browser History APIs

window.history.back()
window.history.forward()
window.history.go(-1)//the equivalent of calling back()
window.history.go(1)// just like calling forward()
// The following statements both have the effect of refreshing the page; 会刷新界面
window.history.go(0)
window.history.go()
//您可以通过查看length属性的值来确定历史记录堆栈中的页面数:
let numberOfEntries = window.history.length

back()
forward()
go()
pushState()
replaceState()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 命名路由name、视图router-view

命名路由name

const router = new VueRouter({
  routes: [{
      path: '/user/:userId',
      name: 'user',
      component: User
  }]
})
1
2
3
4
5
6
7

跳转路由的方式:(要链接到一个命名路由) 下面两种方式都会把路由导航到 /user/123 路径完整示例 (opens new window)

方式一:可以给 router-linkto 属性传一个对象:

<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
1

方式二:这跟代码调用 router.push() 是一回事:

router.push({ name: 'user', params: { userId: 123 }})
1
handleAddProduct () {
    this.$router.push({ path: '/product/add' });
}
handleLogin () {
    this.$refs.loginForm.validate(valid => {
        if (valid) {
            this.loading = true;
            this.loginForm.phone = this.loginForm.phone.toString();
            this.$store
                .dispatch("user/login", this.loginForm)
                .then(() => {
                this.loading = false;
                this.$router.push({ path: this.redirect || "/" });
            })
                .catch(error => {
                this.loading = false;
                console.log(error);
                this.$message.error(error.response.data.error);
            });
        } else {
            console.log("error submit!!");
            return false;
        }
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

视图router-view

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default

<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
1
2
3

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。使用 components 配置 (带上 s):

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})
1
2
3
4
5
6
7
8
9
10
11
12

嵌套命名视图:也有可能使用命名视图创建嵌套视图的复杂布局。这时需要命名用到的嵌套 router-view 组件

/settings/emails                                       /settings/profile
+-----------------------------------+                  +------------------------------+
| UserSettings                      |                  | UserSettings                 |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | |  +------------>  | | Nav | UserProfile        | |
| |     +-------------------------+ |                  | |     +--------------------+ |
| |     |                         | |                  | |     | UserProfilePreview | |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
+-----------------------------------+                  +------------------------------+
1
2
3
4
5
6
7
8
9
  • Nav 只是一个常规组件。
  • UserSettings 是一个视图组件
  • UserEmailsSubscriptionsUserProfileUserProfilePreview嵌套的视图组件

注意我们先忘记 HTML/CSS 具体的布局的样子,只专注在用到的组件上。

UserSettings 组件的 <template> 部分应该是类似下面的这段代码:

<!-- UserSettings.vue -->
<div>
  <h1>User Settings</h1>
  <NavBar/>
  <router-view/>
  <router-view name="helper"/>
</div>
1
2
3
4
5
6
7

嵌套的视图组件在此已经被忽略了,但是你可以在这里 (opens new window)找到完整的源代码。

然后你可以用这个路由配置完成该布局:

{
  path: '/settings',
  component: UserSettings,// 你也可以在顶级路由就配置命名视图
  children: [{
    path: 'emails',
    component: UserEmailsSubscriptions
    }, 
   {
    path: 'profile',
    components: {
      default: UserProfile,
      helper: UserProfilePreview
    }
  }]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 重定向、别名、路由参数

重定向 : 注意导航守卫 (opens new window)并没有应用在跳转路由上,而仅仅应用在其目标上。在下面这个例子中,为 /a 路由添加一个 beforeEachbeforeLeave 守卫并不会有任何效果。 完整示例 (opens new window)

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' },//从 /a 重定向到 /b
    { path: '/a', redirect: { name: 'foo' }},//重定向的目标也可以是一个命名的路由
    { path: '/a', redirect: to => {//甚至是一个方法,动态返回重定向目标
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]
})
1
2
3
4
5
6
7
8
9
10

别名 : “重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b

/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。 ** “别名”的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构**。

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})
1
2
3
4
5
# 路由组件传参

使用 props 将组件和路由解耦:

取代与 $route 的耦合

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})
1
2
3
4
5
6
7
8

通过 props 解耦 : 这样你便可以在任何地方使用该组件,使得该组件更易于重用和测试。

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
  mode: 'history',
  base: __dirname,
  routes: [
    { path: '/user/:id', component: User, props: true },
    // 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
    { path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false }
    }
  ]
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

各种模式处理: 完整示例 (opens new window)

//布尔模式;如果 props 被设置为 true,route.params 将会被设置为组件属性

//对象模式;如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用。
const router = new VueRouter({
  routes: [
    { path: '/promotion/from', component: Promotion, props: { newsletterPopup: false } }
  ]
})

//函数模式; 可以创建一个函数返回props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。
const router = new VueRouter({
  routes: [//URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件。
    { path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
  ]//请尽可能保持 props 函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props,请使用包装组件,这样 Vue 才可以对状态变化做出反应。
})

const router = new VueRouter({
  mode: 'history',
  base: __dirname,
  routes: [
    { path: '/', component: Hello }, // No props, no nothing
    { path: '/hello/:name', component: Hello, props: true }, 
    { path: '/static', component: Hello, props: { name: 'world' }}, // static values
    { path: '/dynamic/:years', component: Hello, props: dynamicPropsFn },
    { path: '/attrs', component: Hello, props: { name: 'attrs' }}
  ]
})
new Vue({
  router,
  template: `
    <div id="app">
      <h1>Route props</h1>
      <ul>
        <li><router-link to="/">/</router-link></li>
        <li><router-link to="/hello/you">/hello/you</router-link></li>
        <li><router-link to="/static">/static</router-link></li>
        <li><router-link to="/dynamic/1">/dynamic/1</router-link></li>
        <li><router-link to="/attrs">/attrs</router-link></li>
      </ul>
      <router-view class="view" foo="123"></router-view>
    </div>
  `
}).$mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# HTML5 History 模式

vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})
1
2
3
4

当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

# 后端配置

nginx 常用; try_files $uri $uri/ /index.html;

server {
    listen       80;
    root /home/samy/deploy/xx-web/vue/;
    server_name  web.xx.cn;
    location /api/ {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://localhost:7002;
        proxy_redirect off;
    }
    location /uploads/ {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://localhost:7002;
        proxy_redirect off;
    }
    location /socket.io/ {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_pass http://localhost:7002;
    }

    location / {
          # index  index.html;
          try_files $uri $uri/ /index.html;
       }
     location /api/1/statistics/person/ {
          return   500;
      }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

原生 Node.js 测试开发模式用

const http = require('http')
const fs = require('fs')
const httpPort = 80
http.createServer((req, res) => {
  fs.readFile('index.htm', 'utf-8', (err, content) => {
    if (err) {
      console.log('We cannot open "index.htm" file.')
    }
    res.writeHead(200, {
      'Content-Type': 'text/html; charset=utf-8'
    })
    res.end(content)
  })
}).listen(httpPort, () => {
  console.log('Server listening on: http://localhost:%s', httpPort)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

基于 Node.js 的 Express

对于 Node.js/Express,请考虑使用 connect-history-api-fallback 中间件 (opens new window)

koa上的设置: router.get('/*', controller.zview.index) 放在最后;

async index () {
    const {ctx, config } = this
    const pathR = path.join(config.baseDir, './public/web/index.html')
    ctx.set('Content-Type', 'text/html; charset=utf-8')
    ctx.body = fs.createReadStream(pathR)
}
1
2
3
4
5
6
# 警告 *

给个警告,因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。 推荐

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '*', component: NotFoundComponent }
  ]
})
1
2
3
4
5
6

或者,如果你使用 Node.js 服务器,你**可以用服务端路由匹配到来的 URL,并在没有匹配到路由的时候返回 404,以实现回退。**更多详情请查阅 Vue 服务端渲染文档 (opens new window)

# 路由模式种类

mode

  • 类型: string
  • 默认值: "hash" (浏览器环境) | "abstract" (Node.js 环境)
  • 可选值: "hash" | "history" | "abstract"

vue-router 有 3 种路由模式:hash(默认模式)、history、abstract;

其中,3 种路由模式的说明如下:

  • hash: 使用 URL hash 值来作路由支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
    • 特点:hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,不需要后台进行配置,因此改变hash不会重新加载页面
  • history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
    • 特点:(需要特定浏览器支持)。history模式改变了路由地址,因为需要后台配置地址
  • abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

对应的源码如下所示:

switch (mode) {
  case 'history':
	this.history = new HTML5History(this, options.base)
	break
  case 'hash':
	this.history = new HashHistory(this, options.base, this.fallback)
	break
  case 'abstract':
	this.history = new AbstractHistory(this, options.base)
	break
  default:
	if (process.env.NODE_ENV !== 'production') {
	  assert(false, `invalid mode: ${mode}`)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

对于 Vue 这类渐进式前端开发框架,为了构建 SPA(单页面应用),需要引入前端路由系统,这也就是 Vue-Router 存在的意义。前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求资源。

为了达到这一目的,浏览器当前提供了一下两种方法

  1. hash--即地址栏 URL 中的(#)符号

    特点:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面

  2. history--利用了 HTML5 History Interface 中新增的 pushState()replaceState() 方法

    这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forword、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求;

因此可以说,hash 模式和 history 模式都属于浏览器自身的特性,Vue-Router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由

# 路由模式实现原理【要点】

常用的 hash 和 history 模式

(1)hash 模式的实现原理

早期的前端路由的实现**就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。**比如下面这个网站,它的 location.hash 的值为 '#search':

https://www.word.com#search
1

hash 路由模式的实现主要是基于下面几个特性

  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
  • hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
  • 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
  • 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变化。vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面

其中做最主要的 API 有以下两个:history.pushState()history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
1
2

history 路由模式的实现主要基于存在下面几个特性

  • pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
  • 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染)
  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

你也许注意到 router.pushrouter.replacerouter.gowindow.history.pushStatewindow.history.replaceStatewindow.history.go类似;

# hash 模式和 history 模式的区别【要点】

  • url 展示上,hash 模式有“#”,history 模式没有
  • 刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由try_files $uri $uri/ /index.html;
  • 兼容性。hash 可以支持低版本浏览器和 IE。

# 进阶

# 导航守卫(设置中间件)

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的

记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象 (opens new window)来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

# Vue导航守卫(路由生命周期)分类【要点】

导航钩子函数;

  • 全局导航钩子确保要调用 next 方法,否则钩子就不会被 resolved。

    • router.beforeEach(to,from,next),作用:跳转前进行判断拦截。【常用】

    • router.beforeResolve;在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

    • router.afterEach【常用】

      router.afterEach(() => {
        NProgress.done()
      })
      
      1
      2
      3
  • 单独路由独享组件

    • beforeEnter
  • 组件内的钩子

    • beforeRouteEnter
    • beforeRouteUpdate
    • beforeRouteLeave

参数: 有to(去的那个路由)、from(离开的路由)、**next(一定要用这个函数才能去到下一个路由,如果不用就拦截)**最常用就这几种。

注:参数或查询的改变并不会触发进入/离开的导航守卫 (你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。)

# 完整的导航解析流程
  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

# 全局前置守卫及参数
router.beforeEach((to, from, next) => {// ...
})
1
2

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象 (opens new window)
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: truename: 'home' 之类的选项以及任何用在 router-linkto prop (opens new window)router.push (opens new window) 中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() (opens new window) 注册过的回调。

确保要调用 next 方法,否则钩子就不会被 resolved。

# 全局解析守卫

在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

# 全局后置钩子

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身

router.afterEach((to, from) => {// ...
})
1
2
# 路由独享的守卫

你可以在路由配置上直接定义 beforeEnter 守卫:

const router = new VueRouter({
  routes: [{
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {// ...
      }
    }
  ]
})
1
2
3
4
5
6
7
8
9

这些守卫与全局前置守卫的方法参数是一样的。

# 组件内的守卫

最后,你可以在路由组件内直接定义以下路由导航守卫:

  • beforeRouteEnter
    • 注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdatebeforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave
const Foo = {
    template: `...`,
    beforeRouteEnter (to, from, next) {
        // 在渲染该组件的对应路由被 confirm 前调用
        // 不!能!获取组件实例 `this`
        // 因为当守卫执行前,组件实例还没被创建
        next(vm => {//不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
            // 通过 `vm` 访问组件实例
        })
       getPost(to.params.id, (err, post) => {
          next(vm => vm.setData(err, post))
       })
    },
    beforeRouteUpdate (to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`
        this.name = to.params.name
        next()
    },
    beforeRouteLeave (to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
        //这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
        const answer = window.confirm('Do you want to leave? you have unsaved changes!')
        if (answer) {
            next()
        } else {
            next(false)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

例子展示在全局导航守卫中检查元字段router/index.js中设置路由处理中间件;或者在 permission.js

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {next()}
  } else {next()} // 确保一定要调用 next()
})

const whiteList = ['/login', '/auth-redirect']
router.beforeEach(async (to, from, next) => {
  NProgress.start()
  document.title = getPageTitle(to.meta.title)
  const hasToken = getToken()
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {}
  } else { // 在免登录白名单,直接进入
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else { // 否则全部重定向到登录页
      next(`/login?redirect=${to.path}`)
      // next('/login')
      NProgress.done()
    }
  }
})
router.afterEach(() => {
  NProgress.done()
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

此外,还可以参照下面数据获取的示例

# 路由元信息 meta

定义路由的时候可以配置 meta 字段:

首先,我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录

例如,根据上面的路由配置,/foo/bar 这个 URL 将会匹配父路由记录以及子路由记录。

一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。

示例:见上面处理;

# 数据获取

有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:

  • 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
  • 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。假设我们有一个 Post 组件,需要基于 $route.params.id 获取文章数据:

导航完成后获取数据 【常用】

当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 created 钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。现在都是用ele组件处理;

<template>
  <div class="post">
    <div v-if="loading" class="loading">
      Loading...
    </div>
    <div v-if="error" class="error">
      {{ error }}
    </div>
    <div v-if="post" class="content">
      <h2>{{ post.title }}</h2>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>
export default {
  data () {
    return {
      loading: false,
      post: null,
      error: null
    }
  },
  created () {// 组件创建完后获取数据,此时 data 已经被 observed 了
    this.fetchData()
  },
  watch: {// 如果路由有变化,会再次执行该方法
    '$route': 'fetchData'
  },
  methods: {
    fetchData () {
      this.error = this.post = null
      this.loading = true
      // replace getPost with your data fetching util / API wrapper
      getPost(this.$route.params.id, (err, post) => {
        this.loading = false
        if (err) {
          this.error = err.toString()
        } else {
          this.post = post
        }
      })
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template>
  <div class="app-container">
    <!-- <el-button type="primary" size="mini" align="right" @click="handleCreate">添加 </el-button> -->
    <el-table
      v-loading="listLoading"
      :data="data"
      element-loading-text="Loading"
      border
      fit
      highlight-current-row
    > </el-table>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13

在导航完成前获取数据

通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法

export default {
  data () {
    return {
      post: null,
      error: null
    }
  },
  beforeRouteEnter (to, from, next) {
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },
  // 路由改变前,组件就已经渲染完了; 逻辑稍稍不同
  beforeRouteUpdate (to, from, next) {
    this.post = null
    getPost(to.params.id, (err, post) => {
      this.setData(err, post)
      next()
    })
  },
  methods: {
    setData (err, post) {
      if (err) {
        this.error = err.toString()
      } else {
        this.post = post
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。

# 过渡动效

<router-view> 是基本的动态组件,所以我们可以用 <transition> 组件给它添加一些过渡效果:

# 单个路由过渡
<transition>
  <router-view></router-view>
</transition>
//上面的用法会给所有路由设置一样的过渡效果,如果你想让每个路由组件有各自的过渡效果,可以在各路由组件内使用 transition并设置不同的 name。
const Foo = {
  template: `
    <transition name="slide">
      <div class="foo">...</div>
    </transition>
  `
}
const Bar = {
  template: `
    <transition name="fade">
      <div class="bar">...</div>
    </transition>
  `
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 路由动态过渡

示例:完整参照 (opens new window)

<!-- 使用动态的 transition name -->
<transition :name="transitionName">
  <router-view></router-view>
</transition>
// 接着在父组件内
// watch $route 决定使用哪种过渡
watch: {
  '$route' (to, from) {
    const toDepth = to.path.split('/').length
    const fromDepth = from.path.split('/').length
    this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 滚动行为 scrollBehavior

使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。

注意: 这个功能只在支持 history.pushState 的浏览器中可用。

scrollBehavior 方法接收 tofrom 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。

这个方法返回滚动位置的对象信息,长这样:

  • { x: number, y: number }
  • { selector: string, offset? : { x: number, y: number }} (offset 只在 2.6.0+ 支持)

如果返回一个 falsy (译者注:falsy 不是 false参考这里 (opens new window))的值,或者是一个空对象,那么不会发生滚动。

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {// return 期望滚动到哪个的位置
    if (savedPosition) {
       return savedPosition //返回savedPosition,在按下`后退/前进`按钮时,就会像浏览器的原生表现那样
     } else {
       return { x: 0, y: 0 }//对于所有路由导航,简单地让页面滚动到顶部。
     }
  }
})
scrollBehavior (to, from, savedPosition) {
  if (to.hash) {//要模拟“滚动到锚点”的行为
    return {
      selector: to.hash
    }
  }
}

const createRouter = () => new Router({
  mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }), //默认的直接设置y:0即可。完全设置考虑,设置为{ x: 0, y: 0 }
  routes: constantRoutes
  // base: __dirname,
})
const router = createRouter()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 异步滚动

2.8.0 新增 也可以返回一个 Promise 来得出预期的位置描述:

scrollBehavior (to, from, savedPosition) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ x: 0, y: 0 })
    }, 500)
  })
}
1
2
3
4
5
6
7

将其挂载到从页面级别的过渡组件的事件上,令其滚动行为和页面过渡一起良好运行是可能的。但是考虑到用例的多样性和复杂性,我们仅提供这个原始的接口,以支持不同用户场景的具体实现。

# 路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

结合 Vue 的异步组件 (opens new window)和 Webpack 的代码分割功能 (opens new window),轻松实现路由组件的懒加载。

# 异步加载

首先,可以将异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件本身); 第二,在 Webpack 2 中,我们可以使用动态 import (opens new window)语法来定义代码分块点 (split point):

注意:如果使用的是 Babel,将需要添加 syntax-dynamic-import (opens new window) 插件,才能使 Babel 可以正确地解析语法。

const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
//webpack2 优化后
import('./Foo.vue') // 返回 Promise
1
2
3

完整示例及优化后简写;

const Foo = () => import('./Foo.vue')
const router = new Router({
  routes: [
    { path: '/foo', component: Foo }
  ]
})
//优化后简化;
export const constantRoutes = [{
    path: '/device',
    component: Layout,
    redirect: '/device/index',
    name: 'Device',
    children: [{
        path: 'index',
        name: 'deviceIndex',
        component: () => import('@/views/device/index'),
        meta: { title: '设备管理', icon: 'device' }
      }
    ]
  }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 把组件按组分块

有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk (opens new window),一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。

const home = () => import(/* webpackChunkName: "home" */ '../pages/home/index')
const routes = [{
    path: '/',
    name: '我的面板',
    component: index,
    leaf: true,
    icon: 'icon-home',
    redirect: '/home',
    children: [{ 
        path: '/home', component: home, name: '我的面板', meta: { page: 'home', requiresAuth: true } }
    ]
  },
1
2
3
4
5
6
7
8
9
10
11
12

Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中

# Vue 路由懒加载【要点】

Vue项目中实现路由按需加载(路由懒加载)的3中方式:

  • vue异步组件;

    {
    		path: '/home',
    		name: 'Home',
    		component: resolve => reqire(['path路径'], resolve)
    	}
    
    1
    2
    3
    4
    5
  • es6提案的import();

    const Home = () => import('path路径')
    
    1
  • webpack的require.ensure();

    {
    		path: '/home',
    		name: 'Home',
    		component: r => require.ensure([],() =>  r(require('path路径')), 'demo')
    	}
    
    1
    2
    3
    4
    5

# 其他相关

# 常用的组件

  • <router-link>
  • <router-view>

# API相关

https://router.vuejs.org/zh/api

<router-link> 组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。

<router-link> 比起写死的 <a href="...">会好一些,理由如下:

  • 无论是 HTML5 history 模式还是 hash 模式,它的表现行为一致,所以,当你要切换路由模式,或者在 IE9 降级使用 hash 模式,无须作任何变动。
  • 在 HTML5 history 模式下,router-link 会守卫点击事件,让浏览器不再重新加载页面。
  • 当你在 HTML5 history 模式下使用 base 选项之后,所有的 to 属性都不需要写 (基路径) 了。
# v-slot API (3.1.0 新增)

router-link 通过一个作用域插槽 (opens new window)暴露底层的定制能力。这是一个更高阶的 API,主要面向库作者,但也可以为开发者提供便利,多数情况用在一个类似 NavLink 这样的组件里。

在使用 v-slot API 时,需要向 router-link 传入一个单独的子元素。否则 router-link 将会把子元素包裹在一个 span 元素内。

<router-link to="/about" v-slot="{ href, route, navigate, isActive, isExactActive }">
  <NavLink :active="isActive" :href="href" @click="navigate">{{ route.fullPath </NavLink>
</router-link>
1
2
3
  • href:解析后的 URL。将会作为一个 a 元素的 href attribute。
  • route:解析后的规范化的地址。
  • navigate:触发导航的函数。会在必要时自动阻止事件,和 router-link 同理。
  • isActive:如果需要应用激活的 class (opens new window) 则为 true。允许应用一个任意的 class。
  • isExactActive:如果需要应用精确激活的 class (opens new window) 则为 true。允许应用一个任意的 class。

示例:将激活的 class 应用在外层元素; 把激活的 class 应用到一个外部元素而不是 `` 标签本身,这时你可以在一个 router-link 中包裹该元素并使用 v-slot property 来创建链接:

提示:如果你在 <a> 元素上添加一个 target="_blank",则 @click="navigate" 处理器会被忽略。

<router-link
  to="/foo"
  v-slot="{ href, route, navigate, isActive, isExactActive }"
>
  <li
    :class="[isActive && 'router-link-active', isExactActive && 'router-link-exact-active']"
  >
    <a :href="href" @click="navigate">{{ route.fullPath }}</a>
  </li>
</router-link>
1
2
3
4
5
6
7
8
9
10

active-class:类型: string;默认值: "router-link-active"设置链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。

# active-class是哪个组件的属性?

vue-router模块的router-link组件。

<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 渲染结果 -->
<a href="home">Home</a>

<!-- 使用 v-bind 的 JS 表达式 -->
<router-link v-bind:to="'home'">Home</router-link>

<!-- 不写 v-bind 也可以,就像绑定别的属性一样 -->
<router-link :to="'home'">Home</router-link>

<!-- 同上 -->
<router-link :to="{ path: 'home' }">Home</router-link>

<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

<!-- 带查询参数,下面的结果为 /register?plan=private -->
<router-link :to="{ path: 'register', query: { plan: 'private' }}"
  >Register</router-link
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

其他示例:

<router-link class="inlineBlock" to="/stock/index">
    <el-button type="primary" icon="el-icon-goods" round>库存查询</el-button>
</router-link>

<ul>
    <router-link tag="li" :to="{name: 'Topic', query: $route.query}">会议议题</router-link>
    <router-link tag="li" :to="{name: 'Material', query: $route.query}">会议资料</router-link>
</ul>

handleAddProduct () {
this.$router.push({ path: '/product/add' });
},
1
2
3
4
5
6
7
8
9
10
11
12

# <router-view>

路由嵌套会将其他组件渲染到该组件内,而不是进行整个页面跳转。router-view本身就是将组件渲染到该位置;

<router-view> 组件是一个 functional 组件,渲染路径匹配到的视图组件。<router-view> 渲染的组件还可以内嵌自己的 <router-view>,根据嵌套路径,渲染嵌套组件。其他属性 (非 router-view 使用的属性) 都直接传给渲染的组件, 很多时候,每个路由的数据都是包含在路由参数中。

因为它也是个组件,所以可以配合<transition><keep-alive> 使用。如果两个结合一起用,要确保在内层使用 <keep-alive>

<transition>
  <keep-alive>
    <router-view></router-view>
  </keep-alive>
</transition>
1
2
3
4
5
# <router-view> Props(属性)

name

  • 类型: string

  • 默认值: "default"

    如果 ``设置了名称,则会渲染对应的路由配置中 components 下的相应组件。查看 命名视图 (opens new window) 中的例子。

# Router 构建选项

# Router 实例属性

# Router 实例方法

# 路由对象

一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息,还有 URL 匹配到的路由记录 (route records)

路由对象是不可变 (immutable) 的,每次成功的导航后都会产生一个新的对象

路由对象出现在多个地方:

  • 在组件内,即 this.$route

  • $route 观察者回调内

  • router.match(location) 的返回值

  • 导航守卫的参数:

    router.beforeEach((to, from, next) => {
      // `to` 和 `from` 都是路由对象
    })
    
    1
    2
    3
  • scrollBehavior 方法的参数:

    const router = new VueRouter({
      scrollBehavior(to, from, savedPosition) {
        // `to` 和 `from` 都是路由对象
      }
    })
    
    1
    2
    3
    4
    5
# 路由对象属性
  • $route.path

    • 类型: string

      字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"

  • $route.params

    • 类型: Object

      一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。

  • $route.query

    • 类型: Object

      一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。

  • $route.hash

    • 类型: string

      当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。

  • $route.fullPath

    • 类型: string

      完成解析后的 URL,包含查询参数和 hash 的完整路径。

  • $route.matched

    • 类型: Array

    一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)。

    const router = new VueRouter({
      routes: [// 下面的对象就是路由记录
        {
          path: '/foo',
          component: Foo,
          children: [// 这也是个路由记录
            { path: 'bar', component: Bar }
          ]
        }
      ]
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    当 URL 为 /foo/bar$route.matched 将会是一个包含从上到下的所有对象 (副本)。

  • $route.name

    当前路由的名称,如果有的话。(查看命名路由 (opens new window))

  • $route.redirectedFrom

    如果存在重定向,即为重定向来源的路由的名字。(参阅重定向和别名 (opens new window))

# 组件注入

# 注入的属性

通过在 Vue 根实例的 router 配置传入 router 实例,下面这些属性成员会被注入到每个子组件。

  • this.$router router 实例。
  • this.$route 当前激活的路由信息对象 (opens new window)。这个属性是只读的,里面的属性是 immutable (不可变) 的,不过你可以 watch (监测变化) 它。
# 增加的组件配置选项
  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave

# 项目示例

App.vue

<template>
  <div id="app">
    <router-view />
  </div>
</template>
<script>
export default {
  name: "App"
};
</script>
1
2
3
4
5
6
7
8
9
10

router/index.js 通过import的方式按需动态加载,提高性能;不会像老版本那样,全部加载在头部;

import Vue from 'vue'
import Router from 'vue-router'
/* Layout */
import Layout from '@/layout'
// import Layout from '../views/layout/Layout'
Vue.use(Router)
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: 'Dashboard',
    hidden: true,
    children: [{
      path: 'dashboard',
      // name: 'Dashboard',
      // component: () => import('@/views/dashboard/index'),
      component: () => import('@/views/index')
      // meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  },
  { path: '*', redirect: '/404', hidden: false }
]
const createRouter = () => new Router({
  mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
  // base: __dirname,
})
const router = createRouter()
export function resetRouter () { // 现在用在退出后,重置路由; 动态替换路由;
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}
export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { resetRouter } from '@/router'
  logout ({ commit, state }) {
    return new Promise((resolve, reject) => {
      // logout(state.token).then(() => {
      commit('SET_TOKEN', '')
      commit('SET_ROLES', [])
      removeToken()
      resetRouter()
      resolve()
      // }).catch(error => {
      //   reject(error)
      // })
    })
  },
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 问题

# route 和 router 的区别

route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。

router是“路由实例对象”,包括了路由的跳转方法(push、replace),钩子函数等。

# vuex

# 简介

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式, 可以理解为全局的数据管理每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

# 几个核心模块【要点】

vuex主要由五部分组成:state、 getters、mutations、actions、module组成。

主要包括以下几个模块:(sgmam) + mapG amsg m

  • State:定义了应用状态的数据结构,可以在这里设置默认的初始状态
  • Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性
  • Mutation:是唯一更改 store 中状态的方法,且必须是同步函数
  • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作
  • Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中

使用流程是: 组件中可以直接调用上面五个部分除了module; Vuex 适用于 父子、隔代、兄弟组件通信

组件不允许直接修改属于 store 实例的 state,而应执行 action 来分发 (dispatch) 事件通知 store 去改变,我们最终达成了 Flux (opens new window) 架构。这样约定的好处是,我们能够记录所有 store 中发生的 state 改变,同时实现能做到记录变更 (mutation)、保存状态快照、历史回滚/时光旅行的先进的调试工具

应用级的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事务异步逻辑应该封装在action中

项目中的实践步骤:

//全局设置(简单方式); store/index.js
Vue.use(Vuex)
//设置state
const state = {
  user: null
}
export default new Vuex.Store({
  state,
  getters,
  actions,
  mutations
})
// actions用户信息初始化
export const init = async ({ state, commit }, user) => {
  if (!state.user) {//获取store中的user
    const res = await service.getAuth()
    if (res.code === 0) commit('INIT', { ...res.data })
  } else commit('INIT', { ...user })//提交到mutations上
}
//mutations
export default {
  'INIT' (state, data) {//保存到state中
    state.user = { ...data }
  }
}
//getters
export const user = state => {
  return state.user//获取store中的user
}
//外部触发
  created () {
    this.$store.dispatch('init')
  }

//外部获取
if (store.getters.token) {
  config.headers['Authorization'] = getToken()
}
  computed: {
    ...mapGetters(["name", "device"])
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 不用Vuex会带来什么问题?

可维护性会下降,想修改数据要维护三个地方;

可读性会下降,因为一个组件里的数据,根本就看不出来是从哪来的;

增加耦合,大量的上传派发,会让耦合性大大增加,本来Vue用Component就是为了减少耦合,现在这么用,和组件化的初衷相背。

# 模块详解

store/index.js 方式一:直接差分;

import Vue from "vue";
import Vuex from "vuex";
import state from './state'
import actions from './actions'
import mutations from './mutations'
import getters from "./getters"
Vue.use(Vuex);
export default new Vuex.Store({
    state,
    getters,
    mutations,
    actions,
});
1
2
3
4
5
6
7
8
9
10
11
12
13

# module,可有无

store/index.js 方式二:通过module分散到子模块;然后单一的 Store 拆分为多个 store 且同时保存在单一的状态树中

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'

import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
import permission from './modules/permission'

Vue.use(Vuex)
const store = new Vuex.Store({
  getters,
  modules: {
    app,
    settings,
    user,
    permission
  }
  // actions,
  // mutations
})
export default store
//单个modules目录下的store导出;
export default {
  namespaced: true,
  state,
  mutations,
  actions
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# state

类似vue 对象的data, 用来存放数据以及状态。存放的数据为响应式,如果数据改变,那么依赖数据的组件也会发生相应的改变。

示例:

const state = {
  token: getToken(),
  name: '',
  avatar: '',
  roles: []
}
1
2
3
4
5
6

# getters

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算

const getters = {
    getSearchRateResult: state => state.searchRateResult,
    getRateUserInfo: state => state.RateUserInfo,
}

import { mapGetters } from 'vuex'
computed: {
    ...mapGetters({//在组件中获取;
        UserInfo: 'login/UserInfo', // 用户信息
        RateUserInfo: 'getRateUserInfo' // Rate用户信息
    })
}
import store from '@/store'
store.getters['getRateUserInfo']//单个获取
if (store.getters.token) {
    config.headers['Authorization'] = getToken()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

注意:可以通过mapState把全局的state和 getters 映射到当前组件的 computed计算属性中。

# mapGetters

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

# actions

Action 通过 store.dispatch 方法触发:action支持异步调用(可以调用api),mutation只支持操作同步,并且action提交的是 mutation,而不是直接变更状态。

例如:actionsmutations联合使用;

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。

实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):

actions:{//上面actions的简写方式;
  increment ({ commit }){
    commit('increment')
  }
}

//主动发送触发命令;自动触发actions,通过dispatch的方式;前面路径是命名空间;
  methods: {
    toggleSideBar () {
      this.$store.dispatch('app/toggleSideBar')
    },
    async logout () {
      await this.$store.dispatch('user/logout')
      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
    }
  }

//modules/user.js
const actions = {
  logout ({ commit, state }) {
    return new Promise((resolve, reject) => {
      // logout(state.token).then(() => {
      commit('SET_TOKEN', '')
      commit('SET_ROLES', [])
      removeToken()
      resetRouter()
      resolve()
      // }).catch(error => {
      //   reject(error)
      // })
    })
  }
}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# mutation

每个 mutation 都有一个字符串的 事件类型(type) 和一个 回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

const mutations = {
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  SET_NAME: (state, name) => {
    state.name = name
  },
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  },
  SET_ROLES: (state, roles) => {
    state.roles = roles
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# API相关

# Vuex.Store

import Vuex from 'vuex'
const store = new Vuex.Store({ ...options })
1
2

# Vuex.Store 构造器选项

# state
# mutations
  • 类型: { [type: string]: Function }

    在 store 上注册 mutation,处理函数总是接受 state 作为第一个参数(如果定义在模块中,则为模块的局部状态),payload 作为第二个参数(可选)。详细介绍 (opens new window)

# actions
  • 类型: { [type: string]: Function }

    在 store 上注册 action。处理函数总是接受 context 作为第一个参数,payload 作为第二个参数(可选)

    context 对象包含以下属性:

    {
      state,      // 等同于 `store.state`,若在模块中则为局部状态
      rootState,  // 等同于 `store.state`,只存在于模块中
      commit,     // 等同于 `store.commit`
      dispatch,   // 等同于 `store.dispatch`
      getters,    // 等同于 `store.getters`
      rootGetters // 等同于 `store.getters`,只存在于模块中
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    同时如果有第二个参数 payload 的话也能够接收。详细介绍 (opens new window)

# getters
  • 类型: { [key: string]: Function }

在 store 上注册 getter,getter 方法接受以下参数:

state,     // 如果在模块中定义则为模块的局部状态
getters,   // 等同于 store.getters
1
2

当定义在一个模块里时会特别一些:

state,       // 如果在模块中定义则为模块的局部状态
getters,     // 等同于 store.getters
rootState    // 等同于 store.state
rootGetters  // 所有 getters
1
2
3
4

注册的 getter 暴露为 store.getters详细介绍 (opens new window)

# modules
  • 类型: Object

    包含了子模块的对象,会被合并到 store,大概长这样:

    {
      key: {
        state,
        namespaced?,
        mutations,
        actions?,
        getters?,
        modules?
      },
      ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    与根模块的选项一样,每个模块也包含 statemutations 选项。模块的状态使用 key 关联到 store 的根状态。模块的 mutation 和 getter 只会接收 module 的局部状态作为第一个参数,而不是根状态,并且模块 action 的 context.state 同样指向局部状态。详细介绍 (opens new window)

# plugins
  • 类型: Array

    一个数组,包含应用在 store 上的插件方法。这些插件直接接收 store 作为唯一参数,可以监听 mutation(用于外部地数据持久化、记录或调试)或者提交 mutation (用于内部数据,例如 websocket 或 某些观察者)详细介绍 (opens new window)

# strict
  • 类型: Boolean

  • 默认值: false

    使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数以外修改 Vuex state 都会抛出错误。

    详细介绍 (opens new window)

# devtools
  • 类型:Boolean

    为某个特定的 Vuex 实例打开或关闭 devtools。对于传入 false 的实例来说 Vuex store 不会订阅到 devtools 插件。可用于一个页面中有多个 store 的情况。

    {
      devtools: false
    }
    
    1
    2
    3

# Vuex.Store 实例属性

state:类型: Object 根状态,只读。

getters:类型: Object 暴露出注册的 getter,只读。

# Vuex.Store 实例方法

# commit
# dispatch
# replaceState
  • replaceState(state: Object)

替换 store 的根状态,仅用状态合并或时光旅行调试。

# watch
  • watch(fn: Function, callback: Function, options?: Object): Function

响应式地侦听 fn 的返回值,当值改变时调用回调函数。fn 接收 store 的 state 作为第一个参数,其 getter 作为第二个参数。最后接收一个可选的对象参数表示 Vue 的 vm.$watch (opens new window) 方法的参数。

要停止侦听,调用此方法返回的函数即可停止侦听。

# subscribe
  • subscribe(handler: Function): Function

    订阅 store 的 mutation。handler 会在每个 mutation 完成后调用,接收 mutation 和经过 mutation 后的状态作为参数:

    store.subscribe((mutation, state) => {
      console.log(mutation.type)
      console.log(mutation.payload)
    })
    
    1
    2
    3
    4

要停止订阅,调用此方法返回的函数即可停止订阅。

通常用于插件。详细介绍 (opens new window)

# subscribeAction
  • subscribeAction(handler: Function): Function

    2.5.0 新增

    订阅 store 的 action。handler 会在每个 action 分发的时候调用并接收 action 描述和当前的 store 的 state 这两个参数:

    store.subscribeAction((action, state) => {
      console.log(action.type)
      console.log(action.payload)
    })
    
    1
    2
    3
    4

    要停止订阅,调用此方法返回的函数即可停止订阅。

    3.1.0 新增

    从 3.1.0 起,subscribeAction 也可以指定订阅处理函数的被调用时机应该在一个 action 分发之前还是之后 (默认行为是之前):

    store.subscribeAction({
      before: (action, state) => {
        console.log(`before action ${action.type}`)
      },
      after: (action, state) => {
        console.log(`after action ${action.type}`)
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8

    该功能常用于插件。详细介绍 (opens new window)

# registerModule
  • registerModule(path: string | Array, module: Module, options?: Object)

    注册一个动态模块。详细介绍 (opens new window)

    options 可以包含 preserveState: true 以允许保留之前的 state。用于服务端渲染。

# unregisterModule
# hotUpdate

# 组件绑定的辅助函数

# mapState
  • mapState(namespace?: string, map: Array | Object): Object

    为组件创建计算属性以返回 Vuex store 中的状态。详细介绍 (opens new window)

    第一个参数是可选的,可以是一个命名空间字符串。详细介绍 (opens new window)

    对象形式的第二个参数的成员可以是一个函数。function(state: any)

# mapGetters
# mapActions
  • mapActions(namespace?: string, map: Array | Object): Object

    创建组件方法分发 action。详细介绍 (opens new window)

    第一个参数是可选的,可以是一个命名空间字符串。详细介绍 (opens new window)

    对象形式的第二个参数的成员可以是一个函数。function(dispatch: function, ...args: any[])

# mapMutations
  • mapMutations(namespace?: string, map: Array | Object): Object

    创建组件方法提交 mutation。详细介绍 (opens new window)

    第一个参数是可选的,可以是一个命名空间字符串。详细介绍 (opens new window)

    对象形式的第二个参数的成员可以是一个函数。function(commit: function, ...args: any[])

# createNamespacedHelpers
  • createNamespacedHelpers(namespace: string): Object

    创建基于命名空间的组件绑定辅助函数。其返回一个包含 mapStatemapGettersmapActionsmapMutations 的对象。它们都已经绑定在了给定的命名空间上。详细介绍 (opens new window)

# axios

axios是一个http请求包,vue官网推荐使用axios进行http调用。

https://www.npmjs.com/package/axios

示例:

axios.post('/user',{
  firstName:'Fred',
  lastName:'Flintstone'
})
.then(function(res){
  console.log(res);
})
.catch(function(err){
  console.log(err);
});
1
2
3
4
5
6
7
8
9
10

简单拦截器设置:

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

封装

import axios from 'axios'
import { Message, MessageBox } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

const service = axios.create({ // create an axios instance
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  timeout: 50000 // request timeout
  // withCredentials: true, // send cookies when cross-domain requests
})
// $axios.defaults.baseURL = apiUrl
// $axios.defaults.timeout = 15000
// $axios.defaults.headers.post['Content-Type'] = 'multipart/form-data'

service.interceptors.request.use(// request interceptor
  config => {
    if (store.getters.token) {
      config.headers['Authorization'] = getToken()
    }
    return config
  },
  error => {
    console.error(error)
    return Promise.reject(error)
  }
)
service.interceptors.response.use(// response interceptor
  response => {
    // return response.data
    const res = response.data
    if (res.code === 200 || res.code === 201) { // 202 以上 显示msg及code的特殊情况
      return res // res.data
    } else {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
      if (res.code === 401 || res.code === 403) {
        MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            window.location.reload()// 为了重新实例化vue-router对象 避免bug
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    }
  },
  error => {
    console.error('err' + error)
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    // const { response } = error;
    //if (response) {// 请求已发出,但是不在2xx的范围 
    // errorHandle(response.status, response.data.message);
    //return Promise.reject(response);
    //} else {  // 处理断网的情况
    // if (!window.navigator.onLine) {
    //   store.commit('changeNetwork', false);
    //} else {
    //  return Promise.reject(error);
    //}
    //}
    return Promise.reject(error)
  }
)

const errorHandle = (status, other) => {
  switch (status) {// 状态码判断
    case 401:// 401: 未登录状态,跳转登录页
      toLogin();
      break;
    case 403: // 403 token过期; 清除token并跳转登录页
      tip('登录过期,请重新登录');
      localStorage.removeItem('token');
      store.commit('loginSuccess', null);
      setTimeout(() => {
        toLogin();
      }, 1000);
      break;
    case 404:// 404请求不存在
      tip('请求的资源不存在'); 
      break;
    default:
      console.log(other);   
  }}

export default service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

# SSR/Nuxt.js

服务器端渲染 (SSR) vue-server-renderer; 是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染

# 服务端渲染 SSR 的优缺点

# SSR与SPA的比较

(1)服务端渲染的优点:

  • 更好的 SEO
    • 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容
    • SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面
  • 更快的内容到达时间(首屏加载更快)
    • SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间
    • SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间

(2) 服务端渲染的缺点:

  • 更多的开发条件限制
    • 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;
    • 并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境
  • 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略

如果项目的 SEO 和 首屏渲染是评价项目的关键指标,那么你的项目就需要服务端渲染来帮助你实现最佳的初始加载性能和 SEO

上次更新: 2022/04/15, 05:41:27
×