成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

使用React做同構(gòu)應(yīng)用

ymyang / 2545人閱讀

摘要:使用做同構(gòu)應(yīng)用是用于開(kāi)發(fā)數(shù)據(jù)不斷變化的大型應(yīng)用程序的前端框架,結(jié)合其他輪子例如和就可以開(kāi)發(fā)大型的前端應(yīng)用。然后客戶(hù)端檢測(cè)到這些已經(jīng)生成的就不會(huì)重新渲染,直接使用現(xiàn)有的結(jié)構(gòu)。

使用React做同構(gòu)應(yīng)用

React是用于開(kāi)發(fā)數(shù)據(jù)不斷變化的大型應(yīng)用程序的前端view框架,結(jié)合其他輪子例如reduxreact-router就可以開(kāi)發(fā)大型的前端應(yīng)用。

React開(kāi)發(fā)之初就有一個(gè)特別的優(yōu)勢(shì),就是前后端同構(gòu)。

什么是前后端同構(gòu)呢?就是前后端都可以使用同一套代碼生成頁(yè)面,頁(yè)面既可以由前端動(dòng)態(tài)生成,也可以由后端服務(wù)器直接渲染出來(lái)

最簡(jiǎn)單的同構(gòu)應(yīng)用其實(shí)并不復(fù)雜,復(fù)雜的是結(jié)合webpack,router之后的各種復(fù)雜狀態(tài)不容易解決

一個(gè)極簡(jiǎn)單的小例子

html


   
   
     
     React同構(gòu)
     
   
   
     
<%- reactOutput %>

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();
     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);
   });

你看服務(wù)端渲染的原理就是,服務(wù)端調(diào)用react的renderToString方法,在服務(wù)器端生成文本,插入到html文本之中,輸出到瀏覽器客戶(hù)端。然后客戶(hù)端檢測(cè)到這些已經(jīng)生成的dom,就不會(huì)重新渲染,直接使用現(xiàn)有的html結(jié)構(gòu)。

然而現(xiàn)實(shí)并不是這么單純,使用react做前端開(kāi)發(fā)的應(yīng)該不會(huì)不使用webpack,React-router,redux等等一些提高效率,簡(jiǎn)化工作的一些輔助類(lèi)庫(kù)或者框架,這樣的應(yīng)用是不是就不太好做同構(gòu)應(yīng)用了?至少不會(huì)向上文這么簡(jiǎn)單吧?

做當(dāng)然是可以做的,但復(fù)雜度確實(shí)也大了不少

結(jié)合框架的例子 webpack-isomorphic-tools

這個(gè)webpack插件的主要作用有兩點(diǎn)

獲取webpack打包之后的入口文件路徑,包括js,css

把一些特殊的文件例如大圖片、編譯之后css的映射保存下來(lái),以便在服務(wù)器端使用

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 = {
  // 項(xiàng)目根目錄
  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
      }
    }
  }
}

這些文件配置好之后,當(dāng)再運(yùn)行webpack打包命令的時(shí)候就會(huì)生成一個(gè)叫做webpack-assets.json
的文件,這個(gè)文件記錄了剛才生成的如文件的路徑以及css,img映射表

客戶(hù)端的配置到這里就結(jié)束了,來(lái)看下服務(wù)端的配置

服務(wù)端的配置過(guò)程要復(fù)雜一些,因?yàn)樾枰褂玫?b>WebpackIsomorphicToolsPlugin生成的文件,
我們直接使用它對(duì)應(yīng)的服務(wù)端功能就可以了

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初始化之后再啟動(dòng)服務(wù)器

文章開(kāi)頭我們知道react是可以運(yùn)行在服務(wù)端的,其實(shí)不光是react,react-router,redux也都是可以運(yùn)行在服務(wù)器端的
既然前端我們使用了react-router,也就是前端路由,那后端又怎么做處理呢

其實(shí)這些react-router在設(shè)計(jì)的時(shí)候已經(jīng)想到了這些,設(shè)計(jì)了一個(gè)api: match

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

match方法在服務(wù)器端解析了當(dāng)前請(qǐng)求路由,獲取了當(dāng)前路由的對(duì)應(yīng)的請(qǐng)求參數(shù)和對(duì)應(yīng)的組件

知道了這些還不足以做服務(wù)端渲染啊,比如一些頁(yè)面自己作為一個(gè)組件,是需要在客戶(hù)端向服務(wù)
器發(fā)請(qǐng)求,獲取數(shù)據(jù)做渲染的,那我們?cè)趺窗唁秩竞脭?shù)據(jù)的頁(yè)面輸出出來(lái)呢?

那就是需要做一個(gè)約定,就是前端多帶帶放置一個(gè)獲取數(shù)據(jù),渲染頁(yè)面的方法,由后端可以調(diào)用,這樣邏輯就可以保持一份,
保持好的維護(hù)性

但是怎么實(shí)現(xiàn)呢?實(shí)現(xiàn)的過(guò)程比較簡(jiǎn)單,想法比較繞

1.調(diào)用的接口的方式必須前端通用

2.渲染頁(yè)面的方式必須前后端通用

先來(lái)第一個(gè),大家都知道前端調(diào)用接口的方式通過(guò)ajax,那后端怎么使用ajax呢?有一個(gè)庫(kù)封裝了服務(wù)器端的
fetch方法實(shí)現(xiàn),可以用來(lái)做這個(gè)

由于ajax方法需要前后端通用,那就要求這個(gè)方法里面不能夾雜著客戶(hù)端或者服務(wù)端特有的api
調(diào)用。

還有個(gè)很重要的問(wèn)題,就是權(quán)限的問(wèn)題,前端有時(shí)候是需要登錄之后才可以調(diào)用的接口,后端直接調(diào)用
顯然是沒(méi)有cookie的,怎么辦呢?解決辦法就是在用戶(hù)第一個(gè)請(qǐng)求進(jìn)來(lái)之后保存cookie甚至是全部的http
頭信息,然后把這些信息傳進(jìn)fetch方法里面去

通用組件方法必須寫(xiě)成類(lèi)的靜態(tài)成員,否則后端獲取不到,名稱(chēng)也必須統(tǒng)一

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

再看第二個(gè)問(wèn)題,前端渲染頁(yè)面自然就是改變state或者傳入props就可以更新視圖,服務(wù)器端怎么辦呢?
redux是可以解決這個(gè)問(wèn)題的

因?yàn)榉?wù)器端不像前端,需要在初始化之后再去更新視圖,服務(wù)器端只需要先把數(shù)據(jù)準(zhǔn)備好,然后直接一遍生成
視圖就可以了,所以上圖的dispatch方法是由前后端都可以傳入

渲染頁(yè)面的后端方法就比較簡(jiǎn)單了

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) =>
                      
閱讀需要支付1元查看
<