网上看了N篇文章,有几篇比较高级的我看不太懂,其他基本全是吹逼复制不讲实质性东西的,我结合了一些自己的见解,写下了这篇可直接复制可用的,虽然还有基于流的解决方案,但是资料不多,时间也有限,就采用这个吧暂时。
1.create-react-app
2.src同级目录下新建server文件夹,新建index.js和entry.js
3.第一个问题:让koa支持import
//index.js 写koa
import Koa from 'koa';
const app = new Koa();
app.listen(8889, () => {
console.log('ssr starting');
})
//entry.js 作为入口文件处理koa使用import
require('babel-register')({
presets:["env","react"]
});
require('babel-polyfill');
module.exports = require('./index');
//依赖
"devDependencies": {
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-register": "^6.26.0"
}
4.第二个问题:我之前使用的是react提供的lazy和Suspense来处理code splitting,做SSR的时候发现不行,改为react-loadable,
抽离组件,新建util>loadable>index.js
import React from 'react';
import Loadable from 'react-loadable';
const loadingComponent = () => {
return (
<div>我正在加载哦哦哦哦哦哦哦哦!</div>
)
}
export default ((loader,loading = loadingComponent) => {
return Loadable({
loader,
loading
})
})
使用react-loadable
//App.js
import React from 'react';
import './App.css';
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
import loadable from './util/loadable';
import Home from './pages/home';
import ReactTransitionGroup from './pages/react-transition-group';
const About = loadable(() => import('./pages/about'));
function App() {
return (
<div className="App">
<h1>react-example</h1>
<Router>
<Route path='/' exact component={Home}></Route>
<Route path='/about' exact component={About}></Route>
<Route path='/reactTransitionGroup' component={ReactTransitionGroup}></Route>
</Router>
</div>
);
}
export default App;
5.接下来就是服务端(大家都懂的,前面试了N+1次)
server/index.js
// 关于react
import React from 'react';
import Router from 'koa-router';
import { StaticRouter } from 'react-router-dom';//react的路由换成这个
import { renderToString } from 'react-dom/server';//这个是渲染react组件的
import Home from '../src/pages/home';//这个是首页,需要渲染的页面
// //关于koa
import Koa from 'koa';
import KoaStatic from 'koa-static';
// // 关于node
import fs from 'fs';
import path from 'path';
const app = new Koa();
const router = new Router();
router.get('/', async (ctx) => {
//我也不知道这个context是干什么用的
const context = {};
//把你react App组件内的东西放到这里来!你可以尝试直接返回出结果,就是root内容
const reactAppString = await renderToString(
(
<div className="App">
<h1>react-example</h1>
<StaticRouter
location={ctx.url}
context={context}
>
<Home />
</StaticRouter>
</div>
)
);
//indexFile先把文件读取出来
const indexFile = await path.resolve(__dirname + 'build/index.html').replace('server','');
const data = await fs.readFileSync(indexFile,'utf8',function(err,data){
return data;
});
//替换root内容为之前的reactAppString
let dataReplaced = await data.replace('<div id="root"></div>',`<div id="root">${reactAppString}</div>`);
console.log(dataReplaced)
//最后返回出去
ctx.body = dataReplaced;
});
app.use(router.routes());
app.use(router.allowedMethods());
//static记得一定要写在这里,不然首页会默认导到build/index.html,root内没有内容
app.use(KoaStatic(
path.resolve(__dirname + 'build').replace('server', '')
));
app.listen(8889, () => {
console.log('ssr starting');
})
渲染结果:
最后总结一下:首屏SSR就是用户访问服务器,服务器返回出带有内容的root,然后再交由客户端,我这里的话基本能够满足我的需求,但是还有数据获取的问题没有解决,因为涉及的问题会有点多,我感觉新人真的不太合适做SSR,我自己接下来也打算往umi+ts+antd+dva这里去探探,看看有没有方便一些的解决办法。