我们在使用时都是使用Router来嵌套Route,所以此时就到Route组件,Route的作用是匹配路由,并传递给要渲染的组件props,Route接受上层的Router传入的context,Router中的history监听着整个页面的路由变化,当页面发生跳转时,history触发监听事件,Router向下传递nextContext,就会更新Route的props和context来判断当前Route的path是否匹配location,如果匹配则渲染,否则不渲染,是否匹配的依据就是computeMatch这个函数,在下文会有分析,这里只需要知道匹配失败则match为null,如果匹配成功则将match的结果作为props的一部分,在render中传递给传进来的要渲染的组件。Route接受三种类型的render props,<Route component>、<Route render>、<Route children>,此时要注意的是如果传入的component是一个内联函数,由于每次的props.component都是新创建的,所以React在diff的时候会认为进来了一个全新的组件,所以会将旧的组件unmount再re-mount。这时候就要使用render,少了一层包裹的component元素,render展开后的元素类型每次都是一样的,就不会发生re-mount了,另外children也不会发生re-mount。
// \packages\react-router\modules\Route.js line 17 class Route extends React.Component { render() { return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Route> outside a <Router>"); const location = this.props.location || context.location; const match = this.props.computedMatch ? this.props.computedMatch // <Switch> already computed the match for us : this.props.path ? matchPath(location.pathname, this.props) : context.match; const props = { ...context, location, match }; let { children, component, render } = this.props; // Preact uses an empty array as children by // default, so use null if that's the case. if (Array.isArray(children) && children.length === 0) { children = null; } if (typeof children === "function") { children = children(props); // ... } return ( <RouterContext.Provider value={props}> {children && !isEmptyChildren(children) ? children : props.match ? component ? React.createElement(component, props) : render ? render(props) : null : null} </RouterContext.Provider> ); }} </RouterContext.Consumer> ); } }我们实际上我们可能写的最多的就是Link这个标签了,所以我们再来看一下<Link>组件,我们可以看到Link最终还是创建一个a标签来包裹住要跳转的元素,在这个a标签的handleClick点击事件中会preventDefault禁止默认的跳转,所以实际上这里的href并没有实际的作用,但仍然可以标示出要跳转到的页面的URL并且有更好的html语义。在handleClick中,对没有被preventDefault、鼠标左键点击的、非_blank跳转的、没有按住其他功能键的单击进行preventDefault,然后push进history中,这也是前面讲过的路由的变化与 页面的跳转是不互相关联的,ReactRouter在Link中通过history库的push调用了HTML5 history的pushState,但是这仅仅会让路由变化,其他什么都没有改变。在Router中的listen,它会监听路由的变化,然后通过context更新props和nextContext让下层的Route去重新匹配,完成需要渲染部分的更新。
// packages\react-router-dom\modules\Link.js line 14 class Link extends React.Component { handleClick(event, history) { if (this.props.onClick) this.props.onClick(event); if ( !event.defaultPrevented && // onClick prevented default event.button === 0 && // ignore everything but left clicks (!this.props.target || this.props.target === "_self") && // let browser handle "target=_blank" etc. !isModifiedEvent(event) // ignore clicks with modifier keys ) { event.preventDefault(); const method = this.props.replace ? history.replace : history.push; method(this.props.to); } } render() { const { innerRef, replace, to, ...rest } = this.props; // eslint-disable-line no-unused-vars return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Link> outside a <Router>"); const location = typeof to === "string" ? createLocation(to, null, null, context.location) : to; const href = location ? context.history.createHref(location) : ""; return ( <a {...rest} onClick={event => this.handleClick(event, context.history)} href=http://www.likecs.com/{href} ref={innerRef} /> ); }} </RouterContext.Consumer> ); } } 每日一题 https://github.com/WindrunnerMax/EveryDay 参考 https://zhuanlan.zhihu.com/p/44548552 https://github.com/fi3ework/blog/issues/21 https://juejin.cn/post/6844903661672333326 https://juejin.cn/post/6844904094772002823 https://juejin.cn/post/6844903878568181768 https://segmentfault.com/a/1190000014294604 https://github.com/youngwind/blog/issues/109