摘要:使用做同構應用是用于開發數據不斷變化的大型應用程序的前端框架,結合其他輪子例如和就可以開發大型的前端應用。然后客戶端檢測到這些已經生成的就不會重新渲染,直接使用現有的結構。
使用React做同構應用
React是用于開發數據不斷變化的大型應用程序的前端view框架,結合其他輪子例如redux和react-router就可以開發大型的前端應用。
React開發之初就有一個特別的優勢,就是前后端同構。
什么是前后端同構呢?就是前后端都可以使用同一套代碼生成頁面,頁面既可以由前端動態生成,也可以由后端服務器直接渲染出來
最簡單的同構應用其實并不復雜,復雜的是結合webpack,router之后的各種復雜狀態不容易解決
一個極簡單的小例子html
React同構 <%- 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); });
你看服務端渲染的原理就是,服務端調用react的renderToString方法,在服務器端生成文本,插入到html文本之中,輸出到瀏覽器客戶端。然后客戶端檢測到這些已經生成的dom,就不會重新渲染,直接使用現有的html結構。
然而現實并不是這么單純,使用react做前端開發的應該不會不使用webpack,React-router,redux等等一些提高效率,簡化工作的一些輔助類庫或者框架,這樣的應用是不是就不太好做同構應用了?至少不會向上文這么簡單吧?
做當然是可以做的,但復雜度確實也大了不少
結合框架的例子 webpack-isomorphic-tools這個webpack插件的主要作用有兩點
獲取webpack打包之后的入口文件路徑,包括js,css
把一些特殊的文件例如大圖片、編譯之后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) => ) } 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) => ) } // (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) => // // ) } render () { const { component, store } = this.props return (前端博客 {this.styles} {this.scripts} ) } }
ok了,頁面刷新的時候,是后端直出的,點擊跳轉的時候是前端渲染的
做了一個相對來說比較完整的案例,使用了react+redux+koa+mongodb開發的,還做了個爬蟲,爬取了一本小說
https://github.com/frontoldma...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82758.html
摘要:同構的關鍵要素完善的屬性及生命周期與客戶端的時機是同構的關鍵。的一致性在前后端渲染相同的,將輸出一致的結構。以上便是在同構服務端渲染的提供的基礎條件。可以將封裝至的中,在服務端上生成隨機數并傳入到這個中,從而保證隨機數在客戶端和服務端一致。 原文地址 React 的實踐從去年在 PC QQ家校群開始,由于 PC 上的網絡及環境都相當好,所以在使用時可謂一帆風順,偶爾遇到點小磕絆,也能夠...
摘要:后面會利用這個框架來做實踐。接下來就是我們要繼續探討的同構同構數據處理的探討我們都知道,瀏覽器端獲取數據需要發起請求,實際上發起的請求就是對應服務端一個路由控制器。是有生命周期的,官方給我們指出的綁定,應該在里來進行。 眾所周知,目前的 WEB 應用,用戶體驗要求越來越高,WEB 交互變得越來越豐富!前端可以做的事越來越多,去年 Node 引領了前后端分層的浪潮,而 React 的出現...
摘要:從零開始搭建同構應用三配置這篇文章來講解來配置,我們先從最簡單的方法開始,用的方式模擬實現。影響生產環境下執行效率。最后權衡下,還是決定使用現在多一套編譯配置的方案。新建,寫入以下內容以為例,注意不能少。 從零開始搭建React同構應用(三):配置SSR 這篇文章來講解來配置server side render,我們先從最簡單的方法開始,用cli的方式模擬實現SSR。 demo在這里 ...
摘要:前戲補上參會的完整記錄,這個問題從一開始我就是準備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 showImg(http://7xqy7v.com1.z0.glb.clouddn.com/colorful/blog/feday2.png); 前戲 2016/3/21 補上參會的完整記錄,這個問題從一開始我就是準備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 ...
摘要:從零開始搭建同構應用二項目工程化瀏覽器端在從零開始同構開發一中我們已經能實現基本的配置和編譯了。接下來我們需要將編譯工作工程化。配置作用自動生成自動在引入,。文件內容如下同構開發配置自動刷新這里我們用到的修飾器特性。 從零開始搭建React同構應用(二) 項目工程化(瀏覽器端) 在從零開始React同構開發(一)中我們已經能實現基本的React配置和編譯了。接下來我們需要將編譯工作工程...
閱讀 2801·2023-04-25 22:51
閱讀 2026·2021-10-11 10:58
閱讀 3308·2019-08-30 10:49
閱讀 1870·2019-08-29 17:09
閱讀 3136·2019-08-29 10:55
閱讀 839·2019-08-26 10:34
閱讀 3467·2019-08-23 17:54
閱讀 980·2019-08-23 16:06