发布于 2026-01-06 6 阅读
0

React Router V4 中的递归路径

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用于渲染我们的RouteLinks,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