尽量源码尚有其他的逻辑,但<Router />的焦点就是为子组件提供一个带有router属性的Context,同时监听history,一旦history产生变革,便通过setState()触发组件从头渲染。
// Link.js /** * The public API for rendering a history-aware <a>. */ class Link extends React.Component { // ...... static contextTypes = { router: PropTypes.shape({ history: PropTypes.shape({ push: PropTypes.func.isRequired, replace: PropTypes.func.isRequired, createHref: PropTypes.func.isRequired }).isRequired }).isRequired }; handleClick = event => { if (this.props.onClick) this.props.onClick(event); if ( !event.defaultPrevented && event.button === 0 && !this.props.target && !isModifiedEvent(event) ) { event.preventDefault(); // 利用<Router />组件提供的router实例 const { history } = this.context.router; const { replace, to } = this.props; if (replace) { history.replace(to); } else { history.push(to); } } }; render() { const { replace, to, innerRef, ...props } = this.props; // ... const { history } = this.context.router; const location = typeof to === "string" ? createLocation(to, null, null, history.location) : to; const href = history.createHref(location); return ( <a {...props} onClick={this.handleClick} href=https://www.jb51.net/article/{href} ref={innerRef} /> ); } }
<Link />的焦点就是渲染<a>标签,拦截<a>标签的点击事件,然后通过<Router />共享的router对history举办路由操纵,进而通知<Router />从头渲染。
// Route.js /** * The public API for matching a single path and rendering. */ class Route extends React.Component { // ...... state = { match: this.computeMatch(this.props, this.context.router) }; // 计较匹配的路径,匹配的话,会返回一个匹配工具,不然返回null computeMatch( { computedMatch, location, path, strict, exact, sensitive }, router ) { if (computedMatch) return computedMatch; // ...... const { route } = router; const pathname = (location || route.location).pathname; return matchPath(pathname, { path, strict, exact, sensitive }, route.match); } // ...... render() { const { match } = this.state; const { children, component, render } = this.props; const { history, route, staticContext } = this.context.router; const location = this.props.location || route.location; const props = { match, location, history, staticContext }; if (component) return match ? React.createElement(component, props) : null; if (render) return match ? render(props) : null; if (typeof children === "function") return children(props); if (children && !isEmptyChildren(children)) return React.Children.only(children); return null; } }
<Route />有一部门源码与<Router />相似,可以实现路由的嵌套,但其焦点是通过Context共享的router,判定是否匹配当前路由的路径,然后渲染组件。
通过上述的阐明,可以看出,整个react-router其实就是环绕着<Router />的Context来构建的。
利用Context开拓组件之前,通过Context开拓过一个简朴的组件,插槽分发组件。本章就借着这个插槽分发组件的开拓经验,聊聊如何利用Context举办组件的开拓。
插槽分发组件首先说说什么是插槽分发组件,这个观念最初是在Vuejs中认识的。插槽分发是一种通过组件的组合,将父组件的内容插入到子组件模板的技能,在Vuejs中叫做Slot。
为了让各人越发直观的领略这个观念,我从Vuejs搬运了一段关于插槽分发的Demo。
对付提供的插槽的组件<my-component />,模板如下:
<div> <h2>我是子组件的标题</h2> <slot> 只有在没有要分发的内容时显示 </slot> </div>
对付父组件,模板如下:
<div> <h1>我是父组件的标题</h1> <my-component> <p>这是一些初始内容</p> <p>这是更多的初始内容</p> </my-component> </div>
最终渲染的功效:
<div> <h1>我是父组件的标题</h1> <div> <h2>我是子组件的标题</h2> <p>这是一些初始内容</p> <p>这是更多的初始内容</p> </div> </div>
可以看到组件<my-component /> 的<slot />节点最终被父组件中<my-component />节点下的内容所替换。
Vuejs还支持具名插槽。
譬喻,一个机关组件<app-layout />: