这个世界真奇妙, 以前只有服务端渲染, 甚至html代码和后端语言代码都混编在一起, 后来Ajax无刷新加载流行一时, 慢慢有了前端框架, 使用前端路由, 干脆全部都是Ajax, 哈哈. 但是这SEO的问题却是个大问题, 同时使用服务端渲染可以加快首屏网页打开速度.
但总不能后端写一套代码, 前端也弄一套吧, 再个这路由也要一样啊, 所以必须还要共用一套代码. 当然这一切React都帮我们解决了. react 提供了 renderToString 方法可以将组件代码在后端渲染成Html代码发送给前端, 同时react-router 也提供了 match 方法在后端来匹配路由.
那还有个问题,如何初始化数据呢? 后端初始化了数据发送给前端, 前端共享这个数据, 不用再次发送请求. 还好有redux这东东, 我们只需要共享维护这一套状态状态树就OK了.
但是在实践的过程中也遇到一些问题, 顺便记录下来.
在前端渲染的情况下启动时会有大量的ajax请求初始化数据, 那如何方便的将这些需要初始化请求的一次性在后端执行. 根据Facebook前端工程师 Stepan 在第二届前端开发者大会的分享, 我们在每个容器组件中添加一个静态方法:
import * as Actions from '../actions'
static fetchData(params){
return [Actions.getUserInfo(),Actions.getIndexImage()]
}
这个方法返回一个数组, 里面是需要初始化请求的action方法. 也可以传入一些参数, 提供给action方法. 这里使用了redux,通过promise中间件, 让这些action都返回一个promise
export default function promiseMiddleware() {
return next => action => {
const { promise, type, ...rest } = action
if (!promise) return next(action)
return promise
.then(response => ({json: response.data, status: response.statusText}))
.then(({json,status}) => {
next({ ...rest, json, type: SUCCESS })
return true
})
.catch(error => {
next({ ...rest, error, type: FAILURE })
return false
})
}
}
那么在后端如何去处理呢.
async function fetchAllData(dispatch, components, params) {
const needs = components
.filter(x=>x.fetchData)
.reduce((prev,current)=>{
return current.fetchData(params).concat(prev)
},[])
.map(x=>{
return dispatch(x)
})
return await Promise.all(needs)
}
在后端我们通过一个fetchAllData函数, 将每个组件的的fetchData方法返回的action都提出来, 并全部dispatch, 同时这里使用async/await ES7异步处理语法, 就可以得到需要初始化的全部数据了. 通过window.INITIAL_STATE 传给前端.
但有些数据,可以是需要传cookie到服务器才能获取的, 比如通过是否存在token来判断是不是登录用户, 用户权限不一样,获取的数据不一样. 所以这部分要先获取cookie, 这里用到了react-cookie.
import reactCookie from 'react-cookie'
reactCookie.plugToRequest(req, res)
const history = createMemoryHistory()
const token = reactCookie.load('token') || null
const store = configureStore({auth:fromJS({
token: token,
user: null
})}, history)
如果是用chrome 扩展是没有问题的, 但是如果嵌入网页的话, 由于它只出现在开发环境的客户端, 并且不能放入前后共用的代码, 否则会出现前后渲染不一致的错误. 所以这里单独通过render方法追加到body最后.
import React from 'react'
import { render } from 'react-dom'
import DevTools from './components/DevTools'
export default function createDevTools(store) {
if(__DEVCLIENT__ && __DEVTOOLS__ && !window.devToolsExtension){
setTimeout(() => render(
<DevTools store={store} />,
window.document.body.appendChild(document.createElement('div'))
), 10)
}
}
得到初始化数据和renderToString转换的字符串, 要将这些加进html代码中, 通常用如下一个方法, 通过模板字符串替换变量, 如下:
function renderFullPage(renderedContent, initialState) {
return `<!doctype html>
<html>
<head>
<base href="/">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Jack Hu's blog for React</title>
<meta name="description" content="This is Jack Hu's blog. use react redux.">
<meta name="keyword" content="Jackblog react redux react-router react-redux-router react-bootstrap react-alert">
<link rel="stylesheet" href="/style.css"/>
</head>
<body class="day-mode">
<div class="top-box" id="root">${renderedContent}</div>
<script>
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}
</script>
<script type="text/javascript" charset="utf-8" src="/bundle.js"></script>
</body>
</html>
`
}
但是这种情况, css文件和js文件都是写死的, 在开发情况无所谓, 但是生产环境有时会给文件名加hash字符串. 所以这里通过webpack插件 html-webpack-plugin 生成 ejs模板来提供给server渲染.
new HtmlWebpackPlugin({
favicon:path.join(__dirname,'../src/favicon.ico'),
title: "Jackblog react redux版",
template: path.join(__dirname,'../src/index.html'),
filename: 'index.ejs',
inject:'body',
htmlContent:'<%- __html__ %>',
initialData:'window.__INITIAL_STATE__ = <%- __state__ %>',
hash:false, //为静态资源生成hash值
minify:{ //压缩HTML文件
removeComments:false, //移除HTML中的注释
collapseWhitespace:false //删除空白符与换行符
}
}),
服务端使用express, ejs模板引擎来渲染.
return res.render('index', {__html__: componentHTML,__state__: JSON.stringify(initialState)})
先记录来这吧. 以后有更好的解决办法再写下篇.
具体实现请参考: https://github.com/jackhutu/jackblog-react-redux 更新到了react v15.0.1
终于登录成功了
为什么评论发表后,,发表的文字还在评论框里
1
qqqq
博主您好,看了你的博客觉得挺好的,请问能讲一下怎么学习前端的这些东西吗,感觉现在前端新东西好多,js都变了好多,还有flux react vue之类的东西,感觉有点晕
凤飞飞
不错 上来测试一下
Good job.
Good job.
Hello World
test
test1
恩 !
1
dsa
2
test
xiexie
不错
晕
test
啊啊啊
hello world
hello
hello
hello
<script>alert(1);</script>
真可以登陆?
棒
请问 package.json 中 betterScripts 字段 是什么作用 有没有文档连接
有个bug,在首页跳转本页时,只是client端路由变化,数据加载是通过componentDidMount里调用,而不是服务端渲染出来的,但是在本页刷新时,服务端却match了路由的变化,页面是由服务端渲染出来的。请问,这是什么问题呢?
博主,你的登录密码加密方式是什么,最近在github看你的源码,有点不懂。。本人小白一枚,求解答。。
大神,我有个问题,虽然我已经有了一番猜测,但不是特别确定,所以想请教下印证我的猜想。 你是在前后端项目中都设置cookiedomain为.jackhu.top来解决跨域cookie共享的问题吧。这么做的前提是前后端都部署在.jackhu.top域名下。现在我自己部署了一个demo,前端部署在github pages上,域名是jackblog.paidepaiper.top,后端部署在heroku上,用的是heroku给的域名jackblog-express.herokuapp.com。因为前后端主域名不一样,所以我不能设置后端的cookiedomain为.paidepaiper.top,只能设置为空。这样前端获取到的后端cookie就在jackblog-express.herokuapp.com域名下。这样做对local登录没有影响,因为local登录后由后端返回token,前端获取token后设置cookie到自己的域名下。但第三方登录就有问题了,第三方登录认证通过后由后端设置cookie,这样token就会在jackblog-express.herokuapp.com域名下。前端无法获取到jackblog-express.herokuapp.com域名下的token,所以一直显示用户未登录。 你的代码没问题,是我自己在部署的时候碰到的一个奇葩问题,上面这些是我自己分析的,不知道对不对。
写得很好~
test
博主,请教个问题,最好能加下我QQ282798275,或者告诉我你的, 我现在是页面里有很多地方有window对象或者是有window.navigator.userAgent;这样node渲染时运行server.js的时候,serverRender报错,没有window对象,想问下这种怎么解决呢
12112
cool
mj
楼主已超神!能否带大家一起学习?创建一个QQ群吧
test
12423
sad
sdas
大牛啊,我还是云里雾里,不知道服务端渲染要怎么搞
要
dfhdfhdfhd
厉害厉害
正在学习react
我想试试评论,我觉得前面发乱七八糟的也是这么想的
uiui