本文翻译自: An Introduction to React Router v4 and its Philosophy Toward Routing
React Router 4 引入了一种基于 component 的动态路由.
这篇文章中会讨论思考 React Router 背后的哲学,同时也会通过分析 React Router 文档中的示例代码来介绍一下它的语法.
更多 React Router 的介绍请戳 这里 .
不想看文字版本还可以点击 这里 看视频.
如果你这几年一直在密切关注着 React,你应该会注意到 React Router 已经经历了多个版本的迭代.今天的 v4 版本更是一个巨大的改变.
产生这些变化的原因都是非常自然的 -- 今天 React 的开发者相比于 React Router 刚产生的时候更有经验.在 2014 年的时候,人人都是新手.没有人会想到 component 的概念在不足一年的 React 中会有这么重要.
因为上面的原因,React Router 的第一个 commit 是这样的:
当时,React Router 的作者 Michael 和 Ryan 都有着 Ember 的开发经验.自然着,React Router 的第一个版本和 Ember 的路由有点相似 -- 都是静态着去建立路由,作为应用初始化的一部分.
这种路由的概念就和熟悉的 Express,Angular,Ember 的概念一致.甚至在 React Router 4 release 之前,使用的也是静态路由.
使用静态路由的时候往往会有这样的一段代码卸载 routes.js 里面:
const routes = (
<Router>
<Route path='/' component={Main}>
<IndexRoute component={Home} />
<Route path='playerOne' component={Prompt} />
<Route path='playerTwo/:playerOne' component={Prompt} />
<Route path='battle' component={ConfirmBattle} />
<Route path='results' component={Results} />
<Route onEnter={checkAuth} path='dashboard' component={Dashboard} />
</Route>
</Router>
)
export default routes
然后在初始化应用的时候,会把路由导入,然后渲染:
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import routes from './config/routes'
ReactDOM.render(routes, document.getElementById('app'))
这就产生了一个问题:静态路由不好用吗?
答案显示是 "不,好用".但大家仍然会觉得静态路由模式不是 React 的风格.
在 React Router 诞生以来,其作者不仅对构建复杂应用的路由有了更多的经验,同时对 React 本身也有了更多的理解.后面在工作与讨论中发现,React Router 的 API 和 React 背后遵循的一些原则有点背道相驰.再回过头来看下前面的代码,我们将一个 onEnter 的 prop 传给 Route 组件.
<Route onEnter={checkAuth} path='dashboard' component={Dashboard} />
这段代码是指在渲染 dashboard 组件的时候需要经过一层鉴权.这听起来像是在 Dashboard 的 componentDidMount 生命周期里去做一些事情?的确就是这样.
在 React Router 4 之前,它所做的事情有点超出路由这个层面.React Router 4 针对这些问题作出了修正,使得他和 React 相处的更好.如果你了解 React 以及它使用 component 的优势,React Router 4 会让你感觉更加亲切 -- 你需要先忘记你对传统静态路由的一些了解.
现在还有一个问题是:React Router 4 到底做了什么不再让他和 React 有冲突呢?答案是他抛弃了静态路由而开始偏向于使用动态路由,所有的 API 都是基于 component.这也就意味着声明路由的时候就像普通组件一模一样.
简单看下下面的例子:
先从最基本的代码开始,然后为其添加路由功能.
import React, { Component } from 'react'
class App extends Component {
render() {
return (
<div>
React Rotuer Course
</div>
)
}
}
export default App
和我前面所说的一样,React Router 4 就是一个普通的 component.因此第一件事是 import 我们所需要的东西.
import {
BrowserRouter as Router,
Route,
Link,
} from 'react-router-dom'
这里需要注意一些事情.首先,我们 import 了 BrowserRouter 并将之重新命名为 Router.这虽然不是必须的,但在代码中还是很常见的.BrowerRouter 所做的事情就是允许 React Router 将应用的路由信息传给任何他需要的组件(通过 context).因此,要让 React Router 正常工作,需要在应用程序的根节点中渲染 BrowerRouter.
import React, { Component } from 'react'
import {
BrowserRouter as Router,
Route,
Link,
} from 'react-router-dom'
class App extends Component {
render() {
return (
<Router>
<div>
React Rotuer Course
</div>
</Router>
)
}
}
export default App
接下来将使用 Route.Route 是 React Router 4 背后的支撑.当应用程序的 location 匹配到某个路由的时候,Route 将渲染指定的 component,否则渲染 null.举个例子,当我们应用的路由是 / 的时候要渲染一个 Home 组件,代码会长的像下面这样:
import React, { Component } from 'react'
import {
BrowserRouter as Router,
Route,
Link,
} from 'react-router-dom'
const Home = () => (
<h2>Home</h2>
)
class App extends Component {
render() {
return (
<Router>
<div>
<Route path='/' component={Home} />
</div>
</Router>
)
}
}
export default App
上面的代码中,如果我们的路径是 / 的时候,将看见 Home 组件.如果不是的话,什么都看不到(Route 会渲染 null).
下面将加入更多的路由:
import React, { Component } from 'react'
import {
BrowserRouter as Router,
Route,
Link,
} from 'react-router-dom'
const Home = () => (
<div>
<h2>Home</h2>
</div>
)
const About = () => (
<div>
<h2>About</h2>
</div>
)
const Topics = () => (
<div>
<h2>Topics</h2>
</div>
)
class App extends Component {
render() {
return (
<Router>
<div>
<Route path='/' component={Home} />
<Route path='/about' component={About} />
<Route path='/topics' component={Topics} />
</div>
</Router>
)
}
}
export default App
如上,如果想在应用中加入更多的路由,只需要渲染更多的 Route 组件.如果你还没有忘记静态路由,可能会对渲染路由这种事情感到奇怪.
你只需要记住 Route 只是一个具有渲染方法的普通 React 组件,改渲染方法渲染组件还是 null 取决于 path 是否匹配.因为在上面的例子中,要么渲染组件,要么渲染 null.
在上面代码中有一个点需要注意:运行这个程序的时候,前往 about 路径,About 组件和 Home 组件都会被渲染.这是因为即使 / 不是完全匹配的,但仍旧会认为是部分匹配,所以 Home 组件也会被渲染.为了解决这种问题,需要在 / 的 Route 中添加一个 exact 的 prop,来确保只有完全匹配的时候才会渲染.
<Route exact path='/' component={Home} />
现在我们通过应用的 location 在动态的渲染 UI,下一件事情就是如何去改变应用的 location.这正是 Link 组件所要做的事情,它是一个允许用户声明性的浏览应用的组件.现在,使用 Link 添加一个简单的导航吧.
render() {
return (
<Router>
<div>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/about'>About</Link></li>
<li><Link to='/topics'>Topics</Link></li>
</ul>
<Route path='/' component={Home} />
<Route path='/about' component={About} />
<Route path='/topics' component={Topics} />
</div>
</Router>
)
}
事实上现在已经介绍完了 React Router 4 的基本操作.我们基于应用的 location 使用 Route 去渲染不同的组件,并且通过 Link 组件来更改应用的 location.
更深一点,一起看看嵌套的路由.嵌套路由是 React Router 之前版本的一个基础功能,今天它仍然也是.与之前版本相比,最大的区别就是现在创建嵌套路由的方式.在之前的版本中,只需要在路由配置中嵌套的使用路由,但今天有雨 React Router 4 是动态路由,所以这样做是行不通的.但是就我而言,觉得 React Router 4 的嵌套路由比之前版本的更加直接.再次强调一下:忘记之前你对静态路由了解的一切.
再看一下我们的例子,如果我们想要 Topics 组件渲染一个嵌套的 导航和其他的一些路由该怎么做呢?不需要很复杂,就像嵌套一个 div 一样,你只需要嵌套使用 Route.
const Topic = () => {
<div>
<h3>TOPIC</h3>
</div>
}
const Topics = () => (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`/topics/rendering`}>
Rendering with React
</Link>
</li>
<li>
<Link to={`/topics/components`}>
Components
</Link>
</li>
<li>
<Link to={`/topics/props-v-state`}>
Props v. State
</Link>
</li>
</ul>
<Route path={`/topics/rendering`} component={Topic} />
<Route path={`/topics/components`} component={Topic} />
<Route path={`/topics/props-v-state`} component={Topic} />
</div>
)
现在当用户导航到 /topics 时,将看到一个嵌套的导航栏,UI 也会随着 location 的变化而自动改变.唯一的区别是我们现在正在通过 React Router 在一个组件内部渲染 navbar 和 Route
你可能会注意到我们都是在硬编码 URL,而不是通过当前嵌套的位置来动态创建.React Router 在渲染一个组件的时候,它会传递三个东西:match,location 和 history.在这个例子中,我们想要的是 match.url,它会给我们当前 URL 中匹配的部分(在我们的例子中,/,/topics).所以在任何我们不好硬编码 /topic 的地方,都可以使用 match.url 替换.
const Topic = () => {
<div>
<h3>TOPIC</h3>
</div>
}
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>
Rendering with React
</Link>
</li>
<li>
<Link to={`${match.url}/components`}>
Components
</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>
Props v. State
</Link>
</li>
</ul>
<Route path={`${match.url}/rendering`} component={Topic} />
<Route path={`${match.url}/components`} component={Topic} />
<Route path={`${match.url}/props-v-state`} component={Topic} />
</div>
)
还有另外一件事情你可能会注意到:即使渲染相同的组件,我们也在渲染三个不同的 Route,他们之前唯一的区别是嵌套的 URL.下面是使用 url 参数的金典例子.
const Topics = ({ match }) => (
<div>
...
<Route path={`${match.url}/:topicId`} component={Topic} />
</div>
)
React Router 渲染 Topic 组件时,使用了之前介绍过的 match 属性,与此类似,还可以使用 match.params 下的 topicId.
const Topic = ({ match }) => (
<div>
<h3>{match.params.topicId}</h3>
</div>
)
最后,当我们处于 /topics 路由时,如果某个主题还未被选中,我们想渲染一个文字,比如:Please select a topic.我们可以创建一个渲染文本的组件或者使用 Route 的 render 属性:
<Route
exact
path={match.url}
render={() => ( <h3>Please select a topic.</h3> )}
/>
就这样,我们酷酷的代码会长得像这样:
import React, { Component } from 'react'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
const Home = () => (
<div>
<h2>Home</h2>
</div>
)
const About = () => (
<div>
<h2>About</h2>
</div>
)
const Topic = ({ match }) => (
<div>
<h3>{match.params.topicId}</h3>
</div>
)
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>
Rendering with React
</Link>
</li>
<li>
<Link to={`${match.url}/components`}>
Components
</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>
Props v. State
</Link>
</li>
</ul>
<Route path={`${match.url}/:topicId`} component={Topic}/>
<Route exact path={match.url} render={() => (
<h3>Please select a topic.</h3>
)}/>
</div>
)
class App extends Component {
render() {
return (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/topics">Topics</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</Router>
)
}
}
export default App
React Router 是一个以 component 为 API 的 Router,是一个真正意义上的 React Router.我相信 React 会让你成为更好的 JavaScript 开发者,而 React Router 4 会让你成为更好的 React 开发者.
想要与作者交流?点击 这里查看原文 了解更多
来源: https://juejin.im/post/5a641747518825732d7fb25f