当前位置: 首页 > Web前端 > HTML

精读《react-snippets - Router 源码》

时间:2023-03-28 11:56:41 HTML

造轮子是核心原理的应用+外围功能的堆叠,所以学习成熟库的源码往往会被非核心代码打扰。Routerrepo在不到100行的源码中实现了ReactRouter的核心机制,非常适合使用学习。精读Router快速实现了ReactRouter的三个核心API:Router、navigate、Link。下面列出基本用法,配合理解源码会更方便:constApp=()=>(},{path:'/articles',component:}]}/>)constHome=()=>(

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)};返回({children});};这种设计既可以兼顾标签的默认行为,又可以优化为点击时单页跳转,preventDefault和metaKey的判断值得借鉴。总结一下,你可以从这个小轮子中学到几点经验:在造轮子之前先想好API的使用,根据API的使用逆向实现,这样会让你的设计更有整体性。在实现一个API的时候,首先想好API之间的关系,如果可以复用就提前设计好复用关系。这样巧妙的关联设计,可以为以后的维护省去很多麻烦。即使在代码不能重用的地方,也尽量重用逻辑。例如,pushState不能触发popstate部分。直接复用popstate代码,或者自己创建状态通信,实在是太low了。使用浏览器API模拟事件触发既轻量又合乎逻辑,因为你要做的只是触发popstate行为,而不是仅仅更新渲染组件,万一以后有地方可以监听popstate,你的触发逻辑可以自然地应用在那里。尝试扩展原生功能,而不是使用自定义方法来补充原生功能。比如Link的实现就是基于标签的扩展。如果使用自定义的标签,不仅要弥补样式上的差异,还要实现ctrl后打开新标签页的行为,甚至默认的访问记录你也得花不少钱钱来弥补行为,所以错误的设计方向会导致事倍功半,甚至无法实现。讨论地址是:Jingdu《react-snippets - Router 源码》·Issue#418·dt-fe/weekly想参与讨论的请点这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)