同构流程是这样:
- 当浏览器请求到服务端后,服务端先得到页面数据,用数据渲染react组件,然后转成html string,插入到待返回的html中,然后将html返回给浏览器;
- 在返回给浏览器的html中存在一段“同构代码”,就是react组件用webpack打包之后的js文件。
- 当浏览器获得server返回的html之后,解析html呈现页面,可以快速的显示出html内容(执行到同构代码的时候,会再次渲染一次,但是用户感知不到,react有机制可以将开销最小化)
- 当页面出现交互行为,发请求其实走的就是浏览器端执行的代码了,可以去请求API或者其他操作。
server
index.js
const app = new (require('koa'));
const mount = require('koa-mount');
const serveStatic = require('koa-static');
const getData = require('./get-data')
const ReactDOMServer = require('react-dom/server');
const App = require('./app.jsx')
const template = require('./template')(__dirname + '/index.htm')
app.use(mount('/static', serveStatic(__dirname + '/source')))
app.use(mount('/data', async (ctx) => {
ctx.body = await getData(+(ctx.query.sort || 0), +(ctx.query.filt || 0));
}));
app.use(async (ctx) => {
ctx.status = 200;
// 准备数据
const filtType = +(ctx.query.filt || 0)
const sortType = +(ctx.query.sort || 0);
const reactData = await getData(sortType, filtType);
// console.log(ReactDOMServer.renderToString(ReactRoot));
ctx.body = template({
// react组件转化成字符串
reactString: ReactDOMServer.renderToString(
App(reactData)
),
reactData,
filtType,
sortType
})
})
app.listen(3000)
// module.exports = app;
app.jsx
const React = require('react')
const Container = require('../component/container.jsx')
module.exports = function (reactData) {
return <Container
columns={reactData}
filt={() => { }}
sort={() => { }}
/>
}
“../component/container.jsx”就是一个react 组件,不贴了;
static也不贴了;
template就是之前讲的模版引擎代码,也不贴了;
get-data.js
const listClient = require('./list-client');
module.exports = async function (sortType = 0, filtType = 0) {
// 使用微服务拉取数据
const data = await new Promise((resolve, reject) => {
// 这里RPC请求去拿数据
listClient.write({
sortType,
filtType
}, function (err, res) {
err ? reject(err) : resolve(res.columns);
})
});
return data
}
listClient.js是利用easy-sock去后端发起全双工RPC通信拿数据,也不贴了。
browser
浏览器部分主要就是一个react代码,以及webpack的打包配置,生成的文件放到static里面
index.jsx
const Container = require('../component/container.jsx');
const React = require('react');
const ReactDOM = require('react-dom');
class App extends React.Component {
constructor() {
super();
this.state = {
columns: reactInitData,
filtType: reactInitFiltType,
sortType: reactInitSortType
}
}
render() {
return (
<Container
columns={this.state.columns}
filt={(filtType) => {
fetch(`./data?sort=${this.state.sortType}&filt=${filtType}`)
.then(res => res.json())
.then(json => {
this.setState({
columns: json,
filtType: filtType
})
})
}}
sort={(sortType) => {
fetch(`./data?sort=${sortType}&filt=${this.state.filtType}`)
.then(res => res.json())
.then(json => {
this.setState({
columns: json,
sortType: sortType
})
})
}}
/>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('reactapp')
)
这里和serverRender中引用的都是同样的组件,挂的都是#reactapp的节点,但是不同的是,这里需要处理交互事件,所以可以看到刚才在server端的sort、filter都是空函数,而在这里都是fetch并setState了。
最后,看下webpack.congif.js
module.exports = {
mode: 'development',
devtool: false,
entry: __dirname + '/index.jsx',
output: {
filename: 'main.js',
path: __dirname + '/../node/source/'
},
module: {
rules: [
{
test: /\.jsx$/, use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-react']
}
}
}
]
}
}