Vue3.0全家桶最全入门指南 – vue-router@4.x和vuex@4.x (3/4)

    |     2020年9月24日   |   vuejs, web前端技术   |     0 条评论   |    1881

Vue2 搭配的是 vue-router@3.x 和 vuex@3.x,Vue3 则换成 vue-router@4.x 和 vuex@4.x。版本号都是 4.x,不要和 Vue 3.x 混为一谈。本文是全家桶系列第 3 篇,对照写法看迁移点。

为方便叙述,文中用 vue2.x router、vue3.x router 代替 vue-router@3.x 和 vue-router@4.x,用 vue2.x vuex、vue3.x vuex 代替 vuex@3.x 和 vuex@4.x。

系列目录:

点 vue2.x vue3.x
创建路由 Vue.use(Router); new Router createRouter + createWebHistory
滚动 { x, y, selector, offset } { el, left, top, behavior }
父级 redirect 路由选项 redirect 子路由 path: ” + redirect
通配 path: ‘/user/:a*’ /:catchAll(.*)
当前匹配组件 router.getMatchedComponents currentRoute.value.matched
拿 store/router this.$store / this.$router useStore / useRouter,不要用 ctx

一、vue-router@4.x

创建实例

// vue2.x router
import Vue from 'vue'
import Router from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

Vue.use(Router)

const router = new Router({
  base: process.env.BASE_URL,
  mode: 'history',
  scrollBehavior: () => ({ y: 0 }),
  routes
})

export default router

// vue3.x router
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

scrollBehavior 滚动行为

vue3 弃用 vue2 的 { selector, x, y, offset },改用更接近原生 DOM 的 { el, left, top, behavior }:

// vue2.x router
const router = new Router({
  base: process.env.BASE_URL,
  mode: 'history',
  scrollBehavior: () => ({ x: 0, y: 0 }),
  routes
})

// vue3.x router
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
  scrollBehavior(to, from, savedPosition) {
    // scroll to id `#app` + 200px, and scroll smoothly (when supported by the browser)
    return {
      el: '#app',
      top: 0,
      left: 0,
      behavior: 'smooth'
    }
  }
})

路由组件跳转

vue2 用路由选项 redirect 自动跳;vue3 去掉该选项,改为在子路由里加一个空路径来匹配跳转。

// vue2.x router
[
  {
    path: '/',
    component: Layout,
    name: 'WebHome',
    meta: { title: '平台首页' },
    redirect: '/dashboard', // 这里写跳转
    children: [
      {
        path: 'dashboard',
        name: 'Dashboard',
        meta: { title: '工作台' },
        component: () => import('../views/dashboard/index.vue')
      }
    ]
  }
]

// vue3.x router
[
  {
    path: '/',
    component: Layout,
    name: 'WebHome',
    meta: { title: '平台首页' },
    children: [
      { path: '', redirect: 'dashboard' }, // 这里写跳转
      {
        path: 'dashboard',
        name: 'Dashboard',
        meta: { title: '工作台' },
        component: () => import('../views/dashboard/index.vue')
      }
    ]
  }
]

捕获所有路由:/:catchAll(.*)

捕获 (/*) 时必须用自定义正则参数 /:catchAll(.*):

// vue2.x router
const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '/user/:a*' },
  ]
})

// vue3.x router
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/user/:a:catchAll(.*)', component: component },
  ]
})

当路由为 /user/a/b 时,捕获到的 params 为 {{"a": "a", "catchAll": "/b"}}。

router.resolve

router.match 与 router.resolve 合并为 router.resolve,签名略有不同:

// vue2.x router
...
resolve ( to: RawLocation, current?: Route, append?: boolean) {
  ...
  return {
    location,
    route,
    href,
    normalizedTo: location,
    resolved: route
  }
}

// vue3.x router
function resolve(
    rawLocation: Readonly<RouteLocationRaw>,
    currentLocation?: Readonly<RouteLocationNormalizedLoaded>
  ): RouteLocation & { href: string } {
  ...
  let matchedRoute = matcher.resolve(matcherLocation, currentLocation)
  ...
  return {
    fullPath,
    hash,
    query: normalizeQuery(rawLocation.query),
    ...matchedRoute,
    redirectedFrom: undefined,
    href: routerHistory.base + fullPath,
  }
}

获取当前路由

删除 router.getMatchedComponents,改从 router.currentRoute.value.matched 取。原文说明:该方法返回目标位置或当前路由匹配的组件数组(是定义/构造类,不是实例),常用于 SSR 数据预加载。

[{
  aliasOf: undefined
  beforeEnter: undefined
  children: []
  components: {default: {…}, other: {…}}
  instances: {default: null, other: Proxy}
  leaveGuards: []
  meta: {}
  name: undefined
  path: "/"
  props: ƒ (to)
  updateGuards: []
}]

使用与就绪

如果使用过渡,可能需要等 router 就绪再挂载:

app.use(router)
// Note: on Server Side, you need to manually push the initial location
router.isReady().then(() => app.mount('#app'))

一般直接挂载也能用。但导航已是异步的,初始化时若有守卫,在 resolve 之前会出现一次初始渲染过渡,类似给过渡提供 appear。

SSR 要传合适的 history mode

const history = isServer ? createMemoryHistory() : createWebHistory()
const router = createRouter({ routes, history })
// on server only
router.push(req.url) // request url
router.isReady().then(() => {
  // resolve the request
})

push / resolve 不存在的命名路由会报错

vue2 里 push 一个不存在的 name,会导航到根路由 / 且不渲染内容,控制台警告,URL 跳到 /。

const router = new VueRouter({
  mode: 'history',
  routes: [{ path: '/', name: 'foo', component: Foo }]
}
this.$router.push({ name: 'baz' })

vue3 同样写法会抛错:

const router = createRouter({
  history: routerHistory(),
  routes: [{ path: '/', name: 'foo', component: Foo }]
})
...
import { useRouter } from 'vue-next-router'
...
const router = userRouter()
router.push({ name: 'baz' })) // 这行代码会报错

获取路由:不要用 ctx

网上有教程通过 ctx 访问 router 和 store。这只在 development 有效,production 编译后那些属性访问不到,容易误导。

错误示例:

import { getCurrentInstance } from 'vue'

export default {
  setup () {
    const { ctx } = getCurrentInstance()
    console.log(ctx)
    console.log(ctx.$router.currentRoute.value)
    const userId = computed(() => ctx.$store.state.app.userId)
    return {
      userId
    }
  }
}

正确使用:用 useRouter、useRoute、useStore。

import { getCurrentInstance } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'

export default {
  setup () {
    const { ctx } = getCurrentInstance()
    console.log(ctx)
    const router = useRouter()
    const route = useRoute()
    const store = userStore()
    console.log(router, route, store)
    console.log(router.currentRoute.value)
    const userId = computed(() => store.state.app.userId)
    return {
      userId
    }
  }
}

二、vuex@4.x

vuex 4.x breaking change 很少,整体改动不大。

创建实例

// vue2.x vuex
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  getters: {},
  modules: {}
}

// vue3.x vuex
import Vuex from 'vuex'

export default Vuex.createStore({
  state: {},
  mutations: {},
  actions: {},
  getters: {},
  modules: {}
})

获取 store

// vue3.x vuex
import { getCurrentInstance } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'

export default {
  setup () {
    const { ctx } = getCurrentInstance()
    console.log(ctx)
    const router = useRouter()
    const route = useRoute()
    const store = userStore()
    console.log(router, route, store)
    console.log(router.currentRoute.value)
    const userId = computed(() => store.state.app.userId)
    return {
      userId
    }
  }
}

一句话总结:Vue3 路由用 createRouter / createWebHistory,通配改 catchAll,守卫异步要等 isReady;取实例用 useRouter/useStore,不要依赖 setup 里的 ctx。

转载请注明来源:Vue3.0全家桶最全入门指南 – vue-router@4.x和vuex@4.x (3/4)
本文链接地址:https://ai.zhousir.top/?p=3086
回复 取消