聊一聊我对 React Context 的理解以及应用(4)

尽管源码还有其他的逻辑,但<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 />:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsdgxg.html