玩转 React 服务器端渲染

【编者按】React 生态提供了很多选择方案,这里我们选用 Redux 和 react-router 来做说

React 提供了两个方法renderToString和renderToStaticMarkup用来将组件(Virtual DOM)输出成 HTML 字符串,这是 React 服务器端渲染的基础,它移除了服务器端对于浏览器环境的依赖,所以让服务器端渲染变成了一件有吸引力的事情。

服务器端渲染除了要解决对浏览器环境的依赖,还要解决两个问题:

前后端可以共享代码

前后端路由可以统一处理

React 生态提供了很多选择方案,这里我们选用 Redux 和 react-router 来做说明。

Redux

Redux 提供了一套类似 Flux 的单向数据流,整个应用只维护一个 Store,以及面向函数式的特性让它对服务器端渲染支持很友好。

2 分钟了解 Redux 是如何运作的

关于 Store:

整个应用只有一个唯一的 Store

Store 对应的状态树(State),由调用一个 reducer 函数(root reducer)生成

状态树上的每个字段都可以进一步由不同的 reducer 函数生成

Store 包含了几个方法比如dispatch,getState来处理数据流

Store 的状态树只能由dispatch(action)来触发更改

Redux 的数据流:

action 是一个包含{ type, payload }的对象

reducer 函数通过store.dispatch(action)触发

reducer 函数接受(state, action)两个参数,返回一个新的 state

reducer 函数判断action.type然后处理对应的action.payload数据来更新状态树

所以对于整个应用来说,一个 Store 就对应一个 UI 快照,服务器端渲染就简化成了在服务器端初始化 Store,将 Store 传入应用的根组件,针对根组件调用renderToString就将整个应用输出成包含了初始化数据的 HTML。

react-router

react-router 通过一种声明式的方式匹配不同路由决定在页面上展示不同的组件,并且通过 props 将路由信息传递给组件使用,所以只要路由变更,props 就会变化,触发组件 re-render。

假设有一个很简单的应用,只有两个页面,一个列表页/list和一个详情页/item/:id,点击列表上的条目进入详情页。

可以这样定义路由,./routes.js

从这里开始,我们通过这个非常简单的应用来解释实现服务器端渲染前后端涉及的一些细节问题。

Reducer

Store 是由 reducer 产生的,所以 reducer 实际上反映了 Store 的状态树结构

./reducers/index.js

rootReducer的state参数就是整个 Store 的状态树,状态树下的每个字段对应也可以有自己的

reducer,所以这里引入了listReducer和itemReducer,可以看到这两个 reducer

的 state 参数就只是整个状态树上对应的list和item字段。

具体到./reducers/list.js

然后是 ./reducers/item.js,处理获取到的 item 数据

Action

对应的应该要有两个 action 来获取 list 和 item,触发 reducer 更改 Store,这里我们定义fetchList和fetchItem两个 action。

./actions/index.js

isomorphic-fetch 是一个前后端通用的 Ajax 实现,前后端要共享代码这点很重要。

另外因为涉及到异步请求,这里的 action 用到了 thunk,也就是函数,redux 通过thunk-middleware来处理这类 action,把函数当作普通的 action dispatch 就好了,比如dispatch(fetchList())

Store

我们用一个独立的./store.js,配置(比如 Apply Middleware)生成 Store

react-redux

接下来实现

,组件,然后把 redux 和 react 组件关联起来,具体细节参见 react-redux

./app.js

至此,客户端部分结束。

Server Rendering

接下来的服务器端就比较简单了,获取数据可以调用 action,routes 在服务器端的处理参考 react-router server rendering,在服务器端用一个match方法将拿到的 request url 匹配到我们之前定义的 routes,解析成和客户端一致的 props 对象传递给组件。

./server.js

服务器端渲染部分可以直接通过共用客户端store.dispatch(action)来统一获取 Store 数据。另外注意renderFullPage生成的页面 HTML 在 React 组件 mount 的部分(

),前后端的 HTML 结构应该是一致的。然后要把store的状态树写入一个全局变量(__INITIAL_STATE__),这样客户端初始化 render 的时候能够校验服务器生成的 HTML 结构,并且同步到初始化状态,然后整个页面被客户端接管。

最后关于页面内链接跳转如何处理?

比如在 /list 页面,对于每一个 item 都会用

绑定一个route url:/item/:id,并且绑定onClick去触发dispatch(fetchItem(id))获取数据,显示详情页内容。

更多参考

Universal (Isomorphic)

isomorphic-redux-app

-EOF-

本文来自企鹅号 - 交互设计前端开发与后端程序设计媒体

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏从零开始学 Web 前端

从零开始学 Web 之 Ajax(七)跨域

跨域这个概念来自一个叫 “同源策略” 的东西。同源策略是浏览器上为了安全考虑实施的非常重要的安全机制。

1374
来自专栏Django中文社区

真正的 Django 博客首页视图

在此之前我们已经编写了 Blog 的首页视图,并且配置了 URL 和模板,让 Django 能够正确地处理 HTTP 请求并返回合适的 HTTP 响应。不过我们...

3598
来自专栏逍遥剑客的游戏开发

Nebula3 Render Application Wizard

1643
来自专栏柠檬先生

Sass 基础(八)

@import       Sass 支持所有css 的@规则,以及一些Sass 专属的规则,也被称为“指令(directive)”.这些规则在Sass 中具...

1989
来自专栏一枝花算不算浪漫

[jQuery学习系列五 ]5-Jquery学习五-表单验证

36212
来自专栏小白鼠

Ionic3 自定义指令

组件的概念比较大,本文讲解的是属性指令和结构指令的创建和使用,Angular官方文档

1303
来自专栏磨磨谈

Luminous监控界面中文语言包

之前有各种ceph的管理平台,在部署方面大部分都比较麻烦,现在在luminous版本当中有一个原生的dashboard,虽然目前这个只能看,但是从界面上面,从接...

1622
来自专栏阮一峰的网络日志

Vim 配置入门

Vim 的配置不太容易,它有自己的语法,许许多多的命令。我总是记不清楚,所以就整理了下面这篇文章,列出主要配置项的含义。

1591
来自专栏finleyMa

使用苹果系统的开发人员必装的iterm和zsh

Zsh支持目录的快速跳转,我们可以使用 d 这个命令,列出最近访问过的各个目录,然后选择目录前面的数字进行快速跳转:

1212
来自专栏技术博文

PHP抓取采集类snoopy

snoopy是一个php类,用来模仿web浏览器的功能,它能完成获取网页内容和发送表单的任务。官方网站 http://snoopy.sourceforge.ne...

7488

扫码关注云+社区

领取腾讯云代金券