造轮子是核心原理的应用+外围功能的堆叠,所以学习成熟库的源码往往会被非核心代码打扰。Routerrepo在不到100行的源码中实现了ReactRouter的核心机制,非常适合使用学习。精读Router快速实现了ReactRouter的三个核心API:Router、navigate、Link。下面列出基本用法,配合理解源码会更方便:constApp=()=>(
home,转到文章,navigate('/details')}>orjumptodetails
)先看Router的实现,再看编码之前,想一想路由器是干什么的?接收routes参数,根据当前url地址决定渲染哪个组件。当url地址改变时(无论是用户触发还是自己的navigateLink),渲染新url对应的组件。所以Router是一个路由渲染分配器和url监听器:exportdefaultfunctionRouter({routes}){//存储当前的url路径,这样当它发生变化时,会触发自己的重新渲染返回对应的组件newurlconst[currentPath,setCurrentPath]=useState(window.location.pathname);useEffect(()=>{constonLocationChange=()=>{//更新当前数据流的url路径并触发自身重新渲染setCurrentPath(window.location.pathname);}//监听popstate事件,当用户点击浏览器前进/后退时触发matchesthecurrenturlpathandrenderreturnroutes.find(({path,component})=>path===currentPath)?.component}最后一段代码好像每次都执行find。性能损失,但实际上根据Router一般在根节点的情况,这个函数很少会因为父组件的重新渲染而触发渲染,所以不用太担心性能问题。但是,如果考虑构建一个完整的ReactRouter组件库,并考虑更复杂的嵌套API,即在Router中嵌套一个Router后,不仅需要改变监控方式,还需要对命中的组件进行缓存,更多的考虑点会逐渐增加。.接下来,是时候实现导航链接了。他们俩所做的就是跳跃。有以下区别:API调用方式不同。navigate是一个调用函数,而Link是一个具有内置导航功能的标签。Link实际上有一个跳转模式,可以在按住ctrl后打开一个新的标签页。这种模式是由浏览器对a标签的默认行为完成的。所以Link比较复杂。我们先实现navigate,然后在实现Link的时候可以复用。既然Router监听到了popstate事件,我们显然想到的是触发url变化,让popstate捕获,自动触发后续的跳转逻辑。但是很遗憾我们要做的ReactRouter需要实现单页跳转逻辑,而单页跳转的APIhistory.pushState是不会触发popstate的。为了让实现更优雅,我们可以在pushState之后手动触发popstate事件,如源码所示:exportfunctionnavigate(href){//使用pushState直接刷新url,不会触发真正的浏览器跳窗。history.pushState({},"",href);//手动触发一次popstate,让Route组件监听并触发onLocationChangeconstnavEvent=newPopStateEvent('popstate');window.dispatchEvent(navEvent);}接下来实现Link很简单,有几个注意事项:返回一个普通的标签。因为正常情况下是点击刷新网页而不是单页跳转,所以应该防止点击时的默认行为,替换掉我们的navigate(源码中没有做这个抽象,作者稍微优化了一下它)。但是当你按住ctrl时,你需要打开一个新标签。这时候使用默认的标签行为,所以此时不??要阻止默认行为,也不要继续执行navigate,因为这个url的变化不会影响当前的tab。exportfunctionLink({className,href,children}){constonClick=(event)=>{//mac的meta或windows的ctrl会打开一个新标签页//所以此时不??用定制,直接返回使用native行为Justif(event.metaKey||event.ctrlKey){return;}//否则禁用原生跳转event.preventDefault();//进行单页跳转navigate(href)};返回(