在大型React应用程序中进行路由


嗨,我叫Boris Shabanov ,我是Rambler集团广告技术开发部门的前端开发主管。 今天,我将向您介绍路由问题如何在我们的应用程序中产生以及如何解决。


广告技术是Rambler集团的一个大部门,它实施一整套广告技术(SSP,DMP,DSP)。 对于最终用户,即广告商和代理商,我们创建了一个名为Summer的便捷界面,在该界面中,他们可以启动广告系列,查看有关它们的统计信息并管理所有内容。



Summer界面是一组屏幕,这些表单通过链接相互连接。 在一个地方,只需单击几下,用户就可以找到他需要的信息。



经过几年的运营,项目要求变得更加严格,我们意识到我们需要解决此问题。 由于该项目从一开始就基于React,因此我们将React Create App作为新版本的基础。 而GraphQL-用于与服务器交互,即-Apollo client 。 此外,该部门拥有很多专业知识。


但是我们有一个问题,什么可以用来路由我们的应用程序? 我们去Google寻找“ react”和“ router”这两个词的解决方案,实际上,在前5页中,除了React Router之外什么都没有。 “好吧,好吧……”-我们认为,聪明的人建议,我们必须接受。 经过短时间的提问并没有减少。 例如,举一个使用react-router的简单示例:


import React from "react"; import { BrowserRouter as Router, Route, Link } from "react-router-dom"; const ParamsExample = () => ( <Router> <div> <h2>Accounts</h2> <ul> <li> <Link to="/netflix">Netflix</Link> </li> <li> <Link to="/zillow-group">Zillow Group</Link> </li> <li> <Link to="/yahoo">Yahoo</Link> </li> <li> <Link to="/modus-create">Modus Create</Link> </li> </ul> <Route path="/:id" component={Child} /> <Route path="/order/:direction" component={ComponentWithRegex} /> </div> </Router> ); 

出现问题:


  • 有很多参数的URL呢?
  • 如果产品经理到达并要求我将页面URL更改为更“友好的” URL,该怎么办? 整个项目中的“地毯承诺”并不是真正想要做的。

在开发过程中,我们开始思考并寻找解决方案,但与此同时,我们仍然使用React-router


在下一个Sprint中,我们使用了“帮助”部分。 页面并不复杂,几小时内我们就完成了。


但是,总的来说,“帮助”是所有其他页面都链接到的部分,并且有很多链接。 并且每个答案的链接是动态生成的。 对我们来说,这是考虑更换路由器的另一个原因。


在Internet上进行广泛搜索之后,我们找到了可接受的解决方案-Router5



Router5是一个与React无关的库,您可以将它与Angular,jQuery和其他任何东西一起使用。 值得一提的是,Router5不是React-router @ 4的延续,它们是由不同的人开发的两种不同的东西,它们之间没有任何联系。


因此,在选择图书馆时,我们开始研究它的流行程度以及使用它是否有意义。 如果按github上的星数进行比较,则优势是可以使用react-router。



但是我们决定借此机会。 我们研究了该项目的生存方式,是否有人参与了该项目的开发,看到人们正在为它带来新的事物,并且该项目非常活跃。



从文档中,您可以发现他与不同的框架和库进行了大量集成。 特别是,该文档描述了与react和redux的集成,但是如果您搜索良好,则可以在其中找到有关mobX的示例。



如何构建接口,以及如何在Router5中构建路由器?
这里的故事与React-router中的故事相同:每条路由都是其子容器。


假设您有一个包含着陆页的网站(我们将其放在主页上),并且有一个特定的管理面板,该面板由两部分组成-用户列表和这些用户的组列表。 我们可以进一步配置所有这些方式,以使我们的“主页”部分看起来像一个登录页面。 此外,管理部分将为其所有子页面和元素(即 例如,将由页脚和页眉组成。 管理区域中已经存在的所有子页面都将变为这些页脚和页眉。



对于这样的站点,Router5的配置如下:


 import createRouter from 'router5'; import browserPlugin from 'router5/plugins/browser'; const routes = [ { name : 'home', }, { name : 'admin', children : [ { name : 'roles', }, { name : 'users', }, ] }, ]; const options = { defaultRoute: 'home', // ... }; const router = createRouter(routes, options) .usePlugin(browserPlugin()); router.start(); 

在示例中,我们有一个简单的选项,但是实际上您可以从文档中了解更多信息,并为自己选择一种更方便的格式。


如前所述,我们在该部门的项目中使用了React,因此我将继续讨论并展示更多有关React的示例。 下面的代码示例显示了如何使用Router @ 5引发项目。


 // app.js import ReactDOM from 'react-dom' import React from 'react' import App from './App' import { RouterProvider } from 'react-router5' import createRouter from './create-router' const router = createRouter() router.start(() => { ReactDOM.render(( <RouterProvider router={router}> <App /> </RouterProvider> ), document.getElementById('root')) }) 

 // Main.js import React from 'react' import { routeNode } from 'react-router5' import { UserView, UserList, NotFound } from './components'​ function Users(props) { const { previousRoute, route } = props​ switch (route.name) { case 'users.list': return <UserList /> case 'users.view': return <UserView /> default: return <NotFound /> } }​ export default routeNode('users')(Users) 

更有趣。 我们喜欢并非常适合该项目的是如何进行过渡。 假设我们有一个具有上述结构的站点。 我们的用户希望从登录页面转到“用户列表”部分。 在这种情况下,router5会做什么? 首先,它取消激活主页部分,从而导致相应的事件。 事件可以在路由本身和中间件中进行处理,在中间件中可以处理所有这些过渡。 接下来,生成admin和admin.users路由的激活事件。



在我们的应用程序中,所有中间件都收集在一个地方。 这些是中间件,负责加载组件(我们尝试将应用程序“切割”成片段并加载用户现在需要的部分),本地化,从服务器获取数据,检查访问权限并收集过渡分析。 结果,开发人员甚至都没有考虑如何接收数据,检查权限以及在屏幕上显示什么。 他们进入下一部分,Router5解决了所有问题。


我们要一劳永逸解决的另一个问题是加载应用程序,并将其分成不同的部分。 我认为,使用React-router下载应用程序类似于刺猬在雾中的冒险。 在每个阶段加载一些数据和片断,您的应用程序就无法避免从服务器接收数据时出现错误。 在这种情况下,您的设计师必须为每种紧急情况提供屏幕的状态。 似乎在这个地方,开发人员犯错的可能性很高(他可能会忘记了),因此应将这些风险降到最低。


在Router5中,这是由以下事实决定的:每个过渡都是事务性地执行的,而可视部分的更改只有在解决了所有中间件之后才会发生,并且路由器确信我们的应用程序可以从中汲取我们想要的东西。 这种方法有其缺点和优点,但在我们的案例中,优点更多。 加载指示器的问题将立即解决。 我们的应用程序使用一个下载指示器,该指示器由路由器控制。


不幸的是,大约3或4个月前使用中间件的工作并未在文档中完全披露。 但是深入研究问题,我们找到了一个很好的示例,展示了如何使用React,Redux和Router5部署和制作应用程序。


我们应用程序的每个URL都存储了显示数据所需的一组特定数据(标识符,其他数据过滤选项等)。 从URL到router5,反之亦然,这些参数的序列化和反序列化看起来并不自然,但是确实如此。


 export default { name: 'help', path: '/help*slugs', loadComponent: () => import('./index'), encodeParams: ({ slugs }) => slugs.join('/'), decodeParams: ({ slugs }) => slugs.split('/'),, defaultParams: { slugs: [], }, }; 

对于我们的项目,这是选择路由器的重要参数,因为我们的应用程序具有许多过滤器和弹出窗口。 并且它应该显示在URL中,指出用户现在在屏幕上看到的状态。 如果用户想向同事展示一些东西,那么他只是从浏览器的地址栏中弄乱了URL。


以下是基本示例:


 const router = router5([ { name: 'admin', path: '/admin' }, { name: 'admin.users', path: '~//users?param1' }, { name: 'admin.user', path: '^/user/:id' }, { name: 'help', path: '^/help/*splat' } ]); console.log(router.buildPath('admin')); // '/admin' console.log(router.buildPath('admin.users'); // '/users' console.log(router.buildPath('admin.users', { param1: true })); // '/users?param1=true' console.log(router.buildPath('admin.users', { id: 100 })); // '/user/100' console.log(router.buildPath('admin.user', { id: 100 })); // '/user/100' console.log(router.buildPath('help', { splat: [1, 2, 3] })); // '/help/1/2/3' 

在React组件中,链接形成如下:


 import React from 'react' import { Link } from 'react-router5'function Menu(props) { return ( <nav> <Link routeName="home">Home</Link> <Link routeName="about">About</Link> <Link routeName="user" routeParams={{ id: 100 }}>User #100</Link> </nav> ) }​ export default Menu 

当创建由3-4页组成的具有最少转换次数的简单站点时,我不使用Router5,但使用React-router。 但是在具有许多页面和链接的项目中,我的选择将是对Router5。


您还可以在这里观看我在RamblerFront和#5上的表演视频:



附言:在2018年10月上半月,我们计划举办下一届RamblerFront&#6。 在habr.com上关注公告。

Source: https://habr.com/ru/post/zh-CN424025/


All Articles