php react ssr,利用React Router4实现的服务端直出渲染(SSR)

我们已经熟悉React 服务端渲染(SSR)的基本步骤,现在让我们更进一步利用 React RouterV4 实现客户端和服务端的同构。毕竟大多数的应用都需要用到web前端路由器,所以要让SSR能够正常的运行,了解路由器的设置是十分有必要的

基本步骤

路由器配置

前言已经简单的介绍了React SSR,首先我们需要添加ReactRouter4到我们的项目中

$ yarn add react-router-dom

# or, using npm

$ npm install react-router-dom

接着我们会描述一个简单的场景,其中组件是静态的且不需要去获取外部数据。我们会在这个基础之上去了解如何完成取到数据的服务端渲染。

在客户端,我们只需像以前一样将我们的的App组件通过ReactRouter的BrowserRouter来包起来。

src/index.js

import React from 'react';

import ReactDOM from 'react-dom';

import { BrowserRouter } from 'react-router-dom';

import App from './App';

ReactDOM.hydrate(

,

document.getElementById('root')

);

在服务端我们将采取类似的方式,但是改为使用无状态的 StaticRouter

server/index.js

app.get('/*', (req, res) => {

const context = {};

const app = ReactDOMServer.renderToString(

);

const indexFile = path.resolve('./build/index.html');

fs.readFile(indexFile, 'utf8', (err, data) => {

if (err) {

console.error('Something went wrong:', err);

return res.status(500).send('Oops, better luck next time!');

}

return res.send(

data.replace('

${app}
`)

);

});

});

app.listen(PORT, () => {

console.log(`😎 Server is listening on port ${PORT}`);

});

StaticRouter组件需要 location和context属性。我们传递当前的url(Express req.url)给location,设置一个空对象给context。context对象用于存储特定的路由信息,这个信息将会以staticContext的形式传递给组件

运行一下程序看看结果是否我们所预期的,我们给App组件添加一些路由信息

src/App.js

import React from 'react';

import { Route, Switch, NavLink } from 'react-router-dom';

import Home from './Home';

import Posts from './Posts';

import Todos from './Todos';

import NotFound from './NotFound';

export default props => {

return (

  • Home

  • Todos

  • Posts

exact

path="/"

render={props => }

/>

);

};

现在如果你运行一下程序($ yarn run dev),我们的路由在服务端被渲染,这是我们所预期的。

利用404状态来处理未找到资源的网络请求

我们做一些改进,当渲染NotFound组件时让服务端使用404HTTP状态码来响应。首先我们将一些信息放到NotFound组件的staticContext

import React from 'react';

export default ({ staticContext = {} }) => {

staticContext.status = 404;

return

Oops, nothing here!

;

};

然后在服务端,我们可以检查context对象的status属性是否是404,如果是404,则以404状态响应服务端请求。

server/index.js

// ...

app.get('/*', (req, res) => {

const context = {};

const app = ReactDOMServer.renderToString(

);

const indexFile = path.resolve('./build/index.html');

fs.readFile(indexFile, 'utf8', (err, data) => {

if (err) {

console.error('Something went wrong:', err);

return res.status(500).send('Oops, better luck next time!');

}

if (context.status === 404) {

res.status(404);

}

return res.send(

data.replace('

${app}
`)

);

});

});

// ...

重定向

补充一下,我们可以做一些类似重定向的工作。如果我们有使用Redirect组件,ReactRouter会自动添加重定向的url到context对象的属性上。

server/index.js (部分)

if (context.url) {

return res.redirect(301, context.url);

}

读取数据

有时候我们的服务端渲染应用需要数据呈现,我们需要用一种静态的方式来定义我们的路由而不是只涉及到客户端的动态的方式。失去定义动态路由的定义是服务端渲染最适合所需要的应用的原因(译者注:这句话的意思应该是SSR不允许路由是动态定义的)。

我们将使用fetch在客户端和服务端,我们增加isomorphic-fetch到我们的项目。同时我们也增加serialize-javascript这个包,它可以方便的序列化服务器上获取到的数据。

$ yarn add isomorphic-fetch serialize-javascript

# or, using npm:

$ npm install isomorphic-fetch serialize-javascript

我们定义我们的路由信息为一个静态数组在routes.js文件里

src/routes.js

import App from './App';

import Home from './Home';

import Posts from './Posts';

import Todos from './Todos';

import NotFound from './NotFound';

import loadData from './helpers/loadData';

const Routes = [

{

path: '/',

exact: true,

component: Home

},

{

path: '/posts',

component: Posts,

loadData: () => loadData('posts')

},

{

path: '/todos',

component: Todos,

loadData: () => loadData('todos')

},

{

component: NotFound

}

];

export default Routes;

有一些路由配置现在有一个叫loadData的键,它是一个调用loadData函数的函数。这个是我们的loadData函数的实现

helpers/loadData.js

import 'isomorphic-fetch';

export default resourceType => {

return fetch(`https://jsonplaceholder.typicode.com/${resourceType}`)

.then(res => {

return res.json();

})

.then(data => {

// only keep 10 first results

return data.filter((_, idx) => idx < 10);

});

};

我们简单的使用fetch来从REST API 获取数据

在服务端我们将使用ReactRouter的matchPath去寻找当前url所匹配的路由配置并判断它有没有loadData属性。如果是这样,我们调用loadData去获取数据并把数据放到全局window对象中在服务器的响应中

server/index.js

import React from 'react';

import express from 'express';

import ReactDOMServer from 'react-dom/server';

import path from 'path';

import fs from 'fs';

import serialize from 'serialize-javascript';

import { StaticRouter, matchPath } from 'react-router-dom';

import Routes from '../src/routes';

import App from '../src/App';

const PORT = process.env.PORT || 3006;

const app = express();

app.use(express.static('./build'));

app.get('/*', (req, res) => {

const currentRoute =

Routes.find(route => matchPath(req.url, route)) || {};

let promise;

if (currentRoute.loadData) {

promise = currentRoute.loadData();

} else {

promise = Promise.resolve(null);

}

promise.then(data => {

// Lets add the data to the context

const context = { data };

const app = ReactDOMServer.renderToString(

);

const indexFile = path.resolve('./build/index.html');

fs.readFile(indexFile, 'utf8', (err, indexData) => {

if (err) {

console.error('Something went wrong:', err);

return res.status(500).send('Oops, better luck next time!');

}

if (context.status === 404) {

res.status(404);

}

if (context.url) {

return res.redirect(301, context.url);

}

return res.send(

indexData

.replace('

${app}
`)

.replace(

'

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值