React-Router v6新特性(react-router-dom文档)
React-Router v6稳定版本已在近期发布,相较前两个主版本(v5和v4, v5并没有做破坏性更改)带来了破坏性的api,v6路由功能搭配一系列的hooks将变得更加强大,我们先一睹为快。
Routes
替换Switch
v5版本中使用
Switch
来匹配命中路由的组件// v5 function App() { return ( <BrowserRouter> <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/about"> <About /> </Route> <Route path="/users/:id" children={<User />} /> </Switch> </BrowserRouter> ); } 复制代码
在v5中,
Switch
担任路由匹配的核心角色,它会遍历查找自己的子元素,基于v5的路由匹配算法会渲染第一个命中路由的组件。// v6 function App() { return ( <BrowserRouter> <Routes> // 使用Routes替换 Switch <Route path="/Home" element={<Home />} /> <Route path="/user" element={<Users />} /> </Routes> </BrowserRouter> ) } 复制代码
v6版本使用
Routes
替换Switch
组件,语义上更贴近其子元素Route
,Routes
中实现了全新的路由查找算法,许多新特性大多基于此算法实现。规范
Route
的渲染属性v5中提供
component、render、children
三种方式渲染组件// v5 function App() { return ( <BrowserRouter> <Switch> // component渲染 <Route exact path=":userId" component={User} /> <Route path=":userId" // render函数渲染 render={routeProps => ( <User routeProps={routeProps} animate={true} /> )} /> <Route path=":userId" // children渲染 children={({ match }) => ( match ? ( <User match={match} animate={true} /> ) : ( <NotFound /> ) )} /> <Route path=":userId" children={<User animate={true} />} /> </Switch> </BrowserRouter> ); } 复制代码
v5的Route可以接收component作为渲染组件,看起来很简单,但我们无法给该组件(User)传递自定义属性,因此我们可以使用render来弥补component的缺陷,除此之外v5版本还提供children属性,当children是一个函数时,接收路由匹配的上下文数据,实际上功能和render相同。v6版本将Route的三个属性统一规范成
element
。// v6 function App() { return ( <BrowserRouter> <Routes> <Route path="/user" element={<User />} /> <Route path="/user/1" element={<User animate={true} />} /> </Routes> </BrowserRouter> ) } // 结合v6提供的hooks,可以获取路由相关数据 function User({ animate }) { let params = useParams(); let location = useLocation(); } 复制代码
element
的类型为React.ReactNode,可以传入Suspense实现路由懒加载。v6在支持原有路由功能的基础上,通过规范、减少组件api来减轻开发者使用的负担,最终让开发体验得到提升。手动排序 vs 智能匹配
由于v5中的路由算法是渲染第一个命中的路由组件,开发者在使用时需要进行手动排序来展示组件优先级。v6中的路由匹配算法更加智能、强大,会通过计算比较返回优先级高的路由组件。通过一个例子看他们之间的区别。
<Route path="/user/:id" component={User} /> <Route path="/user/new" component={NewUser} /> 复制代码
我们定义了两个路由,实际上访问大多数
/user
路径没有很大的歧义,但当页面访问/user/new
时,此时同时命中了这两个路由,那么最终会渲染哪个组件? 在v5中,最终渲染是先定义的路由组件(User),即先定义,先渲染(符合之前说的v5路由算法返回第一个匹配的组件),所以开发者需要手动对路由进行排序控制组件渲染的优先级。但这并不符合我们的页面渲染预期。 在v6中,一切变的更加自动和智能,开发者不需要手动维护路由组件的顺序,而是交给v6路由匹配算法自动选择渲染,那么v6具体是怎样实现的呢?简单的讲,在v6内部,会对每个路径进行分割,对路径中的各个部分累计打分排名,分数越高,则优先渲染,其打分方法如下:// 匹配路由动态部分, 如/:id const paramRe = /^:\w+$/; // 动态路由部分分值 const dynamicSegmentValue = 3; // index子路由分值 const indexRouteValue = 2; // 空路由部分分值 const emptySegmentValue = 1; // 静态路由部分分值 const staticSegmentValue = 10; // 当路径中存在*时分值 const splatPenalty = -2; const isSplat = (s: string) => s === "*"; function computeScore(path: string, index: boolean | undefined): number { // 分割路径成数组 let segments = path.split("/"); let initialScore = segments.length; if (segments.some(isSplat)) { initialScore += splatPenalty; } if (index) { initialScore += indexRouteValue; } return segments .filter(s => !isSplat(s)) .reduce( (score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore ); } 复制代码
观察这个方法,可以看出越是动态的路由它的分数往往越低,例如,当路径中存在*时,它的初始分数就相对较低,同时动态路由部分分值(
dynamicSegmentValue: 3
)比静态路由部分分值(staticSegmentValue: 10
)低了许多。回到上面的例子,在访问/user/new路径时,v6会计算/user/:id
和/user/new
路由分数,由于/user/new
是静态路由分数会高于/user/:id
,因此v6中会渲染NewUser
组件。相对路径的Link和Route v5嵌套路由里需要拼接绝对路径来渲染组件的子路由,例如
// v5 function App() { return ( <BrowserRouter> <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/users"> <Users /> </Route> </Switch> </BrowserRouter> ); } function Users() { // 当子组件渲染嵌套路由时,需要通过useRouteMatch获得match对象,开发者基于match对象拼接绝对路径供Link和Route组件使用。 let match = useRouteMatch(); return ( <div> <nav> <Link to={`${match.url}/me`}>My Profile</Link> </nav> <Switch> <Route path={`${match.path}/me`}> <OwnUserProfile /> </Route> <Route path={`${match.path}/:id`}> <UserProfile /> </Route> </Switch> </div> ); } 复制代码
在v6中,开发者使用Link或Route时只需定义相对父路由的相对路径即可,v6内部会为我们自动拼接全路径。
// v6 function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="users/*" element={<Users />} /> </Routes> </BrowserRouter> ); } function Users() { return ( <div> <nav> <Link to="me">My Profile</Link> </nav> <Routes> <Route path=":id" element={<UserProfile />} /> <Route path="me" element={<OwnUserProfile />} /> </Routes> </div> ); } 复制代码
Outlet组件
v6带来了
Outlet
组件,用于渲染当前路由下的子路由组件,我们对第四点中的v6版本代码做一下转换:// v6 function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="users" element={<Users />}> <Route path="me" element={<OwnUserProfile />} /> <Route path=":id" element={<UserProfile />} /> </Route> </Routes> </BrowserRouter> ); } function Users() { return ( <div> <nav> <Link to="me">My Profile</Link> </nav> // 当访问/users/me 或者 /users/id时,子路由会被渲染 <Outlet /> </div> ); } 复制代码
通过
Outlet
可以将所有的路由(嵌套的子路由)配置合并在一起,可进行路由的统一管理,增加了代码可维护性。一系列的Hooks
实际上v5也提供了个别hooks,例如
useHistory
、useLocation
以及上文提到的useRouteMatch
等,但v5版本核心组件还是基于类组件开发,随着React Hooks的发布,社区中的React类库开始向Hooks迁移,因此v5中的hooks相当于过渡使用。v6开始全面重写,拥抱Hooks函数组件以及TS,许多API也是利用组合的思想来拆分、包装代码,例如Route
s则是useRoutes
的包装,Navigate
是useNavigate
的包装,Outlet
是useOutlet
的包装。
总结
React Router目前是React应用路由管理的最佳方案(没有之一), v6版本基于全新的路由算法带来强大的功能和hooks,开发体验大大增强????
作者:发多
链接:https://juejin.cn/post/7031455218097356831