「译」 你可能不需要 react-router
如果你正好使用 Facebook 的 React.js 工作一段时间,你可能注意到有一些错误的(misconceptions)观点弥漫在 React 的社区。其中有一个观点就是:React 只是 MVC 中的 V,如果进行 Web Application 的开发,需要配合大量的其他类型的库。
实际上,你很少看到 React 开发者使用 MVC 中的 Controller 和 Models。组件化的 UI 架构稳定的掌管着前端社区,如今,越来越少的人在使用 MVC 的模式。
另外一个错误的观点就是认为 React Router(RR) 是 Facebook 的官方路由库。实际上,Facebook 的大部分项目都不会使用 RR。
谈到路由(routing),绝大部分的 Web Application 项目和使用场景,只需要一些小的定制化的库就能做的非常好。在你将这种想法归为异类之前,请让我向你演示一下,如何用不到 50 行的代码实现一个全功能的路由。
导航(Navigation)
首先,像 RR 一样,使用一个组件将路由和客户端的导航绑在一起并不是必须的。这种方式,可以实现真正的 Universal - 在客户端和服务器端,利用同样的 API,使用同样的工作方式。有一个非常赞的 npm 模块叫做「history」,它可以搞定导航这部分(请知悉,这是 HTML5 的 History API的一层封装,被 RR 内置使用)。你只需要在你的项目中初始化这个组件(类)的地方,创建一个 history.js 文件,然后在你的 App 中把它当作一个单例使用:
import createHistory from 'history/lib/createBrowserHistory';
import useQueries from 'history/lib/useQueries';
export default useQueries(createHistory)();
从现在开始,你可以引用这个文件,然后在任何你想将一个用户跳转到一个新地址(URL)的时候,使用history.push('/new-page’)
就好了,而且还不需要刷新整个页面。在应用的主文件里面(bootstrap code),你可以通过以下方式订阅 URL 改变:
import history from './history';
function render(location) { /* Render React app, read on */ }
render(history.getCurrentLocation()); // render the current URL
history.listen(render); // render subsequent URLs
一个工作在客户端的带有链接的 React 组件可能长这个样:
import React from 'react';
import history from '../history';
class App extends React.Component {
transition = event => {
event.preventDefault();
history.push({
pathname: event.currentTarget.pathname,
search: event.currentTarget.search
});
};
render() {
return (
<ul>
<li><a href="/" onClick={this.transition}>Home</a></li>
<li><a href="/one" onClick={this.transition}>One</a></li>
<li><a href="/two" onClick={this.transition}>Two</a></li>
</ul>
);
}
}
尽管如此,实际上你可能想把 ”transition” 功能单独剥离出来成为一个 React 组件。 观察 React Static Boilerplate (RSB)中的Link 组件。你现在可以将客户端的链接写成
<Link to=”/some-page”>Click</Link>.
需要在用户离开页面的时候添加一个跳转信息页面?只像 history 模块文档中描述的那样,在组件中 componentDidMount()
方法中注册一个 history.listenBefore(..)
事件处理器就好了。同样可以给页面切换添加动画。
路由(routing)
你可以直接使用原生 JavaScript 对象来描述路由列表,而不是 JSX,例如:
const routes = [
{ path: '/', action: () => <HomePage /> },
{ path: '/tasks', action: () => <TaskList /> },
{ path: '/tasks/:id', action: () => <TaskDetails /> }
];
BTW,如果有人知道为什么小伙伴更喜欢使用 JSX 来做一些同 UI 渲染无关的事情,请留个言;
你可以使用 ES2015 + async/await 语法来自己写路由处理器,并没有什么需要像在 RR中一样使用回调函数,比如:
{
path: '/tasks/:id(\\d+)',
async action({ params }) {
const resp = await fetch(`/api/tasks/${params.id}`);
const data = await resp.json();
return data && <TaskDetails {...data} />;
}
}
我所熟悉的绝大部分你情况下,并没有啥需要像 RR 中那样使用嵌套路由。使用嵌套路由会让事情变得比实际上更加复杂,并且会导致非常复杂非常难维护的路由实现问题。据我所知,即使是在 Facebook,在他们非常多的应用中(至少不是所有的项目中),不会在客户端使用嵌套路由。
你可以使用嵌套的 React 组件而不是嵌套路由,比如:
import React from 'react';
import Layout from '../components/Layout';
class AboutPage extends React.Component {
render() {
return (
<Layout title="About Us" breadcrumbs="Home > About">
<h1>Welcome!</h1>
<p>Here your can learn more about our product.</p>
</Layout>
);
}
}
export default AboutPage;
这种方式的实现比嵌套路由简单、有更好的灵活性、更偏向直觉并且可以打开更多的使用场景(注意你可以将 Breadcrumbs 的组件传递到 Layout 中)
路由本身可以被写成一对函数(两个) - matchURI(),一个用来辅助比较参数化路径和实际路径字符串的内部(private)的函数;Resolve()函数,用来查询路由列表,找到匹配地址的路由,然后执行对应的路由处理函数,最后将结果返回给调用者。这里是一个例子:
import toRegex from 'path-to-regexp';
function matchURI(path, uri) {
const keys = [];
const pattern = toRegex(path, keys); // TODO: Use caching
const match = pattern.exec(uri);
if (!match) return null;
const params = Object.create(null);
for (let i = 1; i < match.length; i++) {
params[keys[i - 1].name] =
match[i] !== undefined ? match[i] : undefined;
}
return params;
}
async function resolve(routes, context) {
for (const route of routes) {
const uri = context.error ? '/error' : context.pathname;
const params = matchURI(route.path, uri);
if (!params) continue;
const result = await route.action({ ...context, params });
if (result) return result;
}
const error = new Error('Not found');
error.status = 404;
throw error;
}
export default { resolve };
查看 path-to-regex 的官方文档。这个库简直屌炸天!比例,你可以使用同样的这个库将一个参数化的路径转化成一个URI的形式:
const toUrlPath = pathToRegexp.compile('/tasks/:id(\\d+)')
toUrlPath({ id: 123 }) //=> "/user/123"
toUrlPath({ id: 'abc' }) /=> error, doesn't match the \d+ constraint
现在你可以将应用的主文件(入口文件)使用下面的路由进行升级:
import ReactDOM from 'react-dom';
import history from './history';
import router from './router';
import routes from './routes';
const container = document.getElementById('root');
function renderComponent(component) {
ReactDOM.render(component, container);
}
function render(location) {
router.resolve(routes, location)
.then(renderComponent)
.catch(error => router.resolve(routes, { ...location, error })
.then(renderComponent));
}
render(history.getCurrentLocation()); // render the current URL
history.listen(render); // render subsequent URLs
就是这样!你们可能想看看我有这些特性的 React 样例项目:
这些样例项目在全球很多的实际场景中也是非常的流行和成功。毫无疑问,他们值得一试!
原文链接:https://medium.com/@tarkus/you-might-not-need-react-router-38673620f3d#.2l0c10mgg