问题
使用路由来管理其所显示组件的内部状态通常很有帮助。例如,下面是一个React组件,它显示了两个信息选项卡,一个用于 /people ,一个用于 /offices :
当用户单击其中一个选项卡时,内部tabId变量会被更新,相应的People或Offices组件会显示出来(如图2-4所示)。
图2-4:默认情况下,OldAbout组件显示人员的详细信息
这有什么问题呢?该组件确实可以正常工作,但如果我们选中Offices选项卡并刷新页面,则该组件将选中状态重置为People选项卡。同样,当页面选中Offices选项卡时,我们也不能把页面加为书签。我们不能在应用程序的其他地方创建直接跳转到Offices选项卡的链接。无障碍访问设备不太可能注意到选项卡是作为超链接工作的——因为它们并不是以这种方式渲染的。
解决方案
我们将把tabId状态从组件移到当前浏览器的地址上。因此,我们不是在 /about 地址上渲染组件,然后使用onClick事件来改变内部状态,而是使用指向 /about/people 和 /about/offices 的路由,二者分别显示两个选项卡。选项卡在选中后,即使浏览器刷新了,也仍然会保持选中状态。我们可以给特定的页面选项卡添加书签或者创建超链接。同时,我们把选项卡做成真正的超链接后,任何使用键盘或屏幕阅读器导航的人都可以识别。
实现这种功能需要什么呢?只需要react-router-dom:
react-router-dom可以使得当前浏览器的URL与在屏幕上渲染的组件保持同步。
正如你在 App.js 文件中看到的代码实现,现存应用使用react-router-dom在路径为 /oldabout 时展示OldAbout组件:
你可以在GitHub仓库( https://oreil.ly/WmZ18 )中看到该文件的完整代码。
我们将OldAbout组件重构为一个新组件,名为About,并将它挂载在它自己的路由上:
添加了About组件后,应用现在既可以展示 /oldabout 路由,也可以展示 /about/:tabId? 路由。
新的About组件渲染的内容看起来几乎与旧组件完全相同。我们将从组件中提取tabId并将其移动到当前路径中。
Route组件的路径(path)属性被设置为 /about/:tabId? ,这意味着 /about 、 /about/offices 和 /about/people 都将挂载我们的组件。其中,“?”表示tabId为可选参数。
现在我们已经完成了第一部分:将组件的状态放入显示它的路径中。接下来,我们需要根据路由传递的参数来修改组件的代码,而不是像之前那样使用内部状态变量。
在OldAbout组件中,每个选项卡上都有onClick监听器:
我们将把它们转换成分别跳转到 /about/people 和 /about/offices 路径的Link组件。事实上,我们要把它们转换成NavLink组件。NavLink类似于Link,不同的是,如果它要跳转的地址是当前地址,那么它可以添加一个额外的CSS类名。这意味着我们不需要在原始代码中添加className逻辑了:
我们不再设置tabId变量的值,而是通过在路径中传入tabId的方式来传递tabId参数。
那么在代码实现中如何读取tabId值呢?OldAbout代码显示当前选项卡内容如下:
这段代码可以用Switch和Route组件实现:
我们现在差不多完成了。只剩下一个步骤:确定当路径是 /about 且不包含tabId时该做什么。
OldAbout在第一次创建状态时为tabId设置了一个默认值:
我们可以通过在Switch的末尾添加Redirect组件来实现同样的效果。Switch将按顺序遍历它的子组件,直到找到匹配的Route。如果没有与当前路径匹配的Route,Switch将运行Redirect,后者会将路由重定向到 /about/people 。这会导致About组件的重新渲染,默认情况下People选项卡将被选中:
你可以给Redirect组件指定from属性,这样Redirect就会根据当前路径选择是否执行重定向。在本例中,我们可以把from属性设置到/about,这样只有匹配/about的路由才会重定向到/about/people。
下面是我们完整的About组件:
我们不再需要内部的tabId变量了,现在我们得到了一个纯粹的声明性组件(如图2-5所示)。
图2-5:访问 http://localhost/about/offices 查看新组件效果
讨论
将状态移出组件并移到地址栏中可以简化代码,但这只是该方案额外带来的好处。真正的价值在于,你的应用程序开始变得不像应用程序,而更像一个网站。我们可以给页面添加书签,浏览器的后退和前进按钮也能正常工作。在路由中管理更多的状态不是一个抽象的设计决策,而是一种让你的应用程序设计对用户更加友好的方式。
你可以从GitHub网站( https://oreil.ly/myAGj )下载本解决方案的源代码。