教您怎么着在React及Redux项目中打开服务端渲染

教您怎么着在React及Redux项目中打开服务端渲染

您大概感兴趣的文章:

  • 详解React+Koa完毕服务端渲染(SS凯雷德)

 

React
提供了三个办法 renderToString 和 renderToStaticMarkup 用来将零件(Virtual
DOM)输出成 HTML 字符串,这是 React
服务器端渲染的根底,它移除了服务器端对于浏览器情状的借助,所以让服务器端渲染产生了一件有吸重力的事情。

劳务器端渲染部分能够一向通过共用客户端 store.dispatch(action) 来归并获取
Store 数据。其余注意 renderFullPage 生成的页面 HTML 在 React 组件 mount
的一些(<div id=”root”>),前后端的 HTML 结构应该是同样的。然后要把
store
的意况树写入八个全局变量(__INITIAL_STATE__),那样客户端先河化
render 的时候能够校验服务器生成的 HTML
结构,并且一路到伊始化状态,然后全部页面被客户端接管。

倘若服务端再次回到以下的数额格式:

有纯粹的 React,也有 Redux 作为气象处理

二 分钟驾驭 Redux 是哪些运营的

关于 Store:

  • 整个应用唯有三个唯1的 Store
  • Store 对应的图景树(State),由调用二个 reducer 函数(root
    reducer)生成
  • 场合树上的每一种字段都得以进一步由分化的 reducer 函数生成
  • Store 包含了几个法子举个例子 dispatchgetState 来处理数据流
  • Store 的地方树只好由 dispatch(action) 来触发改动

Redux 的数据流:

  • action 是3个带有 { type, payload } 的对象
  • reducer 函数通过 store.dispatch(action) 触发
  • reducer 函数接受 (state, action) 四个参数,重回三个新的 state
  • reducer
    函数判定 action.type 然后管理相应的 action.payload 数据来更新景况树

据此对于任何应用来讲,四个 Store 就相应一个 UI
快速照相,服务器端渲染就简化成了在劳务器端起先化 Store,将 Store
传入应用的根组件,针对根组件调用 renderToString 就将全方位应用输出成包含了开首化数据的
HTML。

rootReducer 的 state 参数正是任何 Store
的景色树,状态树下的种种字段对应也会有谈得来的reducer,所以那边引进了
listReducer 和 itemReducer,能够观望那七个 reducer的 state
参数就只是全部场地树上对应的 list 和 item 字段。

明日我们将构建七个选拔 Redux 的差不离的 React
应用程序,实现服务端渲染(SS奥迪Q三)。该示例包蕴异步数据抓取,那使得职分变得更有意思。

使用 redux-saga
管理异步action,使用 express
管理页面渲染

react-redux

接下去贯彻 <List><Item> 组件,然后把 redux 和 react
组件涉及起来,具体细节参见 react-redux

./app.js

import React from 'react';
import { render } from 'react-dom';
import { Router } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import { Provider } from 'react-redux';
import routes from './routes';
import configureStore from './store';

// `__INITIAL_STATE__` 来自服务器端渲染,下一部分细说
const initialState = window.__INITIAL_STATE__;
const store = configureStore(initialState);
const Root = (props) => {
  return (
    <div>
      <Provider store={store}>
        <Router history={createBrowserHistory()}>
          {routes}
        </Router>
      </Provider>
    </div>
  );
}

render(<Root />, document.getElementById('root'));

迄今截止,客户端部分竣事。

  1. 漫天应用只有一个唯壹的 Store
  2. Store 对应的动静树(State),由调用3个 reducer 函数(root
    reducer)生成
  3. 事态树上的每种字段都足以更进一步由差别的 reducer 函数生成
  4. Store 蕴涵了多少个章程例如 dispatch, getState 来管理数据流
  5. Store 的状态树只好由 dispatch(action) 来触发改动

一、大家分明理解请求的页面需求什么的多少。大家获取数据并选择该数据创建Redux 存款和储蓄。然后我们通过提供已成功的 Store
来显现页面,理论上我们得以变成。

使用 webpack
监听编写翻译文件,nodemon
监听服务器文件变动

越来越多参谋

  • Universal (Isomorphic)
  • isomorphic-redux-app

比方有三个很轻巧的采用,只有多少个页面,二个列表页 /list 和一个端详页
/item/:id,点击列表上的条条框框进入详细情形页。

上述就是本文的全体内容,希望对大家的求学抱有协理,也期待大家多多帮忙脚本之家。

二. React + SSR

在讲哪些贯彻从前,先看看最后效果

可以观望页面是直出的,未有中断

澳门新萄京8522 1

 

在React 15中,实现服务端渲染主要靠的是 ReactDOMServer 的 renderToString
和 renderToStatic马克up方法。

let ReactDOMServer = require('react-dom/server');

ReactDOMServer.renderToString(<Message preloadState={preloadState} />)

ReactDOMServer.renderToStaticMarkup(<Message preloadState={preloadState} />)

将零件直接在服务端管理为字符串,我们依照传入的起头状态值,在服务端进行零部件的开始化

下一场在Node意况中回到,比如在Express框架中,重回渲染四个模板文件

      res.render('messageClient/message.html', {
            appHtml: appHtml,
            preloadState: JSON.stringify(preloadState).replace(/</g, '\\u003c')
        });

这里安装了七个变量传递给模板文件

appHtml 即为管理现在的组件字符串

preloadState
为服务器中的起首状态,浏览器的持续职业要依据那个初步状态,所以必要将此变量传递给浏览器初叶化

        <div id="content">
            <|- appHtml |>
        </div>
        <script id="preload-state">
            var PRELOAD_STATE = <|- preloadState |>
        </script>

express框架重返之后即为在浏览器中看到的发端页面

供给留意的是这里的ejs模板实行了自定义分隔符,因为webpack在开始展览编写翻译时,HtmlWebpackPlugin
插件中自带的ejs管理器恐怕会和那一个模板中的ejs变量争执

在express中自定义就可以

// 自定义ejs模板
app.engine('html', ejs.__express);
app.set('view engine', 'html');
ejs.delimiter = '|';

接下去,在浏览器意况的组件中(以下那些文件为国有文件,浏览器端和劳务器端共用),大家要遵照 PRELOAD_STATE 那几个初步状态来初叶化组件

class Message extends Component {
    constructor(props) {
        super(props);

        this.state = {
            msg: []
        };

        // 根据服务器返回的初始状态来初始化
        if (typeof PRELOAD_STATE !== 'undefined') {
            this.state.msgs = PRELOAD_STATE;
            // 清除
            PRELOAD_STATE = null;
            document.getElementById('preload-state').remove();
        }
        // 此文件为公共文件,服务端调用此组件时会传入初始的状态preloadState
        else {
            this.state.msgs = this.props.preloadState;
        }

        console.log(this.state);
    }

    componentDidMount() {
        // 此处无需再发请求,由服务器处理
    }
...

宗旨就是那几个了,那就完了么?

哪有那么快,还得驾驭怎么编写翻译文件(JSX并不是原生协助的),服务端怎么样管理,浏览器端如何管理

接下去看看项目标文书结构

澳门新萄京8522 2 
 澳门新萄京8522 3

把集中力聚集到红框中

直接由webpack.config.js同不常候编写翻译浏览器端和服务端的JS模块

module.exports = [
    clientConfig,
    serverConfig
];

浏览器端的配置利用 src 下的 client目录,编写翻译到 dist 目录中

服务端的布局利用 src 下的 server 目录,编写翻译到 distSSCRUISER目录中。在服务端的配置中就不须要实行css文件提取等毫无干系的拍卖的,关怀编写翻译代码起头化组件状态就可以

除此以外,服务端的布置的ibraryTarget记得使用 ‘commonjs2’,技艺为Node情形所识别

// 文件输出配置
    output: {
        // 输出所在目录
        path: path.resolve(__dirname, '../public/static/distSSR/js/'),
        filename: '[name].js',
        library: 'node',
        libraryTarget: 'commonjs2'
    },

 

client和server只是进口,它们的公共部分在 common 目录中

在client中,直接渲染导入的零部件  

import React, {Component} from 'react';
import {render, hydrate, findDOMNode} from 'react-dom';
import Message from '../common/message';

hydrate(<Message />, document.getElementById('content'));

此地有个 render和hydrate的分裂

在进展了服务端渲染之后,浏览器端使用render的话会依据气象重新伊始化二遍组件,只怕会有震憾的气象;使用
hydrate则只举行零部件事件的初步化,组件不会开头起头化状态

提出选择hydrate方法,在React壹7中 使用了服务端渲染之后,render将不再协助

在 server中,导出这一个组件给 express框架调用

import Message from '../common/message';

let ReactDOMServer = require('react-dom/server');

/**
 * 提供给Node环境调用,传入初始状态
 * @param  {[type]} preloadState [description]
 * @return {[type]}              [description]
 */
export function init(preloadState) {
    return ReactDOMServer.renderToString(<Message preloadState={preloadState} />);
};

内需小心的是,这里不可能向来动用 module.exports = …
因为webpack不帮助ES陆的 import 和那么些混用

在 common中,管理局地浏览器端和劳动器端的差异,再导出

此间的差异主借使变量的接纳难点,在Node中尚无window document navigator
等对象,直接运用会报错。且Node中的严峻形式直接待上访问未定义的变量也会报错

就此须求用typeof
实行变量检查评定,项目中援引的第1方插件组件有使用到了那些浏览器情形目的的,要留心抓实合作,最简便易行的办法是在
componentDidMount 中再引进这些插件组件

其它,webpack的style-loader也借助了这个目的,在服务器配置文件中需求将其移除

 {
            test: /\.css$/,
            loaders: [
                // 'style-loader',
                'happypack/loader?id=css'
            ]
        }

在Express的服务器框架中,messageSS奇骏 路由
渲染页面以前做一些异步操作获取数据

// 编译后的文件路径
let distPath = '../../public/static/distSSR/js';

module.exports = function(req, res, next) {
    // 如果需要id
    let id = 'req.params.id';

    console.log(id);

    getDefaultData(id);

    async function getDefaultData(id) {
        let appHtml = '';
        let preloadState = await getData(id);

        console.log('preloadState', preloadState);

        try {
            // 获取组件的值(字符串)
            appHtml = require(`${distPath}/message`).init(preloadState);
        } catch(e) {
            console.log(e);
            console.trace();
        }

        res.render('messageClient/message.html', {
            appHtml: appHtml,
            preloadState: JSON.stringify(preloadState).replace(/</g, '\\u003c')
        });
    }
};

应用到Node来开启服务,每一回改了服务器文件之后就得重启相比较辛苦

行使 nodemon工具来监听文件修改自动更新服务器,增多配置文件 nodemon.json

{
    "restartable": "rs",
    "ignore": [
        ".git",
        "node_modules/**/node_modules"
    ],
    "verbose": true,
    "execMap": {
        "js": "node --harmony"
    },
    "watch": [
        "server/",
        "public/static/distSSR"
    ],
    "env": {
        "NODE_ENV": "development"
    },
    "ext": "js,json"
}

自然,对于Node遇到不援救JSX那个标题,除了接纳webpack进行编写翻译之外,

还足以在Node中实践 babel-node
来即时地编写翻译文件,可是这种艺术会变成每一次编写翻译特别久(至少比webpack久)

 

在React16 中,ReactDOMServer 除了具有 renderToString 和
renderToStatic马克up那多个章程之外,

还恐怕有 renderToNodeStream  和 renderToStaticNodeStream 多少个流的办法

它们不是回来二个字符串,而是再次来到二个可读流,三个用于发送字节流的目的的Node
Stream类

渲染到流能够减弱你的从头到尾的经过的首先个字节(TTFB)的时刻,在文书档案的下一部分变化以前,将文书档案的启幕至最终发送到浏览器。
当内容从服务器流式传输时,浏览器将上马深入分析HTML文书档案

以下是行使实例,本文不进行

// using Express
import { renderToNodeStream } from "react-dom/server"
import MyPage from "./MyPage"
app.get("/", (req, res) => {
  res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
  res.write("<div id='content'>"); 
  const stream = renderToNodeStream(<MyPage/>);
  stream.pipe(res, { end: false });
  stream.on('end', () => {
    res.write("</div></body></html>");
    res.end();
  });
});

 

那正是在React中实行服务端渲染的流程了,说得有一点点泛泛,照旧要好去看 体系代码 吧

 

  • 上下端能够共享代码
  • 内外端路由得以统1管理

Action

Redux具体贯彻

  1. React
  2. React +
    SSR
  3. React +
    Redux
  4. React + Redux +
    SSR

劳动器端渲染除了要化解对浏览器情形的借助,还要消除四个难题:

从而对于任何应用来讲,多个 Store 就相应五个 UI
快速照相,服务器端渲染就简化成了在服务器端伊始化 Store,将 Store
传入应用的根组件,针对根组件调用 renderToString
就将壹切应用输出成包蕴了初步化数据的 HTML。

接下来将这一个字符串参预到 Express 的响应里面,所以服务端代码为:

服务端渲染(SS卡宴: Server Side
Rendering)在React项目中装有广大的采纳场景

基于React虚拟DOM的特征,在浏览器端和服务端大家得以兑现同构(能够选用同一份代码来完毕多端的效用)

服务端渲染的亮点主要由叁点

  1. 利于SEO

  2. 巩固首屏渲染速度

  3. 同构直出,使用相同份(JS)代码实现,便于开采和保证

react-router

react-router 通过一种申明式的办法非常区别路由调整在页面上显得差别的零件,并且通过
props 将路由新闻传送给组件使用,所以只要路由更换,props
就能够调换,触发组件 re-render。

倘诺有三个很简短的使用,只有八个页面,1个列表页 /list 和两个端详页 /item/:id,点击列表上的条条框框进入详细情形页。

能够这么定义路由,./routes.js

import React from 'react';
import { Route } from 'react-router';
import { List, Item } from './components';

// 无状态(stateless)组件,一个简单的容器,react-router 会根据 route
// 规则匹配到的组件作为 `props.children` 传入
const Container = (props) => {
  return (
    <div>{props.children}</div>
  );
};

// route 规则:
// - `/list` 显示 `List` 组件
// - `/item/:id` 显示 `Item` 组件
const routes = (
  <Route path="/" component={Container} >
    <Route path="list" component={List} />
    <Route path="item/:id" component={Item} />
  </Route>
);

export default routes;

从那边初叶,我们透过那一个特别轻巧的利用来证明达成劳务器端渲染前后端涉及的局地细节难点。

下一场是 ./reducers/item.js,管理获取到的 item 数据

最后3个有效的授命,用于周转大家的http服务器:

花色地址 ,招待围观!

Store

我们用贰个单身的 ./store.js,配置(比如 Apply Middleware)生成 Store

import { createStore } from 'redux';
import rootReducer from './reducers';

// Apply middleware here
// ...

export default function configureStore(initialState) {
  const store = createStore(rootReducer, initialState);
  return store;
}

举例在 /list 页面,对于每三个 item 都会用 <Link> 绑定2个 route
url:/item/:id,并且绑定 onClick 去触发 dispatch(fetchItem(id))
获取数据,展现详细的情况页内容。

大家使用了同1的机件 <App /> 和 store
,差别之处在于它回到的是四个字符串,而不是虚拟DOM。

一、React

贯彻二个最基本的React组件,就会搞掂第三个页面了

/**
 * 消息列表
 */
class Message extends Component {
    constructor(props) {
        super(props);

        this.state = {
            msgs: []
        };
    }

    componentDidMount() {
        setTimeout(() => {
            this.setState({
                msgs: [{
                    id: '1',
                    content: '我是消息我是消息我是消息',
                    time: '2018-11-23 12:33:44',
                    userName: '王羲之'
                }, {
                    id: '2',
                    content: '我是消息我是消息我是消息2',
                    time: '2018-11-23 12:33:45',
                    userName: '王博之'
                }, {
                    id: '3',
                    content: '我是消息我是消息我是消息3',
                    time: '2018-11-23 12:33:44',
                    userName: '王安石'
                }, {
                    id: '4',
                    content: '我是消息我是消息我是消息45',
                    time: '2018-11-23 12:33:45',
                    userName: '王明'
                }]
            });
        }, 1000);
    }

    // 消息已阅
    msgRead(id, e) {
        let msgs = this.state.msgs;
        let itemIndex = msgs.findIndex(item => item.id === id);

        if (itemIndex !== -1) {
            msgs.splice(itemIndex, 1);

            this.setState({
                msgs
            });
        }
    }

    render() {
        return (
            <div>
                <h4>消息列表</h4>
                <div className="msg-items">
                {
                    this.state.msgs.map(item => {
                        return (
                            <div key={item.id} className="msg-item">
                                <p className="msg-item__header">{item.userName} - {item.time}</p>
                                <p className="msg-item__content">{item.content}</p>
                                <a href="javascript:;" className="msg-item__read" onClick={this.msgRead.bind(this, item.id)}>&times;</a>
                            </div>
                        )
                    })
                }
                </div>
            </div>
        )
    }
}

render(<Message />, document.getElementById('content'));

是或不是异常粗略,代码相比较轻松就隐瞒了

来探视页面效果

澳门新萄京8522 4

能够看到页面白屏时间比较长

那边有五个白屏

  1. 加载完JS后才初阶化标题

  2. 举办异步请求数据,再将消息列表渲染

看起来是行车制动器踏板地比较久的,那么使用服务端渲染有啥样意义啊?

 

Reducer

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

./reducers/index.js

import listReducer from './list';
import itemReducer from './item';

export default function rootReducer(state = {}, action) {
  return {
    list: listReducer(state.list, action),
    item: itemReducer(state.item, action)
  };
}

rootReducer 的 state 参数便是全方位 Store
的图景树,状态树下的各种字段对应也能够有谐和的
reducer,所以那边引进了 listReducer 和 itemReducer,能够看看这三个reducer
的 state 参数就只是整套场馆树上对应的 list 和 item 字段。

具体到 ./reducers/list.js

const initialState = [];

export default function listReducer(state = initialState, action) {
  switch(action.type) {
  case 'FETCH_LIST_SUCCESS': return [...action.payload];
  default: return state;
  }
}

list 便是一个带有 items
的简短数组,可能类似这种布局:[{ id: 0, name: 'first item'}, {id: 1, name: 'second item'}],从'FETCH_LIST_SUCCESS' 的 action.payload 获得。

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

const initialState = {};

export default function listReducer(state = initialState, action) {
  switch(action.type) {
  case 'FETCH_ITEM_SUCCESS': return [...action.payload];
  default: return state;
  }
}

Store

最后1个急需优化的地点,正是当已经取到 users 时,必须遏止 fetch 。

五、其他

假定项目选拔了其他服务器语言的,譬如PHP Yii框架 斯马特y
,把服务端渲染整起来只怕没那么轻易

以此是 smarty的模板语法和ejs的不太搞得来

其2是Yii框架的路由和Express的长得不太同样

 

在Nginx中配备Node的反向代理,配置3个 upstream ,然后在server中匹配location ,实行代理配置

upstream connect_node {
    server localhost:54321;
    keepalive 64;
}

...

server
{
    listen 80;
        ...

    location / {
        index index.php index.html index.htm;
    }

        location ~ (home|message)\/\d+$ {
            proxy_pass http://connect_node;
        }

    ...

越来越多配备

 

想得头大,干脆就不想了,有用过Node举行中间转播代理落成SSR的情侣,招待钻探区分享哈~

 

Redux

Redux 提供了壹套类似
Flux 的单向数据流,整个应用唯有限支撑二个Store,以及面向函数式的脾性让它对劳务器端渲染辅助很投机。

您大概感兴趣的稿子:

  • 从零先河最小完结react服务器渲染详解
<html>
 <head>
  <title>App</title>
 </head>
 <body>
  <div id="content"><div data-reactroot=""></div></div>
  <script src="/bundle.js"></script>
 </body>
</html>

本项目包涵多个页面,四种组成,满满的干货,文字恐怕说不清楚,就去看代码吧!

React
生态提供了多数选项方案,这里大家选择 Redux 和 react-router 来做表达。

更加多参考

"scripts": {
 "build": "...",
 "watch": "...",
 "start": "nodemon ./build/server/server.js"
}

 

Action

相应的相应要有多个 action 来获得 list 和 item,触发 reducer 更改Store,这里大家定义 fetchList 和 fetchItem 两个 action。

./actions/index.js

import fetch from 'isomorphic-fetch';

export function fetchList() {
  return (dispatch) => {
    return fetch('/api/list')
        .then(res => res.json())
        .then(json => dispatch({ type: 'FETCH_LIST_SUCCESS', payload: json }));
  }
}

export function fetchItem(id) {
  return (dispatch) => {
    if (!id) return Promise.resolve();
    return fetch(`/api/item/${id}`)
        .then(res => res.json())
        .then(json => dispatch({ type: 'FETCH_ITEM_SUCCESS', payload: json }));
  }
}

isomorphic-fetch 是四个内外端通用的
Ajax 达成,前后端要共享代码这一点很要紧。

其它因为涉及到异步请求,这里的 action 用到了 thunk,约等于函数,redux
通过 thunk-middleware 来管理那类 action,把函数当作普通的 action
dispatch 就好了,比方 dispatch(fetchList())

react-router
通过壹种评释式的方法特别分歧路由决定在页面上显得不相同的零件,并且经过
props 将路由消息传递给组件使用,所以只要路由更动,props
就能够变动,触发组件 re-render。

计算下来有以下几点:

同台看看哪些在实际的类别中贯彻服务端渲染

Server Rendering

接下去的服务器端就比较轻便了,获取数据可以调用 action,routes
在劳务器端的拍卖仿照效法 react-router server
rendering,在服务器端用1个 match 方法将获得的
request url 相配到我们前边定义的 routes,深入分析成和客户端1致的 props
对象传递给组件。

./server.js

import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { RoutingContext, match } from 'react-router';
import { Provider } from 'react-redux';
import routes from './routes';
import configureStore from './store';

const app = express();

function renderFullPage(html, initialState) {
  return `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
    </head>
    <body>
      <div id="root">
        <div>
          ${html}
        </div>
      </div>
      <script>
        window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
      </script>
      <script src="/static/bundle.js"></script>
    </body>
    </html>
  `;
}

app.use((req, res) => {
  match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {
    if (err) {
      res.status(500).end(`Internal Server Error ${err}`);
    } else if (redirectLocation) {
      res.redirect(redirectLocation.pathname + redirectLocation.search);
    } else if (renderProps) {
      const store = configureStore();
      const state = store.getState();

      Promise.all([
        store.dispatch(fetchList()),
        store.dispatch(fetchItem(renderProps.params.id))
      ])
      .then(() => {
        const html = renderToString(
          <Provider store={store}>
            <RoutingContext {...renderProps} />
          </Provider>
        );
        res.end(renderFullPage(html, store.getState()));
      });
    } else {
      res.status(404).end('Not found');
    }
  });
});

劳动器端渲染部分能够一贯通过共用客户端 store.dispatch(action) 来统一获取
Store 数据。别的注意 renderFullPage 生成的页面 HTML 在 React 组件
mount 的壹部分(<div id="root">),前后端的 HTML
结构应该是一样的。然后要把 store 的事态树写入一个全局变量(__INITIAL_STATE__),那样客户端初叶化
render 的时候能够校验服务器生成的 HTML
结构,并且一路到开始化状态,然后一切页面被客户端接管。

./reducers/index.js

二、大家一同重视于运作在客户端上的代码,总计出最终的结果。

四、React + Redux + SSR

能够看到上海体育场所是有一部分眨眼的,因为数量不是一开首就存在

设想参加SSWrangler,先来探望最终页面效果,功能大概,但一直出来了,看起来相当漂亮好呀~

澳门新萄京8522 5

在Redux中参预SSENCORE, 其实跟纯粹的React组件是看似的。

合法给了一个大致的例证

都是在劳务器端获取起先状态后甩卖组件为字符串,分歧主若是React直接运用state,
Redux直接行使store

浏览器中大家得认为三个页面使用同3个store,但在劳动器端不行,大家须求为每三个呼吁成立一个store

 

再来看档期的顺序社团,Redux的SSPAJERO使用到了红框中的文件

澳门新萄京8522 6

劳动端路由homeSS凯雷德与messageSS汉兰达类似,都以回去数据

服务端入口文件 server中的home.js 则是创立二个新的 store,
然后传出ReactDOMServer进行拍卖回来

import {createStore} from 'redux';
import reducers from '../store/reducers';
import App from '../common/home';
import defaultState from '../store/state';

let ReactDOMServer = require('react-dom/server');

export function init(preloadState) {
    // console.log(preloadState);

    let defaultState = Object.assign({}, defaultState, preloadState);

    // 服务器需要为每个请求创建一份store,并将状态初始化为preloadState
    let store = createStore(
        reducers,
        defaultState
    );

    return ReactDOMServer.renderToString(<App store={store} />);
};

坚定不移的,大家须求在common文件中拍卖 Node遇到与浏览器情况的1对差异

比如在 home.jsx 中,加入

// 公共部分,在Node环境中无window document navigator 等对象
if (typeof window === 'undefined') {
    // 设置win变量方便在其他地方判断环境
    global.win = false;
    global.window = {};
    global.document = {};
}

其余组件加载之后也无需发请求获取数据了

/**
     * 初始获取数据之后的某些操作
     * @return {[type]} [description]
     */
    afterInit() {
        console.log('afterInit');
    }

    componentDidMount() {
        console.log('componentDidMount');

        // 初始化发出 INIT_PAGE 操作;
        // 已交由服务器渲染
        // this.props.initPage(() => {
            this.afterInit();
        // });
    }

common中的home.js入口文件用于给组件管理store,
与未用SSXC60的文书差异(js目录下边包车型客车home.js入口)

它须求同一时候为浏览器端和劳务器端服务,所以扩充一些料定,然后导出

if (module.hot) {
    module.hot.accept();
}

import React, {Component} from 'react';
import {render, findDOMNode} from 'react-dom';
import Home from './homeComponent/home.jsx';
import {Provider} from 'react-redux';
import store from '../store';

class App extends Component {
    render() {
        // 如果为Node环境,则取由服务器返回的store值,否则使用 ../store中返回的值
        let st = global.win === false ? this.props.store : store;

        return (
            <Provider store={st}>
                <Home />
            </Provider>
        )
    }
}

export default App;

浏览器端的入口文件 home.js 直接引用渲染就可以

import React, {Component} from 'react';
import {render, hydrate, findDOMNode} from 'react-dom';
import App from '../common/home';

// render(<App />, document.getElementById('content'));
hydrate(<App />, document.getElementById('content'));

 

那正是Redux 加上 SS汉兰达之后的流程了

 

实则还漏了三个Express的server.js服务文件,也就一丝丝代码

澳门新萄京8522 7澳门新萄京8522 8

 1 const express = require('express');
 2 const path = require('path');
 3 const app = express();
 4 const ejs = require('ejs');
 5 
 6 // 常规路由页面
 7 let home = require('./routes/home');
 8 let message = require('./routes/message');
 9 
10 // 用于SSR服务端渲染的页面
11 let homeSSR = require('./routes/homeSSR');
12 let messageSSR = require('./routes/messageSSR');
13 
14 app.use(express.static(path.join(__dirname, '../')));
15 
16 // 自定义ejs模板
17 app.engine('html', ejs.__express);
18 app.set('view engine', 'html');
19 ejs.delimiter = '|';
20 
21 app.set('views', path.join(__dirname, '../views/'));
22 
23 app.get('/home', home);
24 app.get('/message', message);
25 
26 app.get('/ssr/home', homeSSR);
27 app.get('/ssr/message', messageSSR);
28 
29 let port = 12345;
30 
31 app.listen(port, function() {
32     console.log(`Server listening on ${port}`);
33 });

View Code

 

小说说得错错乱乱的,恐怕没那么好精通,照旧去看
品类文件
本身琢磨吧,本人弄下来编写翻译运维看看

 

最终关于页面内链接跳转怎么着管理?

react-router
提供了二个 <Link> 组件用来代替 <a> 标签,它肩负管理浏览器
history,从而不是每一趟点击链接都去央浼服务器,然后能够经过绑定 onClick 事件来作别的管理。

比如在 /list 页面,对于每三个 item 都会用 <Link> 绑定1个 route
url:/item/:id,并且绑定 onClick 去触发 dispatch(fetchItem(id)) 获取数据,展现详情页内容。

至此,客户端部分甘休。

fetchUsers 是二个异步函数,它通过Fetch
API恳请数据。当数码重临时,会派发
users_fetch 动作,从而通过 reducer 重新总计状态,而大家的 <App />
由于总是到 Redux 从而被再一次渲染。

三、React + Redux

澳门新萄京8522,React的中的数据是单向流动的,即父组件状态退换今后,能够通过props将品质传递给子组件,但子组件并不可能直接修改父级的组件。

貌似供给经过调用父组件传来的回调函数来直接地修改父级状态,只怕采纳Context ,使用 事件公布订阅机制等。

引进了Redux实行状态管理之后,就便于一些了。但是会增加代码复杂度,此外要专注的是,React
1陆的新的Context天性貌似给Redux带来了累累撞倒

 

在React项目中利用Redux,当某些管理有比较多逻辑时,服从胖action瘦reducer,相比较通用的提出时将第三逻辑放在action中,在reducer中只实行更新state的等简易的操作

貌似还索要中间件来拍卖异步的动作(action),比较普及的有种种redux-thunk  redux-saga  redux-promise  redux-observable
,它们的周旋统1

此地选拔了 redux-saga,它相比优雅,管理异步也很有优势

 

来探视项目组织

澳门新萄京8522 9

我们将 home组件拆分出多少个子组件便于维护,也造福和Redux实行关联

home.js 为输入文件

利用 Provider 包装组件,传入store状态渲染组件

import React, {Component} from 'react';
import {render, findDOMNode} from 'react-dom';
import {Provider} from 'react-redux';

// 组件入口
import Home from './homeComponent/Home.jsx';
import store from './store';

/**
 * 组装Redux应用
 */
class App extends Component {
    render() {
        return (
            <Provider store={store}>
                <Home />
            </Provider>
        )
    }
}

render(<App />, document.getElementById('content'));

store/index.js 中为状态制造的进度

此间为了便利,就把服务端渲染的部分也位于1块儿了,实际上它们的分裂不是相当大,仅仅是
defaultState起初状态的两样而已

import {createStore, applyMiddleware, compose} from 'redux';
import createSagaMiddleware from 'redux-saga';
// import {thunk} from 'redux-thunk';

import reducers from './reducers';
import wordListSaga from './workListSaga';
import state from './state';

const sagaMiddleware = createSagaMiddleware();

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

let defaultState = state;

// 用于SSR
// 根据服务器返回的初始状态来初始化
if (typeof PRELOAD_STATE !== 'undefined') {
    defaultState = Object.assign({}, defaultState, PRELOAD_STATE);
    // 清除
    PRELOAD_STATE = null;
    document.getElementById('preload-state').remove();
}

let store = createStore(
    reducers,
    defaultState,
    composeEnhancers(
        applyMiddleware(sagaMiddleware)
    ));

sagaMiddleware.run(wordListSaga);

export default store;

大家将有个别action(基本是异步的)交给saga处理

在workListSaga.js中,

澳门新萄京8522 10澳门新萄京8522 11

 1 import {delay} from 'redux-saga';
 2 import {put, fork, takeEvery, takeLatest, call, all, select} from 'redux-saga/effects';
 3 
 4 import * as actionTypes from './types';
 5 
 6 /**
 7  * 获取用户信息
 8  * @yield {[type]} [description]
 9  */
10 function* getUserInfoHandle() {
11     let state = yield select();
12 
13     return yield new Promise((resolve, reject) => {
14         setTimeout(() => {
15             resolve({
16                 sex: 'male',
17                 age: 18,
18                 name: '王羲之',
19                 avatar: '/public/static/imgs/avatar.png'
20             });
21         }, 500);
22     });
23 }
24 
25 /**
26  * 获取工作列表
27  * @yield {[type]} [description]
28  */
29 function* getWorkListHandle() {
30     let state = yield select();
31 
32     return yield new Promise((resolve, reject) => {
33         setTimeout(() => {
34             resolve({
35                 todo: [{
36                     id: '1',
37                     content: '跑步'
38                 }, {
39                     id: '2',
40                     content: '游泳'
41                 }],
42 
43                 done: [{
44                     id: '13',
45                     content: '看书'
46                 }, {
47                     id: '24',
48                     content: '写代码'
49                 }]
50             });
51         }, 1000);
52     });
53 }
54 
55 /**
56  * 获取页面数据,action.payload中如果为回调,可以处理一些异步数据初始化之后的操作
57  * @param {[type]} action        [description]
58  * @yield {[type]} [description]
59  */
60 function* getPageInfoAsync(action) {
61     console.log(action);
62 
63     let userInfo = yield call(getUserInfoHandle);
64 
65     yield put({
66         type: actionTypes.INIT_USER_INFO,
67         payload: userInfo
68     });
69 
70     let workList = yield call(getWorkListHandle);
71 
72     yield put({
73         type: actionTypes.INIT_WORK_LIST,
74         payload: workList
75     });
76 
77     console.log('saga done');
78 
79     typeof action.payload === 'function' && action.payload();
80 }
81 
82 /**
83  * 获取页面数据
84  * @yield {[type]} [description]
85  */
86 export default function* getPageInfo() {
87     yield takeLatest(actionTypes.INIT_PAGE, getPageInfoAsync);
88 }

View Code

监听页面包车型客车开端化action actionTypes.INIT_PAGE
,获取数据之后再接触1个action ,转交给reducer就可以

let userInfo = yield call(getUserInfoHandle);

    yield put({
        type: actionTypes.INIT_USER_INFO,
        payload: userInfo
    });

reducer中做的事根本是立异意况,

import * as actionTypes from './types';
import defaultState from './state';

/**
 * 工作列表处理
 * @param  {[type]} state  [description]
 * @param  {[type]} action [description]
 * @return {[type]}        [description]
 */
function workListReducer(state = defaultState, action) {
    switch (action.type) {
        // 初始化用户信息
        case actionTypes.INIT_USER_INFO:
            // 返回新的状态
            return Object.assign({}, state, {
                userInfo: action.payload
            });

        // 初始化工作列表
        case actionTypes.INIT_WORK_LIST:
            return Object.assign({}, state, {
                todo: action.payload.todo,
                done: action.payload.done
            });

        // 添加任务
        case actionTypes.ADD_WORK_TODO:
            return Object.assign({}, state, {
                todo: action.payload
            });

        // 设置任务完成
        case actionTypes.SET_WORK_DONE:
            return Object.assign({}, state, {
                todo: action.payload.todo,
                done: action.payload.done
            });

        default:
            return state
    }
}

在 action.js中得以定义一些好端端的action,比如

export function addWorkTodo(todoList, content) {
    let id = Math.random();

    let todo = [...todoList, {
        id,
        content
    }];

    return {
        type: actionTypes.ADD_WORK_TODO,
        payload: todo
    }
}

/**
 * 初始化页面信息
 * 此action为redux-saga所监听,将传入saga中执行
 */
export function initPage(cb) {
    console.log(122)
    return {
        type: actionTypes.INIT_PAGE,
        payload: cb
    };
}

回去刚才的 home.js入口文件,在其引进的主模块
home.jsx中,大家需求将redux的事物和这些 home.jsx绑定起来

import {connect} from 'react-redux';

// 子组件
import User from './user';
import WorkList from './workList';

import  {getUrlParam} from '../util/util'
import '../../scss/home.scss';

import {
    initPage
} from '../store/actions.js';

/**
 * 将redux中的state通过props传给react组件
 * @param  {[type]} state [description]
 * @return {[type]}       [description]
 */
function mapStateToProps(state) {
    return {
        userInfo: state.userInfo,
        // 假如父组件Home也需要知悉子组件WorkList的这两个状态,则可以传入这两个属性
        todo: state.todo,
        done: state.done
    };
}

/**
 * 将redux中的dispatch方法通过props传给react组件
 * @param  {[type]} state [description]
 * @return {[type]}       [description]
 */
function mapDispatchToProps(dispatch, ownProps) {
    return {
        // 通过props传入initPage这个dispatch方法
        initPage: (cb) => {
            dispatch(initPage(cb));
        }
    };
}

...

class Home extends Component {
...

export default connect(mapStateToProps, mapDispatchToProps)(Home);

不容置疑,并不是不得不给store绑定3个零件

假如有些组件的情况能够被别的零件共享,恐怕这一个组件需求拜访store,按根组件1层1层通过props传入很麻烦的话,也得以平素给那些组件绑定store

举例说此处的 workList.jsx
也进行了绑定,user.jsx这种只供给出示数据的零件,或然别的一些自治(状态在内处,和表面非亲非故)的组件,则没有供给引入redux的store,也挺困苦的

 

绑定之后,我们必要在 Home组件中调用action,起首获取数据

   /**
     * 初始获取数据之后的某些操作
     * @return {[type]} [description]
     */
    afterInit() {
        console.log('afterInit');
    }

    componentDidMount() {
        console.log('componentDidMount');

        // 初始化发出 INIT_PAGE 操作
        this.props.initPage(() => {
            this.afterInit();
        });
    }

这里有个小技巧,假诺在收获异步数据现在要接着举行其他操作,能够流传
callback ,我们在action的payload中置入了这么些 callback,方便调用

然后Home组件中的已经远非稍微state了,已经交由store管理,通过mapStateToProps传入

故而能够依赖props得到那几个属性

<User {...this.props.userInfo} />

要么调用传入的 reducer ,直接地派发一些action

    // 执行 ADD_WORK_TODO
        this.props.addWorkTodo(this.props.todo, content.trim());

 

页面显示

澳门新萄京8522 12

 

React 生态提供了成都百货上千挑选方案,这里大家选取 Redux 和 react-router
来做表明。

咱俩将用babelify更动到利用browserify和watchify来打包大家的客户端代码。对于我们的劳务器端代码,大家将直接使用babel-cli。

import listReducer from './list';
import itemReducer from './item';

export default function rootReducer(state = {}, action) {
 return {
  list: listReducer(state.list, action),
  item: itemReducer(state.item, action)
 };
}

总结

接下去贯彻 <List>,<Item> 组件,然后把 redux 和 react
组件涉及起来,具体细节参见 react-redux

实现React组件

React 提供了三个主意 renderToString 和 renderToStatic马克up
用来将零件(Virtual DOM)输出成 HTML 字符串,那是 React
服务器端渲染的根底,它移除了劳动器端对于浏览器情状的借助,所以让服务器端渲染产生了壹件有吸重力的事情。

build
src
 ├── client
 │  └── client.js
 └── server
   └── server.js

我们用一个单身的 ./store.js,配置(比方 Apply Middleware)生成 Store

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图