-
Notifications
You must be signed in to change notification settings - Fork 320
服务端渲染
一、准备动作
1、安装nodejs与安装express
安装nodejs教程:http://www.cnblogs.com/pigtail/archive/2013/01/08/2850486.html
安装express教程:http://www.expressjs.com.cn/starter/installing.html
2、安装node-jsx(使nodejs支持jsx语法)
$ npm install node-jsx
3、安装ejs模板引擎
$ npm install ejs
在项目中建立一个app.js,输入
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
var server = app.listen(3000, function () {
console.log("请在浏览器访问:http://localhost:3000/");
});
通过如下命令启动此应用:
$ node app.js
打开浏览器,输入localhost:3000就看到效果了。
使用模板引擎,我们在建立一个test目录,里面再建一个views目录,模块文件都放里面。
var express = require("express");
var app = express();
//指定模板引擎
app.set("view engine", 'ejs');
//指定模板位置
app.set('views', __dirname + '/test/views');
//利用模板文件home.ejs渲染为html
app.get("/", function(req, res) {
res.render('home.ejs', {
name: '司徒正美'
});
});
var server = app.listen(3000, function() {
console.log("请在浏览器访问:http://localhost:3000/");
});
然后我们模块home.ejs
<html>
<head>
<title>my ejs template</title>
</head>
<body>
<p>Hi <%= name %></p>
</body>
</html>
如果你嫌弃ejs后缀文件,你的编辑器无法别识(没有语法高亮),可以改进一下,将home.ejs改为home.html
var express = require("express");
var app = express();
//指定模板引擎
var ejs = require('ejs');
app.set("view engine", 'ejs');
//指定模板位置
app.set('views', __dirname + '/test/views');
//使用ejs模板引擎解析html视图文件
app.engine('.html',ejs.__express);
//利用模板文件home.ejs渲染为html
app.get("/", function(req, res) {
res.render('home.html', {//这里指定文件名
name: '司徒正美'
});
});
var server = app.listen(3000, function() {
console.log("请在浏览器访问:http://localhost:3000/");
});
我们先看一下官方react15.3如何实现后端渲染的
npm install react
npm install react-dom
在Test目录下建立一个components目录,里面建一个Test.js,表示这里是一个类
var React=require("react");
class Test extends React.Component{
render(){
return <h1>{this.props.name}</h1>;
}
}
module.exports = Test
然后修改app.js
var express = require("express");
var app = express();
//指定模板引擎
var ejs = require('ejs');
app.set("view engine", 'ejs');
//指定模板位置
app.set('views', __dirname + '/test/views');
//使用ejs模板引擎解析html视图文件
app.engine('.html',ejs.__express);
//................
//安装"node-jsx",安装该模块可以使nodejs兼容jsx语法
require("node-jsx").install()
var React = global.React = require("react");
var ReactDOMServer = require('react-dom/server')
var Test = require('./test/component/Test.js') //引入React组件
//利用模板文件home.ejs渲染为html
app.get("/", function(req, res) {
res.render('home.html', {//这里指定文件名
component: ReactDOMServer.renderToString( React.createElement( Test,{name:"司徒正美"}) )
})
})
//................
var server = app.listen(3000, function() {
console.log("请在浏览器访问:http://localhost:3000/");
});
然后将模板改一下
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>react 后端渲染</title>
</head>
<body>
<div id="container">
<%-component%>
</div>
<!--使用ejs模板解析后的html字符串-->
</body>
</html>
如果想使用anu的后端渲染方案,主要改一下链接就是
var express = require("express");
var app = express();
//指定模板引擎
var ejs = require('ejs');
app.set("view engine", 'ejs');
//指定模板位置
app.set('views', __dirname + '/test/views');
//使用ejs模板引擎解析html视图文件
app.engine('.html',ejs.__express);
//................
//安装"node-jsx",安装该模块可以使nodejs兼容jsx语法
require("node-jsx").install()
var React = global.React = require("./dist/React");
//var ReactDOMServer = require('react-dom/server')
var ReactDOMServer = require('./dist/ReactDOMServer')
var Test = require('./test/components/Test.js') //引入React组件
//利用模板文件home.ejs渲染为html
app.get("/", function(req, res) {
res.render('home.html', {//这里指定文件名
component: ReactDOMServer.renderToString( React.createElement( Test,{name:"司徒正美"}) )
})
})
//................
var server = app.listen(3000, function() {
console.log("请在浏览器访问:http://localhost:3000/");
});
但现在前端是一个静态页面,没有JS ,我们让它能活动起来 设置一下静态资态的目录,我把React.js, babel.js什么放到这里上
//app.js
app.use(express.static('dist'));
重写一下Test目录,让它有事件
var React=require("../../dist/React");
class Test extends React.Component{
click(){
console.log('=========')
}
render(){
return <h1>{this.props.name}
<p onClick={this.click.bind(this)}>事件</p>
</h1>;
}
}
module.exports = Test
home.html也改一下
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>react 后端渲染</title>
<script src='React.js'></script>
<script src='babel.js'></script>
<script type='text/babel'>
class Test extends React.Component{
click(){
console.log('=========')
}
render(){
return <h1>{this.props.name}
<p onClick={this.click.bind(this)}>事件</p>
</h1>;
}
}
window.onload = function(){
ReactDOM.render(<Test name='司徒正美' />, document.getElementById('container'))
}
</script>
</head>
<body>
<div id="container">
<%-component%>
</div>
<!--使用ejs模板解析后的html字符串-->
</body>
</html>
这个后端渲染与前端渲染有什么区别呢?后端渲染会为你的根组件生成的标签添加两个属性data-reactroot
与 data-react-checksum
。其中后者是为了兼容官网React,anu只需要前者就行了。在前端的ReactDOM.render方法里面,anu会检测插入位置的所有直接孩子,判定它有没有data-reactroot
属性,有则进入对齐模式。对齐模式与传统的创建模式不一样。
创建模式是根据虚拟DOM创建一棵真实DOM树,然后移除原容器的所有孩子,插入其中。
对齐模式是因为后端已经将所有孩子直接创建好,但可能会多出一些文本节点。这时它只根据虚拟DOM 的type与真实DOM 的**node.toLowerCase()**进行比较就是。速度肯定快上几个数量级。
而在实际项目中,我们可以通过babel将Test(会去掉里面的module.export = Test
)及其他代码进行打包,不会直接写在页面上的。这样一来 ,就可以达到前后共享一套代码。
https://cnodejs.org/topic/5660f8f9d0bc14ae27939b37 http://blog.csdn.net/mqy1023/article/details/55051788 http://imweb.io/topic/5547892e73d4069201d83e6c http://blog.techbridge.cc/2016/08/27/react-redux-immutablejs-node-server-isomorphic-tutorial/ https://blog.coding.net/blog/React-Server-Side-Rendering-for-SPA-SEO
``html
<script type='text/javascript' src="./dist/React.js"></script> <script type='text/javascript' src="./test/react.development.js"></script> <script type='text/javascript' src="./test/react-dom.development.js"></script><script type='text/javascript' src="./lib/babel.js"></script>
<pre>应该等于
</pre>
<h1 id='root' class="root">
</h1>
<script type='text/babel'>
var container = document.getElementById("example")
var div = container
var PropTypes = React.PropTypes
if(!window.ReactDOM){
window.ReactDOM = React
}
var expect = function(a) {
return {
toBe: function(b) {
console.log(a,"vs", b, a === b)
},
toEqual(b){
console.log(a,"vs", b, a +""=== b+"")
},
toThrow(b){
try{
a()
}catch(e){
console.log(e,"vs", b, e.message +""=== b+"")
}
}
}
}
const Hello = ({ name }) => <h1>Hello {name}!</h1>;
class ShowMyError extends React.Component {
constructor(props) {
super(props);
this.state = { error: false };
}
componentDidCatch(error, info) {
console.log(error, info)
this.setState({ error, info });
}
render() {
if (this.state.error) {
return (
<div>
<h1>
Error AGAIN: {this.state.error.toString()}
</h1>
{this.state.info &&
this.state.info.componentStack.split("\n").map(i => {
return (
<div key={i}>
{i}
</div>
);
})}
</div>
);
}
return this.props.children;
}
}
class Broken extends React.Component {
constructor(props) {
super(props);
this.state = { throw: false, count: 0 };
}
render() {
if (this.state.throw) {
throw new Error("YOLO");
}
return (
<div>
<button
onClick={e => {
this.setState({ throw: true });
}}
>
button will render error.
</button>
<button onClick={e => {
this.setState(({ count }) => ({
count: count + 1
}));
}}>button will not throw</button>
<div>
{"All good here. Count: "}{this.state.count}
</div>
</div>
);
}
}
class App extends React.Component {
render() {
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
return (
<div style={styles}>
<Hello name="ShowMyError" />
<h2>
Start clicking to see some {"\u2728"}magic{"\u2728"}
</h2>
<ShowMyError>
<Broken />
</ShowMyError>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));