有的东西不总结,永远不是自己的。


问题概述

IndexRoute的设定是,必须为叶节点,而要是作为父级元素,在IndexRoute下面嵌套更多的子节点。也就是这样的写法是不支持的:

<Router>
  <IndexRoute>
    <IndexRoute></IndexRoute>
  </IndexRoute>
  <Route></Route>
</Router>

问题还原

而在实际的业务需求中,经常会遇到下面这种情况:当我从这个入口进入:

想要看到

而实际情况是:

需要一层一层进行点击才能得到想要的结果。(图中的是React-Router官方的示例运行后看到的)

(因为实际业务中都用到异步路由,按需加载,所以照着官方的huge-app示例进行修改和描述。看官如果觉得描述的有点混乱,可以下载源码,npm install后npm start,结合页面跳转进行理解。)

尝试解决

我们先用应用IndexRouter,在huge-apps/routes/Course/index.js中添加getIndexRoute方法,完整代码如下:

module.exports = {
  path: 'course/:courseId',

  getChildRoutes(partialNextState, cb) {
    require.ensure([], (require) => {
      cb(null, [
        require('./routes/Announcements'),
        require('./routes/Assignments'),
        require('./routes/Grades')
      ])
    })
  },

  getIndexRoute(location, cb) {
    require.ensure([], require =>
      cb(null, require('./routes/Announcements'))
    )
  },

  getComponent(nextState, cb) {
    require.ensure([], (require) => {
      cb(null, require('./components/Course'))
    })
  }
}

从入口进入后,如图

可以看到,上图和第二张图的 url 是一样的,而显示的效果和这个url( localhost:8080/huge-apps/course/0/announcements)的结果是一样的,这就是IndexRoute的基本用法。这个时候,点击Announcements这个按钮,会发现url改变了,这个时候该死的强迫症就来了——明明页面没有改变,为什么 url 里多了一个字段!!与此同时,控制台会报一个Warning[react-router] Index routes should not have paths!!

为了解决这个问题,尝试将/Announcement/index.jspath去掉,然后需要修改activeClassName,链接这个路径需要用IndexLink组件,或者Link组件加上onlyActiveOnIndex属性(详见阮一峰React-Router教程(九))。这样的话,用map遍历出来的Link组件就需要加入若干个判断,比较麻烦。

这个支线问题只是为了解决强迫症,而且上面的解决方案并不够优雅。下面回到主线问题(工期要紧)。这个时候,如果再用同样的方法,在/Announcement/index.js中使用getIndexRoute方法,结果发现,怎么也进不到第三张图的页面了,控制台只是说没有匹配到相应的路由(那只是没有修改Link组件中to的链接导致的,我们想要的是在/huge-apps/course/0路径下得到第三张图的效果)。

之后想到另一种解决方法,因为IndexRoute想解决的问题是this.props.childrenundefined(详见阮一峰React-Router教程(五)),那我们是不是可以写成{this.props.children || <Announcement/>}呢。

经过实践,是可以实现需求的,但是和antd的面包屑组件结合起来就会有问题。具体问题是:想要显示的面包屑是三级的,但实际只显示了二级面包屑,而且点击一级面包屑后,面包屑变为一级,但是显示的并不改变,虽然这本来就不会改变,但是这样的体验很不好。。

最后,想出用重定向的方式,即解决了强迫症的问题,又满足了需求,与antd的面包屑组件结合也没有冲突。下面贴上代码:

class SomeApp extends React.Component {
    redirect() {
        if (!this.props.children) {
            this.context.router.push('some url')
        }
    }

    componentWillMount() {
        this.redirect()
    }

    componentDidUpdate() {
        this.redirect()
    }

    render() {
        return (
            <div>
                {this.props.children}
            </div>
        )
    }
}

SomeApp.contextTypes = {
    router: React.PropTypes.object
}

这里遇到的最后一个坑是关于生命周期的问题,要把重定向函数写在componentWillMountcomponentDidUpdate两个方法里。