vue-router 源码分析——10.路由组件传参

这是对vue-router 3 版本的源码分析。
本次分析会按以下方法进行:

  1. 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时的回答都非常有帮助。
  2. 在围绕某个功能展开讲解时,所有不相干的内容都会暂时去掉,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以…表示。
  3. 每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。

本章讲解router中路由组件传参中的三种方式是如何实现的。
另外我的vuex3源码分析也发布完了,欢迎大家学习:
vuex3 最全面最透彻的源码分析
还有vue-router的源码分析:
vue-router 源码分析——1. 路由匹配
vue-router 源码分析——2. router-link 组件是如何实现导航的
vue-router 源码分析——3. 动态路由匹配
vue-router 源码分析——4.嵌套路由
vue-router 源码分析——5.编程式导航
vue-router 源码分析——6.命名路由
vue-router 源码分析——7.命名视图
vue-router 源码分析——8.重定向
vue-router 源码分析——9.别名

官网例子

  • 路由组件传参的作用是将组件和路由状态进行解耦,使得组件可以在多个不同url路径的路由中使用。
  • 官网例子虽然解释了使用props将路由和组件解耦的方法,但是没有直观地展示这么做的好处,下面是我写的一个例子:在这里例子可以看到,多个路由中重用这个UserProfile组件,而不管路由的参数是如何命名的。这样不仅体现了解耦,更体现了组件的复用性和易维护性。
const UserProfile = {
  props: ['userId'],
  template: '<div>User Profile for {{ userId }}</div>'
}

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: UserProfile, props: route => ({ userId: route.params.id }) },
    { path: '/profile/:userId', component: UserProfile, props: route => ({ userId: route.params.userId }) },
  ]
})

三种模式

  • 布尔模式:如果 props 被设置为 true,route.params 将会被设置为组件属性。
  • 对象模式:如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用。
  • 函数模式:可以创建一个函数返回 props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。
const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
// 布尔模式
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true },
  ]
})

// 对象模式
const router = new VueRouter({
  routes: [
    {
      path: '/promotion/from-newsletter',
      component: Promotion,
      props: { newsletterPopup: false }
    }
  ]
})

// 函数模式
const router = new VueRouter({
  routes: [
    {
      path: '/search',
      component: SearchUser,
      props: route => ({ query: route.query.q })
    }
  ]
})

路由记录(Record)对 props 的处理

  • 在路由初始化,生成路由记录record时,用了多个三元运算符按不同情况将props进行了格式化处理。
  • 如果路由没有设置props,则为空对象。
  • 如果使用了命名视图,则直接将设置的props赋值给record.props。
  • 如果不属于以上两种情况,则将设置的props赋值给一个新对象的default属性,并将此对象赋值给record.props。
// create-route-map.js
export function createRouteMap (...) : {...} {
    routes.forEach(route => {
        addRouteRecord(pathList, pathMap, nameMap, route, parentRoute)
      })
}
...
function addRouteRecord(...) {
    const record: RouteRecord = {
        ...
        props:
        route.props == null
            ? {} // 未设置props或props为空
            : route.components
              ? route.props // 使用了命名视图时
              : { default: route.props }    // 设置了props且未使用命名视图
    }
}

触发路由导航时对 props 的处理

  • 触发路由导航时,会尝试获取匹配的路由记录的props数据。
  • 通过 switch - case 按props的不同类型做不同处理(这里就实现了props传参的三种方法:布尔、对象和函数)。
  • 将处理后的props数据赋值给组件的props属性,这样组件就能通过设置props来获取需要的数据了。
// ./components/view.js
export default {
    ...
    props: {
        name: {
            type: String,
            default: 'default'        
        }    
    },
    render(_, { props, children, parent, data }) {
        ...
        const matched = route.matched[depth]
        const configProps = matched.props && matched.props[name] // configProps 是按照当前视图名称获取的,在使用了命名视图时,需要分别为每个命名视图添加 `props` 选项
        if (configProps) {
            ...
            fillPropsinData(component, data, route, configProps) // 将 configProps 填充到data中
        }
        return h(component, data, children)
    }
}

function fillPropsinData (component, data, route, configProps) {
    // resolve props
    let propsToPass = data.props = resolveProps(route, configProps)
    if (propsToPass) {
        // clone to prevent mutation
        propsToPass = data.props = extend({}, propsToPass)
        ...
    }
}

// resolveProps函数中的switch-case处理了路由传参的三种方法
function resolveProps (route, config) {
    switch (typeof config) {
        case 'undefined':
            return
        case 'object':
            return config // 对象模式,没有动态转换,所以适合静态props
        case 'function':
            return config(route) // 函数模式,接受route作为唯一参数
        case 'boolean':
            return config ? route.params : undefined // 布尔模式,将动态路由的params赋值给props
        default:
            if (process.env.NODE_ENV !== 'production') {
                warn(
                    false,
                    `props in "${route.path}" is a ${typeof config}, ` +
                    `expecting an object, function or boolean.`
                )
          }
    }
}

源码分析补充:组件可以取到它未使用的 props 吗?

  • 假设有如下设置:路由中定义的props中有两个属性:userId 和 defaultId,但是组件 UserProfile 的props 只接收 userId。如果我们想要在组件中使用 defaultId,但是又不想在 props 中接收它,同时也不能使用 this.$route 来获取,那该怎么办呢?
  • 通过分析源码发现,vue-router 会将路由组件传参中未被组件 props 定义接受的参数传给 data.attrs属性。
const UserProfile = {
    props: ['userId'],
    template: '<div>User Profile for {{ userId }}</div>'
}

const router = new VueRouter({
    routes: [{
        path: '/user/:id',
        component: UserProfile,
        props: route => ({
            userId: route.params.id,
            defaultId: 123456
        })
    }]
})
  • 所以在 UserProfile 组件中,可以通过 this.$attrs 来访问 defaultId:
// ./components/view.js
...
function fillPropsinData (component, data, route, configProps) { // resolve props
    let propsToPass = data.props = resolveProps(route, configProps)
    if (propsToPass) {
        propsToPass = data.props = extend({}, propsToPass)
        const attrs = data.attrs = data.attrs || {}
        // 处理 data.attrs 的逻辑
        for (const key in propsToPass) {
            if (!component.props || !(key in component.props)) {
                attrs[key] = propsToPass[key]
                delete propsToPass[key]
            }
        }
    }
}
  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值