关于 React Router 4 的一切
关于 React Router 4 的一切我在 React Rally 2016 大会上第一次遇到了 Michael Jackson,不久之后便写了一篇 an article on React Router 3。Michael 与 Ryan Florence 都是 React Router 的主要作者。遇到一位我非常喜欢的工具的创建者是激动人心的,但当他这么说的时候,我感到很震惊。“让我向你们展示我们在 React Router 4 的想法,它的方式是截然不同的!”。老实说,我真的不明白新的方向以及为什么它需要如此大的改变。由于路由是应用程序架构的重要组成部分,因此这可能会改变一些我喜欢的模式。这些改变的想法让我很焦虑。考虑到社区凝聚力以及 React Router 在这么多的 React 应用程序中扮演着重要的角色,我不知道社区将如何接受这些改变。 几个月后,React Router 4 发布了,仅仅从 Twitter 的嗡嗡声中我便得知,大家对于这个重大的重写存在着不同的想法。这让我想起了第一个版本的 React Router 针对其渐进概念的推回。在某些方面,早期版本的 React Router 符合我们传统的思维模式,即一个应用的路由“应该”将所有的路由规则放在一个地方。然而,并不是每个人都接受使用嵌套的 JSX 路由。但就像 JSX 自身说服了批评者一样(至少是大多数),许多人转而相信嵌套的 JSX 路由是很酷的想法。 如是,我学习了 React Router 4。无可否认,第一天是挣扎的。挣扎的倒不是其 API,而更多的是使用它的模式和策略。我使用 React Router 3 的思维模式并没有很好地迁移到 v4。如果要成功,我将不得不改变我对路由和布局组件之间的关系的看法。最终,出现了对我有意义的新模式,我对路由的新方向感到非常高兴。React Router 4 不仅包含 v3 的所有功能,而且还有新的功能。此外,起初我对 v4 的使用过于复杂。一旦我获得了一个新的思维模式,我就意识到这个新的方向是惊人的! 本文的意图并不是重复 React Router 4 已经写得很好的文档。我将介绍最常见的 API,但真正的重点是我发现的成功模式和策略。 对于本文,以下是一些你需要熟悉的 JavaScript 概念:
如果你喜欢跳转到演示区的话,请点这里: 查看演示 新的 API 和新的思维模式React Router 的早期版本将路由规则集中在一个位置,使它们与布局组件分离。当然,路由可以被划分成多个文件,但从概念上讲,路由是一个单元,基本上是一个美化的配置文件。 或许了解 v4 不同之处的最好方法是用每个版本编写一个简单的两页应用程序并进行比较。示例应用程序只有两个路由,对应首页和用户页面。 这里是 v3 的: import { Router,Route,IndexRoute } from 'react-router' const PrimaryLayout = props => ( <div className="primary-layout"> <header> Our React Router 3 App </header> <main> {props.children} </main> </div> ) const HomePage =() => <div>Home Page</div> const UsersPage = () => <div>Users Page</div> const App = () => ( <Router history={browserHistory}> <Route path="/" component={PrimaryLayout}> <IndexRoute component={HomePage} /> <Route path="/users" component={UsersPage} /> </Route> </Router> ) render(<App />,document.getElementById('root')) 以下是 v3 中的一些核心思想,但在 v4 中是不正确的:
React Router 4 不再主张集中式路由了。相反,路由规则位于布局和 UI 本身之间。例如,以下是 v4 中的相同的应用程序: import { BrowserRouter,Route } from 'react-router-dom'
const PrimaryLayout = () => (
<div className="primary-layout">
<header>
Our React Router 4 App
</header>
<main>
<Route path="/" exact component={HomePage} />
<Route path="/users" component={UsersPage} />
</main>
</div>
)
const HomePage =() => <div>Home Page</div>
const UsersPage = () => <div>Users Page</div>
const App = () => (
<BrowserRouter>
<PrimaryLayout />
</BrowserRouter>
)
render(<App />,document.getElementById('root'))
新的 API 概念:由于我们的应用程序是用于浏览器的,所以我们需要将它封装在来自 v4 的 对于使用 React Router v4 构建的应用程序,首先看到的是“路由”似乎丢失了。在 v3 中,路由是我们的应用程序直接呈现给 DOM 的最巨大的东西。 现在,除了 另一个在 v3 的例子中有而在 v4 中没有的是,使用 包容性路由在前面的例子中,你可能已经注意到了 在上一个例子中,我们试图根据路径渲染 要更好地了解匹配逻辑,请查看 path-to-regexp,这是 v4 现在正在使用的,以确定路由是否匹配 URL。 为了演示包容性路由是有帮助的,我们在标题中包含一个 const PrimaryLayout = () => (
<div className="primary-layout">
<header>
Our React Router 4 App
<Route path="/users" component={UsersMenu} />
</header>
<main>
<Route path="/" exact component={HomePage} />
<Route path="/users" component={UsersPage} />
</main>
</div>
)
现在,当用户访问 排他性路由如果你只需要在路由列表里匹配一个路由,则使用 const PrimaryLayout = () => (
<div className="primary-layout">
<PrimaryHeader />
<main>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/users/add" component={UserAddPage} />
<Route path="/users" component={UsersPage} />
<Redirect to="/" />
</Switch>
</main>
</div>
)
在给定的 当然,如果我们以某种方式使用 如果遇到, “默认路由”和“未找到”尽管在 v4 中已经没有 嵌套布局你可能开始期待嵌套子布局,以及如何实现它们。我原本不认为我会纠结这个概念,但我确实纠结了。React Router v4 给了我们很多选择,这使它变得很强大。但是,选择意味着有选择不理想策略的自由。表面上看,嵌套布局很简单,但根据你的选择,可能会因为你组织路由的方式而遇到阻碍。 为了演示,假设我们想扩展我们的用户部分,所以我们会有一个“用户列表”页面和一个“用户详情”页面。我们也希望产品也有类似的页面。用户和产品都需要其个性化的子布局。例如,每个可能都有不同的导航选项卡。有几种方法可以解决这个问题,有的好,有的不好。第一种方法不是很好,但我想告诉你,这样你就不会掉入这个陷阱。第二种方法要好很多。 第一种方法,我们修改 const PrimaryLayout = props => {
return (
<div className="primary-layout">
<PrimaryHeader />
<main>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/users" exact component={BrowseUsersPage} />
<Route path="/users/:userId" component={UserProfilePage} />
<Route path="/products" exact component={BrowseProductsPage} />
<Route path="/products/:productId" component={ProductProfilePage} />
<Redirect to="/" />
</Switch>
</main>
</div>
)
}
虽然这在技术上可行的,但仔细观察这两个用户页面就会发现问题: const BrowseUsersPage = () => (
<div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <BrowseUserTable /> </div> </div>
)
const UserProfilePage = props => (
<div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <UserProfile userId={props.match.params.userId} /> </div> </div> )
新 API 概念: 每个用户页面不仅要渲染其各自的内容,而且还必须关注子布局本身(并且每个子布局都是重复的)。虽然这个例子很小,可能看起来微不足道,但重复的代码在一个真正的应用程序中可能是一个问题。更不用说,每次 这里有另一种更好的方法: const PrimaryLayout = props => {
return (
<div className="primary-layout">
<PrimaryHeader />
<main>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/users" component={UserSubLayout} />
<Route path="/products" component={ProductSubLayout} />
<Redirect to="/" />
</Switch>
</main>
</div>
)
}
与每个用户和产品页面相对应的四条路由不同,我们为每个部分的布局提供了两条路由。 请注意,上述示例没有使用 通过这种策略,渲染其它路由将成为子布局的任务。 const UserSubLayout = () => (
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route path="/users" exact component={BrowseUsersPage} />
<Route path="/users/:userId" component={UserProfilePage} />
</Switch>
</div>
</div>
)
新策略中最明显的胜出在于所有用户页面之间的不重复布局。这是一个双赢,因为它不会像第一个示例那样具有相同生命周期的问题。 有一点需要注意的是,即使我们在布局结构中深入嵌套,路由仍然需要识别它们的完整路径才能匹配。为了节省重复输入(以防你决定将“用户”改为其他内容),请改用 const UserSubLayout = props => (
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route path={props.match.path} exact component={BrowseUsersPage} />
<Route path={`${props.match.path}/:userId`} component={UserProfilePage} />
</Switch>
</div>
</div>
)
匹配到目前为止, match.path vs match.url起初这两者之间的区别似乎并不清楚。控制台日志有时会显示相同的输出,这使得它们之间的差异更加模糊。例如,当浏览器路径为 const UserSubLayout = ({ match }) => {
console.log(match.url) // 输出:"/users"
console.log(match.path) // 输出:"/users"
return (
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route path={match.path} exact component={BrowseUsersPage} />
<Route path={`${match.path}/:userId`} component={UserProfilePage} />
</Switch>
</div>
</div>
)
}
ES2015 概念: 虽然我们看不到差异,但 选择哪一个?如果你要使用其中一个来帮助你构建路由路径,我建议你选择 const UserComments = ({ match }) => (
<div>UserId: {match.params.userId}</div>
)
const UserSettings = ({ match }) => (
<div>UserId: {match.params.userId}</div>
)
const UserProfilePage = ({ match }) => (
<div>
User Profile:
<Route path={`${match.url}/comments`} component={UserComments} />
<Route path={`${match.path}/settings`} component={UserSettings} />
</div>
)
为了说明问题,我渲染了两个子组件,一个路由路径来自于
那么为什么 直到后来我看到文档的这一部分,才意识到它有多重要:
避免匹配冲突假设我们制作的应用程序是一个仪表版,所以我们希望能够通过访问 const UserSubLayout = ({ match }) => (
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route exact path={props.match.path} component={BrowseUsersPage} />
<Route path={`${match.path}/add`} component={AddUserPage} />
<Route path={`${match.path}/:userId/edit`} component={EditUserPage} />
<Route path={`${match.path}/:userId`} component={UserProfilePage} />
</Switch>
</div>
</div>
)
请注意,为了确保进行适当的匹配,新增和编辑路由需要战略性地放在详情路由之前。如果详情路径在前面,那么访问 或者,如果我们这样创建路径 授权路由在应用程序中,通常会根据用户的登录状态来限制用户访问某些路由。对于未经授权的页面(如“登录”和“忘记密码”)与已授权的页面(应用程序的主要部分)看起来不一样也是常见的。为了解决这些需求,需要考虑一个应用程序的主要入口点: class App extends React.Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/auth" component={UnauthorizedLayout} />
<AuthorizedRoute path="/app" component={PrimaryLayout} />
</Switch>
</BrowserRouter>
</Provider>
)
}
}
使用 react-redux 与 React Router v4 非常类似,就像之前一样,只需将 通过这种方法可以得到一些启发。第一个是根据我们所在的应用程序的哪个部分,在两个顶层布局之间进行选择。像访问 虽然 class AuthorizedRoute extends React.Component {
componentWillMount() {
getLoggedUser()
}
render() {
const { component: Component,pending,logged,...rest } = this.props
return (
<Route {...rest} render={props => {
if (pending) return <div>Loading...</div>
return logged
? <Component {...this.props} />
: <Redirect to="/auth/login" />
}} />
)
}
}
const stateToProps = ({ loggedUserState }) => ({
pending: loggedUserState.pending,logged: loggedUserState.logged
})
export default connect(stateToProps)(AuthorizedRoute)
可能你的登录策略与我的不同,我是使用网络请求来 点击此处查看 CodePen 上完整的身份验证示例。 其他提示React Router v4 还有很多其他很酷的方面。最后,一定要提几件小事,以免到时它们让你措手不及。
|