运用React做同构运用

运用React做同构运用

React是用于开辟数据不停变化的大型运用程序的前端view框架,连系其他轮子比方reduxreact-router就能够开辟大型的前端运用。

React开辟之初就有一个迥殊的上风,就是前后端同构。

什么是前后端同构呢?就是前后端都能够运用一致套代码天生页面,页面既能够由前端动态天生,也能够由后端效劳器直接衬着出来

最简朴的同构运用实在并不庞杂,庞杂的是连系webpack,router以后的种种庞杂状况不容易处置惩罚

一个极简朴的小例子

html

<!DOCTYPE html>
   <html>
   <head lang="en">
     <meta charset="UTF-8">
     <title>React同构</title>
     <link href="styles/main.css" rel="stylesheet" />
   </head>
   <body>
     <div id="app">
     <%- reactOutput %>
     </div>
     <script src="bundle.js"></script>
   </body>
   </html>

js

   import path from 'path';
   import Express from 'express';
   import AppRoot from '../app/components/AppRoot'
   import React from 'react';
   import {renderToString} from 'react-dom/server'

   var app = Express();
   var server;
   const PATH_STYLES = path.resolve(__dirname, '../client/styles');
   const PATH_DIST = path.resolve(__dirname, '../../dist');
   app.use('/styles', Express.static(PATH_STYLES));
   app.use(Express.static(PATH_DIST));
   app.get('/', (req, res) => {
     var reactAppContent = renderToString(<AppRoot state={{} }/>);
     console.log(reactAppContent);
     res.render(path.resolve(__dirname, '../client/index.ejs'),
   {reactOutput: reactAppContent});
   });
   server = app.listen(process.env.PORT || 3000, () => {
     var port = server.address().port;
     console.log('Server is listening at %s', port);
   });

你看效劳端衬着的道理就是,效劳端挪用react的renderToString要领,在效劳器端天生文本,插进去到html文本当中,输出到浏览器客户端。然后客户端检测到这些已天生的dom,就不会从新衬着,直接运用现有的html构造。

然则实际并非这么纯真,运用react做前端开辟的应当不会不运用webpack,React-router,redux等等一些进步效力,简化事情的一些辅佐类库或许框架,如许的运用是否是就不太好做同构运用了?最少不会向上文这么简朴吧?

做当然是能够做的,但庞杂度确切也大了不少

连系框架的例子

webpack-isomorphic-tools

这个webpack插件的主要作用有两点

  1. 猎取webpack打包以后的进口文件途径,包含js,css

  2. 把一些特别的文件比方大图片、编译以后css的映照保留下来,以便在效劳器端运用

webpack设置文件

import path from "path";
import webpack from "webpack";
import WebpackIsomorphicToolsPlugin from "webpack-isomorphic-tools/plugin";
import ExtractTextPlugin from "extract-text-webpack-plugin";
import isomorphicToolsConfig from "../isomorphic.tools.config";
import {client} from "../../config";

const webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(isomorphicToolsConfig)

const cssLoader = [
  'css?modules',
  'sourceMap',
  'importLoaders=1',
  'localIdentName=[name]__[local]___[hash:base64:5]'
].join('&')

const cssLoader2 = [
  'css?modules',
  'sourceMap',
  'importLoaders=1',
  'localIdentName=[local]'
].join('&')


const config = {
  // 项目根目录
  context: path.join(__dirname, '../../'),
  devtool: 'cheap-module-eval-source-map',
  entry: [
    `webpack-hot-middleware/client?reload=true&path=http://${client.host}:${client.port}/__webpack_hmr`,
    './client/index.js'
  ],
  output: {
    path: path.join(__dirname, '../../build'),
    filename: 'index.js',
    publicPath: '/build/',
    chunkFilename: '[name]-[chunkhash:8].js'
  },
  resolve: {
    extensions: ['', '.js', '.jsx', '.json']
  },
  module: {
    preLoaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'eslint-loader'
      }
    ],
    loaders: [
      {
        test: /\.jsx?$/,
        loader: 'babel',
        exclude: [/node_modules/]
      },
      {
        test: webpackIsomorphicToolsPlugin.regular_expression('less'),
        loader: ExtractTextPlugin.extract('style', `${cssLoader}!less`)
      },
      {
        test: webpackIsomorphicToolsPlugin.regular_expression('css'),
        exclude: [/node_modules/],
        loader: ExtractTextPlugin.extract('style', `${cssLoader}`)
      },
      {
        test: webpackIsomorphicToolsPlugin.regular_expression('css'),
        include: [/node_modules/],
        loader: ExtractTextPlugin.extract('style', `${cssLoader2}`)
      },
      {
        test: webpackIsomorphicToolsPlugin.regular_expression('images'),
        loader: 'url?limit=10000'
      }
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new ExtractTextPlugin('[name].css', {
      allChunks: true
    }),
    webpackIsomorphicToolsPlugin
  ]
}

export default config

webpack-isomorphic-tools 设置文件

import WebpackIsomorphicToolsPlugin from 'webpack-isomorphic-tools/plugin'

export default {
  assets: {
    images: {
      extensions: ['png', 'jpg', 'jpeg', 'gif', 'ico', 'svg']
    },
    css: {
      extensions: ['css'],
      filter(module, regex, options, log) {
        if (options.development) {
          return WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log)
        }
        return regex.test(module.name)
      },
      path(module, options, log) {
        if (options.development) {
          return WebpackIsomorphicToolsPlugin.style_loader_path_extractor(module, options, log);
        }
        return module.name
      },
      parser(module, options, log) {
        if (options.development) {
          return WebpackIsomorphicToolsPlugin.css_modules_loader_parser(module, options, log);
        }
        return module.source
      }
    },
    less: {
      extensions: ['less'],
      filter: function(module, regex, options, log)
      {
        if (options.development)
        {
          return webpack_isomorphic_tools_plugin.style_loader_filter(module, regex, options, log)
        }

        return regex.test(module.name)
      },

      path: function(module, options, log)
      {
        if (options.development)
        {
          return WebpackIsomorphicToolsPlugin.style_loader_path_extractor(module, options, log);
        }

        return module.name
      },

      parser: function(module, options, log)
      {
        if (options.development)
        {
          return WebpackIsomorphicToolsPlugin.css_modules_loader_parser(module, options, log);
        }

        return module.source
      }
    }
  }
}

这些文件设置好以后,当再运行webpack打包敕令的时刻就会天生一个叫做webpack-assets.json
的文件,这个文件记录了适才天生的如文件的途径以及css,img映照表

客户端的设置到这里就完毕了,来看下效劳端的设置

效劳端的设置历程要庞杂一些,因为须要运用到WebpackIsomorphicToolsPlugin天生的文件,
我们直接运用它对应的效劳端功用就能够了

import path from 'path'
import WebpackIsomorphicTools from 'webpack-isomorphic-tools'
import co from 'co'
import startDB from '../../server/model/'

import isomorphicToolsConfig from '../isomorphic.tools.config'

const startServer = require('./server')
var basePath = path.join(__dirname, '../../')

global.webpackIsomorphicTools = new WebpackIsomorphicTools(isomorphicToolsConfig)
  // .development(true)
  .server(basePath, () => {
    const startServer = require('./server')
    co(function *() {
      yield startDB
      yield startServer
    })
  })

肯定要在WebpackIsomorphicTools初始化以后再启动效劳器

文章开首我们晓得react是能够运行在效劳端的,实在不光是react,react-router,redux也都是能够运行在效劳器端的
既然前端我们运用了react-router,也就是前端路由,那后端又怎样做处置惩罚呢

实在这些react-router在设想的时刻已想到了这些,设想了一个api: match

match({routes, location}, (error, redirectLocation, renderProps) => {
    matchResult = {
      error,
      redirectLocation,
      renderProps
    }
  })

match要领在效劳器端剖析了当前请求路由,猎取了当前路由的对应的请求参数和对应的组件

晓得了这些还不足以做效劳端衬着啊,比方一些页面本身作为一个组件,是须要在客户端向效劳
器发请求,猎取数据做衬着的,那我们怎样把衬着好数据的页面输出出来呢?

那就是须要做一个商定,就是前端零丁安排一个猎取数据,衬着页面的要领,由后端能够挪用,如许逻辑就能够坚持一份,
坚持好的保护性

然则怎样完成呢?完成的历程比较简朴,主意比较绕

1.挪用的接口的体式格局必需前端通用

2.衬着页面的体式格局必需前后端通用

先来第一个,人人都晓得前端挪用接口的体式格局经由过程ajax,那后端怎样运用ajax呢?有一个库封装了效劳器端的
fetch要领完成,能够用来做这个

因为ajax要领须要前后端通用,那就请求这个要领内里不能夹杂着客户端或许效劳端特有的api
挪用。

另有个很主要的题目,就是权限的题目,前端有时刻是须要登录以后才能够挪用的接口,后端直接挪用
显然是没有cookie的,怎样办呢?处置惩罚办法就是在用户第一个请求进来以后保留cookie以至是悉数的http
头信息,然后把这些信息传进fetch要领内里去

通用组件要领必需写成类的静态成员,不然后端猎取不到,称号也必需一致

static getInitData (params = {}, cookie, dispatch, query = {}) {
    return getList({
      ...params,
      ...query
    }, cookie)
      .then(data => dispatch({
        type: constants.article.GET_LIST_VIEW_SUCCESS,
        data: data
      }))
  }

再看第二个题目,前端衬着页面天然就是转变state或许传入props就能够更新视图,效劳器端怎样办呢?
redux是能够处置惩罚这个题目的

因为效劳器端不像前端,须要在初始化以后再去更新视图,效劳器端只须要先把数据准备好,然后直接一遍天生
视图就能够了,所以上图的dispatch要领是由前后端都能够传入

衬着页面的后端要领就比较简朴了

import React, { Component, PropTypes } from 'react'
import { renderToString } from 'react-dom/server'
import {client} from '../../config'

export default class Html extends Component {

  get scripts () {
    const { javascript } = this.props.assets

    return Object.keys(javascript).map((script, i) =>
      <script src={`http://${client.host}:${client.port}` + javascript[script]} key={i} />
    )
  }

  get styles () {
    const { assets } = this.props
    const { styles, assets: _assets } = assets
    const stylesArray = Object.keys(styles)

    // styles (will be present only in production with webpack extract text plugin)
    if (stylesArray.length !== 0) {
      return stylesArray.map((style, i) =>
        <link href={`http://${client.host}:${client.port}` + assets.styles[style]} key={i} rel="stylesheet" type="text/css" />
      )
    }

    // (will be present only in development mode)
    // It's not mandatory but recommended to speed up loading of styles
    // (resolves the initial style flash (flicker) on page load in development mode)
    // const scssPaths = Object.keys(_assets).filter(asset => asset.includes('.css'))
    // return scssPaths.map((style, i) =>
    //   <style dangerouslySetInnerHTML={{ __html: _assets[style]._style }} key={i} />
    // )
  }

  render () {
    const { component, store } = this.props

    return (
      <html>
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black" />
        <title>前端博客</title>
        <link rel="icon" href="/favicon.ico" />
        {this.styles}
      </head>

      <body>
      <div id="root" dangerouslySetInnerHTML={{ __html: renderToString(component) }} />
      <script dangerouslySetInnerHTML={{ __html: `window.__INITIAL_STATE__=${JSON.stringify(store.getState())};` }} />
      {this.scripts}
      </body>
      </html>
    )
  }
}

ok了,页面革新的时刻,是后端直出的,点击跳转的时刻是前端衬着的

做了一个相对来说比较完全的案例,运用了react+redux+koa+mongodb开辟的,还做了个爬虫,爬取了一本小说

https://github.com/frontoldma…

    原文作者:frontoldman
    原文地址: https://segmentfault.com/a/1190000009235324
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞