本文旨在帮助读者在react router v4下搭建本身的知足seo需求的server side render app。react
reactgit
很少说了,近年来发展迅猛,被各大主流应用普遍使用;github
react-router-v4web
react路由作了一次较大的改动,若是你的app是16年或者更早搭建的话,相信路由仍是v3版本,不过没有关系,你能够参照本文尝试如今就升级你的router,v4会给你带来更多的灵活性。express
与v3相比,v4主要有如下更改:npm
匹配规则
本文假设你已经熟悉react,redux和express,而且构建过本身的app;redux
1. 安装React Router v4;promise
npm i --save react-router-dom react-router-config
复制代码
或用yarn
,react-router-config
是web的router,它进一步封装了react-router
, (native app的安装包是react-router-native
), 而且保留了一些react-router
的接口,因此若是你对系统已经装了react-router
,建议删除,直接用react-router-config
就好。bash
2. 配置路由;react-router
若是你在用v3,那么你可能有这样一个集中式的路由文件:
import React from 'react';
import { Route } from 'react-router';
const Routes = () => (
<Route path="/" onEnter={() => {}} onChange={() => {}}>
<Route path=":channel/abc" component={MyContainer1} />
<Route path=":channel/def" component={MyContainer2} />
<Route path=":channel/*" component={NotFoundContainer} status={404} />
</Route>
);
export default Routes;
复制代码
v4是大相径庭的作法,首先,定义一个你的路由配置:
# routes.js
import RootApp from './RootApp';
import Home from './Home';
import List from './List';
const routes = [
{ component: RootApp,
routes: [ # 多级嵌套
{ path: '/',
exact: true,
component: Home
},
{ path: '/home',
component: Home
},
{ path: '/list',
component: List
}
]
}
];
export default routes;
复制代码
在你的client端,使用<BrowserRouter>
render你的routes, 你的client.js
长成这个样子:
# client.js
import React from 'react';
import {render} from 'react-dom';
import BrowserRouter from 'react-router-dom/BrowserRouter';
import { renderRoutes } from 'react-router-config';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import routes from './routes'; # 上文定义的routes配置;
import reducers from './modules';
const store = createStore(
reducers, window.__INITIAL_STATE__, applyMiddleware(thunk)
);
const AppRouter = () => {
return (
<Provider store={store}>
<BrowserRouter> # 使用v4的BrowserRouter;
{renderRoutes(routes)} # 使用v4的renderRoutes;
</BrowserRouter>
</Provider>
)
}
render(<AppRouter />, document.querySelector('#app'));
复制代码
在你的server端,使用<StaticRouter>
,eg. server.js
:
# server.js
import express from 'express';
import request from 'request';
import React from 'react';
import { renderToString } from 'react-dom/server';
import StaticRouter from 'react-router-dom/StaticRouter';
import { renderRoutes } from 'react-router-config';
import routes from './routes'; # 上文定义的routes配置;
const router = express.Router();
router.get('*', (req, res) => {
let context = {};
const content = renderToString(
<StaticRouter location={req.url} context={context}>
{renderRoutes(routes)}
</StaticRouter>
);
res.render('index', {title: 'Express', data: false, content });
});
module.exports = router;
复制代码
实现你的root component:
# RootApp.js
import React from "react";
import { renderRoutes } from "react-router-config";
const RootApp = (props) => {
return (
<div>
{renderRoutes(props.route.routes)} # 分布式,若是你使用嵌套路由,在你的每一个father component上作相似的render;
</div>
);
};
export default RootApp;
复制代码
renderRoutes
会帮你作相似这样的事情:
render() {
return(
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/home" component={Home}/>
<Route path="/list" component={List}/>
</Switch>
)
}
复制代码
3. fetch 数据
若是你的app须要fetch数据,v4下可使用如下几种方式:
集中式:
在你的路由配置中把fetch动做配置好,而后server.js内统一处理:
# routes.js
import RootApp from './RootApp';
import Home from './Home';
import List from './List';
import {fetchRootData,fetchHomeData, fetchListData} from './fetchData';
const routes = [
{ component: RootApp,
fetchData: () => {fetchRootData}
routes: [
{ path: '/',
exact: true,
component: Home,
fetchData: () => {fetchHomeData}
},
{ path: '/home',
component: Home,
fetchData: () => {fetchHomeData}
},
{ path: '/list',
component: List,
fetchData: () => {fetchListData}
}
]
}
];
export default routes;
复制代码
# server.js
import express from 'express';
import request from 'request';
import React from 'react';
import { renderToString } from 'react-dom/server';
import StaticRouter from 'react-router-dom/StaticRouter';
import { renderRoutes } from 'react-router-config';
import routes from './routes'; # 上文定义的routes配置;
const router = express.Router();
router.get('*', (req, res) => {
const {path, query} = req;
const matchedRoutes = matchRoutes(routes, path); # 注意,这里使用path作match而不是req.url, 由于req.url内含query,v4 router在作正则match的时候并不会过滤query;
const store = configureStore();
const dispatch = store.dispatch;
const promises = matchedRoutes.map(({route}) => {
let fetchData = route.fetchData;
return fetchData instanceof Function ? fetchData(store) : Promise.resolve(null)
});
return Promise.all(promises)
.then(throwErrorIfApiResponseFailed(store)) # implement yourself
.then(handleSuccessPage(store, req, res)) # server side <StaticRouter> render, implement yourself
.catch(handleError(res, query)); # error handler, return error page and error code, implement yourself
});
module.exports = router;
复制代码
这种方式集中配置,集中处理,可是有一个问题须要注意的是client side如何fetchData,v4已不直接支持history.listen(client url变化时能够拦截并追加一系列操做), 因此须要寻找一种方式让client side也能拿到数据。
分布式:
使用react生命周期函数,在合适的时机触发Action请求数据,将fetch数据动做散落在每一个component级;
#
componentDidMount() {
fetchData();
}
复制代码
你可能会有疑问,这只能给client用啊,server side怎么办?你能够把fetchData Action这样绑到component上,而后server side也能够统一处理:
# RootApp
class List extends Component {
static fetchData(store) {
return store.dispatch(fetchUsers());
}
componentDidMount() {
this.props.fetchUsers();
}
render() {
return <div></div>
}
}
复制代码
# server.js
import express from 'express';
import request from 'request';
import React from 'react';
import { renderToString } from 'react-dom/server';
import StaticRouter from 'react-router-dom/StaticRouter';
import { renderRoutes } from 'react-router-config';
import routes from './routes'; # 上文定义的routes配置;
const router = express.Router();
router.get('*', (req, res) => {
const {path, query} = req;
const matchedRoutes = matchRoutes(routes, path);
const store = configureStore();
const dispatch = store.dispatch;
const promises = matchedRoutes.map(({route}) => {
let fetchData = route.component.fetchData;
return fetchData instanceof Function ? fetchData(store) : Promise.resolve(null)
});
return Promise.all(promises) # server side 集中请求数据;
.then(throwErrorIfApiResponseFailed(store))
.then(handleSuccessPage(store, req, res)) # server side <StaticRouter> render, implement yourself
.catch(handleError(res, query)); # error handler, return error page and error code, implement yourself
});
module.exports = router;
复制代码
5. Handle 404 错误页面
# routes.js
import RootApp from './RootApp';
import Home from './Home';
import List from './List';
const routes = [
{ component: RootApp,
routes: [
{ path: '/',
exact: true,
component: Home
},
{ path: '/home',
component: Home
},
{ path: '/list',
component: List
},
{
+ path: '*',
+ component: NotFound
}
]
}
];
export default routes;
复制代码
你的NotFound component本身维护错误码;
# Notfound.js
import React from 'react';
import { Route } from 'react-router-dom';
const NotFound = () => {
return (
<Route render={({ staticContext }) => {
if (staticContext) {
staticContext.status = 404;
}
return (
<div>
<h1>404 : Not Found</h1>
</div>
)
}}/>
);
};
export default NotFound;
复制代码
server 端:
# server.js
import express from 'express';
import request from 'request';
import React from 'react';
import { renderToString } from 'react-dom/server';
import StaticRouter from 'react-router-dom/StaticRouter';
import { renderRoutes } from 'react-router-config';
import routes from './routes'; # 上文定义的routes配置;
const router = express.Router();
router.get('*', (req, res) => {
let context = {};
const content = renderToString(
<StaticRouter location={req.url} context={context}>
{renderRoutes(routes)}
</StaticRouter>
);
+ if(context.status === 404) { # 获取状态码并响应;
+ res.status(404);
+ }
res.render('index', {title: 'Express', data: false, content });
});
module.exports = router;
复制代码
5. Handle redirects重定向
# routes.js
import AppRoot from './AppRoot';
import Home from './Home';
import List from './List';
import NotFound from './Notfound';
+import ListToUsers from './ListToUsers';
const routes = [
{ component: AppRoot,
routes: [
{ path: '/',
exact: true,
component: Home
},
{ path: '/home',
component: Home
},
+ { path: '/list',
+ component: ListToUsers
+ }
+ { path: '/users',
+ component: List
+ }
{
path: '*',
component: NotFound
}
]
}
];
export default routes;
复制代码
与404 的处理相似,component本身维护状态码:
# ListToUsers.jsx
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const ListToUsers = () => {
return (
<Route render={({ staticContext }) => {
if (staticContext) {
staticContext.status = 302;
}
return <Redirect from="/list" to="/users" /> # react redirect
}}/>
);
};
export default ListToUsers;
复制代码
server 端:
# server.js
import express from 'express';
import request from 'request';
import React from 'react';
import { renderToString } from 'react-dom/server';
import StaticRouter from 'react-router-dom/StaticRouter';
import { renderRoutes } from 'react-router-config';
import routes from './routes'; # 上文定义的routes配置;
const router = express.Router();
router.get('*', (req, res) => {
let context = {};
const content = renderToString(
<StaticRouter location={req.url} context={context}>
{renderRoutes(routes)}
</StaticRouter>
);
if(context.status === 404) {
res.status(404);
}
+ if (context.status === 302) { # 获取状态码并响应;
+ return res.redirect(302, context.url);
+ }
res.render('index', {title: 'Express', data: false, content });
});
module.exports = router;
复制代码
关于react-router-v4 如何作 server side render就介绍到这里啦,示例中有部分为伪代码。v4 让你更加灵活的处理多页面。