class Route extends Component {
static propTypes = {
exact: PropTypes.bool,
path: PropTypes.string,
component: PropTypes.func,
render: PropTypes.func,
}
render () {
const {
path,
exact,
component,
render,
} = this.props
const match = matchPath(
location.pathname, // global DOM variable
{ path, exact }
)
if (!match) {
// Do nothing because the current
// location doesn't match the path prop.
return null
}
if (component) {
// The component prop takes precedent over the
// render method. If the current location matches
// the path prop, create a new element passing in
// match as the prop.
return React.createElement(component, { match })
}
if (render) {
// If there's a match but component
// was undefined, invoke the render
// prop passing in match as an argument.
return render({ match })
}
return null
}
}
构建您自己的React Router v4
当我第一次开始学习客户端应用程序中的路由时,我还记得感觉。当时我只是一个小孩,仍然让我的脚湿润了这整个“单页申请”的事情,我会说谎,如果我说它没有在我的大脑上一个粪便。从一开始就好像我的大脑将我的应用程序代码和路由器代码视为两个独特而独特的想法。他们就像步行兄弟,他们彼此不喜欢,但是被迫一起生活在一起。
在过去几年中,我可能在这一点上反对你的批准,幸运的是能够将这种想法传递给其他开发人员。不幸的是,事实证明,我们大多数的大脑似乎与我的类似。我认为这有一些原因。首先,路由一般是相当复杂的。这对于那些图书馆作者来说,找到正确的路由抽象更复杂。第二,由于这种复杂性,路由库的消费者倾向于盲目地信任抽象,而不会真正理解发生了什么。在本教程中,我们将深入解决这两个问题。首先,通过重新创建我们自己的React Router v4的简化版本,然后再说明一下,就是RRv4是否合理的抽象。
这是我们的应用程序代码,我们将使用它来测试我们的〜React路由器实现一旦我们构建它。你可以在这里玩最后的例子
如果您不熟悉React Router v4,这是基本的前提。
Route
当URL与您在路线的path
道具中指定的位置匹配时,会渲染一些UI 。Link
s提供了一种声明性,可访问的方式来浏览您的应用程序。换句话说,Link
组件允许您更新URL,Route
组件根据该新URL更改您的UI。本教程的重点并不在于教授RRV4的基础知识,所以如果上面的代码仍然令人困惑,请转到正式的文档,并附上示例,一旦你更舒适,回来。你应该注意到的第一件事是,我们已经介绍了给我们路由器到我们的应用程序,两个组件,
Link
和Route
。我最喜欢的React Router v4是API是“Just Components™”。这意味着,如果您已经熟悉React,您对组件的相同直觉以及如何撰写它们将继续适用于您的路由代码。对于我们在这里使用的情况更加方便,因为我们已经熟悉如何创建组件,创建我们自己的React路由器将不仅仅是我们已经熟悉的,创建更多的组件。我们将从创建我们的
Route
组件开始。在我们深入了解代码之前,让我们先看看API(它正好是哪个道具需要的)。在上面的例子中,您会注意到
<Route>
可以使用三个道具。exact
,path
和component
。这意味着propTypes
我们的Route
组件目前看起来像这样,这里有几个细节。首先,原因
path
不是必需的,因为如果Route
没有给出路径,它将自动呈现。第二,原因component
没有标记为必需,因为实际上有几种不同的方式告诉React Router要路由匹配的UI。一个不在我们上面的例子中的方法就是render
道具。看起来像这样,render
允许您方便地内联返回一些UI的功能,而不是创建单独的组件。所以我们还将把它添加到我们的propTypes中,现在我们知道道具
Route
收到了什么,让我们再来谈一下实际的做法。路线“会在URL与您在路线的path
道具中指定的位置匹配时呈现一些UI 。基于这个定义,我们知道这<Route>
将需要一些功能来检查当前的URL是否匹配组件的path
支持。如果是这样,我们将渲染一些UI。如果没有,我们将返回null来执行任何操作。让我们看看代码中看起来像什么,相信我们会建立匹配函数
matchPath
,稍后我们将会调用这个函数。现在
Route
看起来很实在 如果当前位置与path
传入的prop 相匹配,我们会渲染一些UI,否则,我们不做任何事情。让我们退一步,谈一下路由。在客户端应用程序中,用户只需两种方式来更新URL。第一种方法是点击锚标签,第二种方式是单击后退/转发按钮。从基础上看,我们的路由器需要了解当前的URL并基于它渲染UI。这也意味着我们的路由器需要意识到URL何时改变,以便它可以根据该新URL找出要显示的新UI。如果我们知道更新URL的唯一方法是通过锚标签或前进/后退按钮,我们可以计划并对这些更改做出反应。稍后,当我们构建
<Link>
组件时,我们将进入锚标签,但现在我想集中在后退/前进按钮。反应路由器使用历史记录的.listen
方法来监听当前URL的更改,但为避免引入另一个库,我们将使用HTML5的popstate
事件。popstate
,这将在用户点击前进或后退按钮时触发,正是我们需要的。因为正是Route
基于当前URL渲染UI,所以Route
当发生popstate
事件时,还可以让侦听和重新呈现的能力也是有意义的。通过重新渲染,每个都Route
将重新检查它们是否与新的URL匹配。如果他们这样做,他们会渲染UI,否则,他们什么都不做。让我们看看现在这个样子,您应该注意到,我们完成的所有操作都是
popstate
在组件挂载时添加一个监听器,当popstate
事件被触发时,我们调用forceUpdate
它将启动重新渲染。现在,无论
<Route>
我们渲染多少,每个人都会根据前进/后退按钮进行聆听,重新匹配和重新渲染。有一件事我们一直在“挥手”,直到这一点是我们的
matchPath
功能。这个功能对我们的路由器至关重要,因为它是一个功能,它将决定当前URL是否与<Route>
上面讨论的组件路径匹配。一个细微差别matchPath
是,我们需要确保我们考虑到<Route>
小号exact
道具。如果你不熟悉什么exact
,这里直接来自文档/one
/one/two
true
/one
/one/two
false
现在,我们来看看我们的
matchPath
功能的实现。如果你回顾我们的Route
组件,你会看到这样的签名matchPath
,match
根据是否有匹配,对象或空值在哪里。基于这个签名,我们可以构建这样的第一部分matchPath
,这里我们使用了一些ES6魔法。我们在说“创建一个名为exact的变量,等同于options.exact,除非未定义,然后将其设置为false。还要创建一个名为path的变量,这个变量等于options.path“。
之前我提到“原因
path
不是必需的,因为如果Route
没有给出路径,它将自动呈现”。那么因为它间接地是我们的matchPath
功能,决定是否有某物被渲染(通过是否有一个匹配),让我们现在添加这个功能。现在是匹配的部分。React Router 为此使用pathToRegex,我们将简化事情,只需使用一个简单的Regex。
如果你不熟悉
.exec
,它将返回一个包含匹配文本的数组,如果它找到一个匹配,否则返回null。match
当我们的示例应用程序路由到`/ topics /组件时,这里是每一个/
/topics/components
['/']
/about
/topics/components
null
/topics
/topics/components
['/topics']
/topics/rendering
/topics/components
null
/topics/components
/topics/components
['/topics/components']
/topics/props-v-state
/topics/components
null
/topics
/topics/components
['/topics']
现在我们知道
match
这.exec
是什么回报,我们现在需要做的就是弄清楚是否有一场比赛。之前,我提到如果您是用户,通过后退/前进按钮或点击achor标签,真正只需两种方法来更新网址。我们已经通过
popstate
我们的事件侦听器重新渲染了后退/转发点击Route
,现在我们通过构建我们的Link
组件来处理锚标签。API
Link
看起来像这样,to
字符串在哪里,是要链接到的位置,replace
是一个布尔值,如果为true,则单击链接将替换历史堆栈中的当前条目,而不是添加新的条目。将这些propTypes添加到我们的Link组件中,我们得到这个,
现在我们知道我们
Link
组件中的render方法需要返回一个锚标签,但是我们显然不希望在每次切换路由时都会重新整理页面,所以我们将通过添加一个onClick
处理程序来劫持锚标签现在所有缺少的都是改变现在的位置。要做到这一点阵营路由器使用历史的
push
和replace
方法,但我们将使用HTML5的[pushState的](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method "")和[replaceState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_replaceState()_method "")方法,以避免增加的依赖。双方
pushState
并replaceState
采取三个参数。第一个是与新的历史记录条目相关联的对象 - 我们不需要这个功能,所以我们只需要传递一个空的对象。第二个是标题,我们也不需要,所以我们将传入null。第三个,我们实际使用的是一个相对URL。现在在我们的
Link
组件内部,我们将调用historyPush
或historyReplace
依赖于replace
支持,现在还有一个我们需要做的另外一个重要的事情。如果您要使用我们当前的路由器代码与我们的示例应用程序一起玩,您会发现一个相当大的问题。当您浏览时,网址将更新,但UI将保持完全相同。这是因为即使我们使用我们的功能来改变位置,我们
historyReplace
也不知道这种变化,并且不知道应该重新渲染和重新匹配。为了解决这个问题,我们需要跟踪哪些已经被渲染,并且每当路由改变时调用它们。historyPush``<Route>``<Route>``forceUpdate
为了保持路由器的简单性,我们将
<Route>
通过将其实例推送到数组来跟踪已经呈现的映射,然后每当发生位置更改时,我们可以循环遍历该数组,并在所有实例上调用forceUpdate。请注意,我们创建了两个功能。
register
每当<Route>
安装unregister
时,我们都会打电话,无论何时卸载。然后,每当我们打电话historyPush
或historyReplace
(我们每次用户点击一次<Link>
),我们可以循环遍历这些实例forceUpdate
。我们先来更新我们的
<Route>
组件,现在,我们来更新
historyPush
和historyReplace
🎉现在每当
<Link>
点击并且位置发生变化时,每个<Route>
都会意识到这一点,并重新匹配并重新渲染。现在,我们的完整的路由器代码看起来像下面的代码,我们上面的示例应用程序完美地与它完成。
奖励:React Router API还附带了一个
<Redirect>
组件。使用我们以前写的代码,创建这个组件是非常简单的注意,这个组件实际上并没有渲染任何UI,而是纯粹作为路由导向器,因此这个名字。
我希望这可以帮助您为React Router创建更好的心理模型,同时帮助您更好地了解React Router的优雅和“Just Components”API。我一直说React会让你成为一个更好的JavaScript开发人员。我现在也相信React Router会让你成为一个更好的React开发者。因为一切只是组件,如果你知道React,你知道React Router。