React Router V4 中的递归路径
由于 React Router 本质上就是组件,所以你可以实现一些非常规的功能,比如递归路由。本文将通过分析 React Router 文档中的“递归路径”示例来学习其工作原理。
递归路由虽然不是世界上最实用的方法,但它确实展现了 React Router 基于组件的路由方法的优势。
这里的核心思想是,由于 React Router 本质上就是组件,理论上你可以创建递归路由,从而实现无限路由。关键在于设置正确的数据结构,这才能实现无限路由。在这个例子中,我们将使用一个人员数组,每个人员都有一个 id 和一个姓名,以及一个包含所有好友的数组。
const users = [
{ id: 0, name: 'Michelle', friends: [ 1, 2, 3 ] },
{ id: 1, name: 'Sean', friends: [ 0, 3 ] },
{ id: 2, name: 'Kim', friends: [ 0, 1, 3 ], },
{ id: 3, name: 'David', friends: [ 1, 2 ] }
]
通过这种数据结构设置,当我们渲染一个对象时Person,我们会将其所有好友渲染为Link元素。然后,当Link点击该对象时,我们会将该对象的所有好友渲染为元素Link,以此类推。每次Link点击对象,应用程序的路径名都会逐渐变长。
最初,我们将位于此处/,用户界面将如下所示。
Michelle's Friends
* Sean
* Kim
* David
如果Kim点击,则 URL 将更改为/2(Kim 的 ID),并且用户界面将显示如下内容。
Michelle's Friends
* Sean
* Kim
* David
Kim's Friends
* Michelle
* Sean
* David
如果David点击,URL 将变为/2/3(Kim 的 ID,然后是 David 的 ID),用户界面将如下所示。
Michelle's Friends
* Sean
* Kim
* David
Kim's Friends
* Michelle
* Sean
* David
David's Friends
* Sean
* Kim
只要用户想点击Links,这个过程就会重复进行。
一旦你设置好了正确的数据结构,下一步的重要工作就是持续渲染 `a`Route和一些Links`s`。因为我们要创建无限路由,所以需要确保Route每次点击 `a` 时都会渲染一个 `s` Link。否则,我们将无法获得更多匹配项,这意味着 React Router 将不会渲染任何组件。在我们的 `a`Link和 `s`中Route,我们需要知道应用程序的当前路径名,以便在每次Link点击 `a` 时将其追加到该路径名上(就像上面的示例一样,我们从 `a` 跳转/2到/2/3`a`,以此类推)。幸运的是,React Router 会通过 `s` 提供路径名match.url。考虑到这一点,我们的 `s` 的初始部分Link将如下所示:
<Link to={`{match.url}/${id}}>
我们渲染的内容Route将按照类似的模式进行匹配,然后渲染相同的组件。
<Route path={`${match.url}/:id`} component={Person}/>
现在我们已经掌握了基础知识,让我们开始构建将要递归渲染的组件Person。
请记住,这个组件需要负责几件事。
1)它应该为该特定用户的每个好友渲染一个 Link 组件。2
)它应该渲染一个 Route 组件,该组件将匹配当前路径名 + /:id。
与所有递归问题一样,我们需要以某种方式“启动”递归。通常这涉及到调用函数,但如果递归调用的是一个组件,我们可以简单地创建该元素来实现。
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
const users = [
{ id: 0, name: 'Michelle', friends: [ 1, 2, 3 ] },
{ id: 1, name: 'Sean', friends: [ 0, 3 ] },
{ id: 2, name: 'Kim', friends: [ 0, 1, 3 ], },
{ id: 3, name: 'David', friends: [ 1, 2 ] }
]
const Person = ({ match }) => {
return (
<div>
PERSON
</div>
)
}
class App extends React.Component {
render() {
return (
<Router>
<Person />
</Router>
)
}
}
export default App
现在我们需要做的就是从数组中获取特定朋友的信息,users以便获取他们的姓名并渲染他们的好友列表。你可能会注意到这里有个问题。最终,`<path>` 组件Person会被 React Router 渲染,因此它会被传递一个matchprop。我们将使用这个matchprop 来获取当前路径名,并(借助 `<path>` 组件users)获取该人的姓名和好友列表。问题在于,我们Person在主App组件内部手动渲染 `<path>` 组件以启动递归。这意味着match在第一次渲染时,`<path>` 组件的值将是 `undefined` Person。解决这个问题的方法比看起来要简单。当我们第一次手动渲染 `<path>` 组件时,<Person />我们需要match像 React Router 那样传递一个 prop。
class App extends React.Component {
render() {
return (
<Router>
<Person match={{ params: { id: 0 }, url: '' }}/>
</Router>
)
}
}
现在,每次Person渲染时(包括第一次渲染),都会传递一个matchprop,其中包含我们需要的两样东西,url用于渲染我们的Route和Links,params.id这样我们就可以确定正在渲染的是哪个人。
好了,回到正题。Person需要
1)它应该为该特定用户的每个好友渲染一个 Link 组件。2
)它应该渲染一个 Route 组件,该组件将匹配当前路径名 + /:id。
我们先来解决第一个问题。在渲染任何Link对象之前,我们需要获取该人物的好友信息。我们已经id从某个地方知道了该人物的好友match.params.id信息。利用这些信息和相应Array.find的方法,获取好友信息应该非常简单。我们将为此创建一个辅助函数。
const users = [
{ id: 0, name: 'Michelle', friends: [ 1, 2, 3 ] },
{ id: 1, name: 'Sean', friends: [ 0, 3 ] },
{ id: 2, name: 'Kim', friends: [ 0, 1, 3 ], },
{ id: 3, name: 'David', friends: [ 1, 2 ] }
]
const find = (id) => users.find(p => p.id == id)
const Person = ({ match }) => {
const person = find(match.params.id)
return (
<div>
PERSON
</div>
)
}
慢慢接近目标了。现在我们已经有了这个人,接下来渲染一些用户界面,包括Link他/她的每个朋友的信息。
const users = [
{ id: 0, name: 'Michelle', friends: [ 1, 2, 3 ] },
{ id: 1, name: 'Sean', friends: [ 0, 3 ] },
{ id: 2, name: 'Kim', friends: [ 0, 1, 3 ], },
{ id: 3, name: 'David', friends: [ 1, 2 ] }
]
const find = (id) => users.find(p => p.id == id)
const Person = ({ match }) => {
const person = find(match.params.id)
return (
<div>
<h3>{person.name}’s Friends</h3>
<ul>
{person.friends.map((id) => (
<li key={id}>
<Link to={`${match.url}/${id}`}>
{find(id).name}
</Link>
</li>
))}
</ul>
</div>
)
}
我们离完成只差一步之遥了。既然我们已经Link为每个用户的朋友都创建了一个页面(如第 2 点所述),我们还需要确保我们也渲染一个页面Route。
const Person = ({ match }) => {
const person = find(match.params.id)
return (
<div>
<h3>{person.name}’s Friends</h3>
<ul>
{person.friends.map((id) => (
<li key={id}>
<Link to={`${match.url}/${id}`}>
{find(id).name}
</Link>
</li>
))}
</ul>
<Route path={`${match.url}/:id`} component={Person}/>
</div>
)
}
完整的代码现在看起来像这样
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
const find = (id) => users.find(p => p.id == id)
const users = [
{ id: 0, name: 'Michelle', friends: [ 1, 2, 3 ] },
{ id: 1, name: 'Sean', friends: [ 0, 3 ] },
{ id: 2, name: 'Kim', friends: [ 0, 1, 3 ], },
{ id: 3, name: 'David', friends: [ 1, 2 ] }
]
const Person = ({ match }) => {
const person = find(match.params.id)
return (
<div>
<h3>{person.name}’s Friends</h3>
<ul>
{person.friends.map((id) => (
<li key={id}>
<Link to={`${match.url}/${id}`}>
{find(id).name}
</Link>
</li>
))}
</ul>
<Route path={`${match.url}/:id`} component={Person}/>
</div>
)
}
class App extends React.Component {
render() {
return (
<Router>
<Person match={{ params: { id: 0 }, url: '' }}/>
</Router>
)
}
}
export default App
第一次Person渲染时,我们传递一个模拟match对象。然后,Person渲染一个列表Link,以及Route一个与列表中任意一个Link元素匹配的元素。当Link点击一个元素时,Route匹配到的元素会渲染另一个Person组件,该组件又会渲染一个列表Link和一个新的元素Route。理论上,只要用户持续点击任何Link元素,这个过程就会无限循环下去。
本文最初发表于TylerMcGinnis.com,是其React Router课程的一部分。
文章来源:https://dev.to/tylermcginnis/recursive-paths-with-react-router-4b0g