摘要:使用做同構(gòu)應(yīng)用是用于開發(fā)數(shù)據(jù)不斷變化的大型應(yīng)用程序的前端框架,結(jié)合其他輪子例如和就可以開發(fā)大型的前端應(yīng)用。然后客戶端檢測到這些已經(jīng)生成的就不會重新渲染,直接使用現(xiàn)有的結(jié)構(gòu)。
使用React做同構(gòu)應(yīng)用
React是用于開發(fā)數(shù)據(jù)不斷變化的大型應(yīng)用程序的前端view框架,結(jié)合其他輪子例如redux和react-router就可以開發(fā)大型的前端應(yīng)用。
React開發(fā)之初就有一個(gè)特別的優(yōu)勢,就是前后端同構(gòu)。
什么是前后端同構(gòu)呢?就是前后端都可以使用同一套代碼生成頁面,頁面既可以由前端動態(tài)生成,也可以由后端服務(wù)器直接渲染出來
最簡單的同構(gòu)應(yīng)用其實(shí)并不復(fù)雜,復(fù)雜的是結(jié)合webpack,router之后的各種復(fù)雜狀態(tài)不容易解決
一個(gè)極簡單的小例子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文本之中,輸出到瀏覽器客戶端。然后客戶端檢測到這些已經(jīng)生成的dom,就不會重新渲染,直接使用現(xiàn)有的html結(jié)構(gòu)。
然而現(xiàn)實(shí)并不是這么單純,使用react做前端開發(fā)的應(yīng)該不會不使用webpack,React-router,redux等等一些提高效率,簡化工作的一些輔助類庫或者框架,這樣的應(yīng)用是不是就不太好做同構(gòu)應(yīng)用了?至少不會向上文這么簡單吧?
做當(dāng)然是可以做的,但復(fù)雜度確實(shí)也大了不少
結(jié)合框架的例子 webpack-isomorphic-tools這個(gè)webpack插件的主要作用有兩點(diǎn)
獲取webpack打包之后的入口文件路徑,包括js,css
把一些特殊的文件例如大圖片、編譯之后css的映射保存下來,以便在服務(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í)候就會生成一個(gè)叫做webpack-assets.json
的文件,這個(gè)文件記錄了剛才生成的如文件的路徑以及css,img映射表
客戶端的配置到這里就結(jié)束了,來看下服務(wù)端的配置
服務(wù)端的配置過程要復(fù)雜一些,因?yàn)樾枰褂玫?b>WebpackIsomorphicToolsPlugin生成的文件,
我們直接使用它對應(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初始化之后再啟動服務(wù)器
文章開頭我們知道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)前請求路由,獲取了當(dāng)前路由的對應(yīng)的請求參數(shù)和對應(yīng)的組件
知道了這些還不足以做服務(wù)端渲染啊,比如一些頁面自己作為一個(gè)組件,是需要在客戶端向服務(wù)
器發(fā)請求,獲取數(shù)據(jù)做渲染的,那我們怎么把渲染好數(shù)據(jù)的頁面輸出出來呢?
那就是需要做一個(gè)約定,就是前端多帶帶放置一個(gè)獲取數(shù)據(jù),渲染頁面的方法,由后端可以調(diào)用,這樣邏輯就可以保持一份,
保持好的維護(hù)性
但是怎么實(shí)現(xiàn)呢?實(shí)現(xiàn)的過程比較簡單,想法比較繞
1.調(diào)用的接口的方式必須前端通用
2.渲染頁面的方式必須前后端通用
先來第一個(gè),大家都知道前端調(diào)用接口的方式通過ajax,那后端怎么使用ajax呢?有一個(gè)庫封裝了服務(wù)器端的
fetch方法實(shí)現(xiàn),可以用來做這個(gè)
由于ajax方法需要前后端通用,那就要求這個(gè)方法里面不能夾雜著客戶端或者服務(wù)端特有的api
調(diào)用。
還有個(gè)很重要的問題,就是權(quán)限的問題,前端有時(shí)候是需要登錄之后才可以調(diào)用的接口,后端直接調(diào)用
顯然是沒有cookie的,怎么辦呢?解決辦法就是在用戶第一個(gè)請求進(jìn)來之后保存cookie甚至是全部的http
頭信息,然后把這些信息傳進(jìn)fetch方法里面去
通用組件方法必須寫成類的靜態(tài)成員,否則后端獲取不到,名稱也必須統(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è)問題,前端渲染頁面自然就是改變state或者傳入props就可以更新視圖,服務(wù)器端怎么辦呢?
redux是可以解決這個(gè)問題的
因?yàn)榉?wù)器端不像前端,需要在初始化之后再去更新視圖,服務(wù)器端只需要先把數(shù)據(jù)準(zhǔn)備好,然后直接一遍生成
視圖就可以了,所以上圖的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了,頁面刷新的時(shí)候,是后端直出的,點(diǎn)擊跳轉(zhuǎn)的時(shí)候是前端渲染的
做了一個(gè)相對來說比較完整的案例,使用了react+redux+koa+mongodb開發(fā)的,還做了個(gè)爬蟲,爬取了一本小說
https://github.com/frontoldma...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/82758.html
摘要:同構(gòu)的關(guān)鍵要素完善的屬性及生命周期與客戶端的時(shí)機(jī)是同構(gòu)的關(guān)鍵。的一致性在前后端渲染相同的,將輸出一致的結(jié)構(gòu)。以上便是在同構(gòu)服務(wù)端渲染的提供的基礎(chǔ)條件??梢詫⒎庋b至的中,在服務(wù)端上生成隨機(jī)數(shù)并傳入到這個(gè)中,從而保證隨機(jī)數(shù)在客戶端和服務(wù)端一致。 原文地址 React 的實(shí)踐從去年在 PC QQ家校群開始,由于 PC 上的網(wǎng)絡(luò)及環(huán)境都相當(dāng)好,所以在使用時(shí)可謂一帆風(fēng)順,偶爾遇到點(diǎn)小磕絆,也能夠...
摘要:后面會利用這個(gè)框架來做實(shí)踐。接下來就是我們要繼續(xù)探討的同構(gòu)同構(gòu)數(shù)據(jù)處理的探討我們都知道,瀏覽器端獲取數(shù)據(jù)需要發(fā)起請求,實(shí)際上發(fā)起的請求就是對應(yīng)服務(wù)端一個(gè)路由控制器。是有生命周期的,官方給我們指出的綁定,應(yīng)該在里來進(jìn)行。 眾所周知,目前的 WEB 應(yīng)用,用戶體驗(yàn)要求越來越高,WEB 交互變得越來越豐富!前端可以做的事越來越多,去年 Node 引領(lǐng)了前后端分層的浪潮,而 React 的出現(xiàn)...
摘要:從零開始搭建同構(gòu)應(yīng)用三配置這篇文章來講解來配置,我們先從最簡單的方法開始,用的方式模擬實(shí)現(xiàn)。影響生產(chǎn)環(huán)境下執(zhí)行效率。最后權(quán)衡下,還是決定使用現(xiàn)在多一套編譯配置的方案。新建,寫入以下內(nèi)容以為例,注意不能少。 從零開始搭建React同構(gòu)應(yīng)用(三):配置SSR 這篇文章來講解來配置server side render,我們先從最簡單的方法開始,用cli的方式模擬實(shí)現(xiàn)SSR。 demo在這里 ...
摘要:前戲補(bǔ)上參會的完整記錄,這個(gè)問題從一開始我就是準(zhǔn)備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 showImg(http://7xqy7v.com1.z0.glb.clouddn.com/colorful/blog/feday2.png); 前戲 2016/3/21 補(bǔ)上參會的完整記錄,這個(gè)問題從一開始我就是準(zhǔn)備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 ...
摘要:從零開始搭建同構(gòu)應(yīng)用二項(xiàng)目工程化瀏覽器端在從零開始同構(gòu)開發(fā)一中我們已經(jīng)能實(shí)現(xiàn)基本的配置和編譯了。接下來我們需要將編譯工作工程化。配置作用自動生成自動在引入,。文件內(nèi)容如下同構(gòu)開發(fā)配置自動刷新這里我們用到的修飾器特性。 從零開始搭建React同構(gòu)應(yīng)用(二) 項(xiàng)目工程化(瀏覽器端) 在從零開始React同構(gòu)開發(fā)(一)中我們已經(jīng)能實(shí)現(xiàn)基本的React配置和編譯了。接下來我們需要將編譯工作工程...
閱讀 2880·2023-04-25 22:51
閱讀 2261·2021-10-11 10:58
閱讀 3386·2019-08-30 10:49
閱讀 1946·2019-08-29 17:09
閱讀 3192·2019-08-29 10:55
閱讀 905·2019-08-26 10:34
閱讀 3628·2019-08-23 17:54
閱讀 1048·2019-08-23 16:06