摘要:使用做同構(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é)合其他輪子例如redux和react-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) => ) } 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了,頁(yè)面刷新的時(shí)候,是后端直出的,點(diǎn)擊跳轉(zhuǎn)的時(shí)候是前端渲染的
做了一個(gè)相對(duì)來(lái)說(shuō)比較完整的案例,使用了react+redux+koa+mongodb開(kāi)發(fā)的,還做了個(gè)爬蟲(chóng),爬取了一本小說(shuō)
https://github.com/frontoldma...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/82758.html
摘要:同構(gòu)的關(guān)鍵要素完善的屬性及生命周期與客戶(hù)端的時(shí)機(jī)是同構(gòu)的關(guān)鍵。的一致性在前后端渲染相同的,將輸出一致的結(jié)構(gòu)。以上便是在同構(gòu)服務(wù)端渲染的提供的基礎(chǔ)條件。可以將封裝至的中,在服務(wù)端上生成隨機(jī)數(shù)并傳入到這個(gè)中,從而保證隨機(jī)數(shù)在客戶(hù)端和服務(wù)端一致。 原文地址 React 的實(shí)踐從去年在 PC QQ家校群開(kāi)始,由于 PC 上的網(wǎng)絡(luò)及環(huán)境都相當(dāng)好,所以在使用時(shí)可謂一帆風(fēng)順,偶爾遇到點(diǎn)小磕絆,也能夠...
摘要:后面會(huì)利用這個(gè)框架來(lái)做實(shí)踐。接下來(lái)就是我們要繼續(xù)探討的同構(gòu)同構(gòu)數(shù)據(jù)處理的探討我們都知道,瀏覽器端獲取數(shù)據(jù)需要發(fā)起請(qǐng)求,實(shí)際上發(fā)起的請(qǐng)求就是對(duì)應(yīng)服務(wù)端一個(gè)路由控制器。是有生命周期的,官方給我們指出的綁定,應(yīng)該在里來(lái)進(jìn)行。 眾所周知,目前的 WEB 應(yīng)用,用戶(hù)體驗(yàn)要求越來(lái)越高,WEB 交互變得越來(lái)越豐富!前端可以做的事越來(lái)越多,去年 Node 引領(lǐng)了前后端分層的浪潮,而 React 的出現(xiàn)...
摘要:從零開(kāi)始搭建同構(gòu)應(yīng)用三配置這篇文章來(lái)講解來(lái)配置,我們先從最簡(jiǎn)單的方法開(kāi)始,用的方式模擬實(shí)現(xiàn)。影響生產(chǎn)環(huán)境下執(zhí)行效率。最后權(quán)衡下,還是決定使用現(xiàn)在多一套編譯配置的方案。新建,寫(xiě)入以下內(nèi)容以為例,注意不能少。 從零開(kāi)始搭建React同構(gòu)應(yīng)用(三):配置SSR 這篇文章來(lái)講解來(lái)配置server side render,我們先從最簡(jiǎn)單的方法開(kāi)始,用cli的方式模擬實(shí)現(xiàn)SSR。 demo在這里 ...
摘要:前戲補(bǔ)上參會(huì)的完整記錄,這個(gè)問(wèn)題從一開(kāi)始我就是準(zhǔn)備自問(wèn)自答的,希望可以通過(guò)這種形式把大會(huì)的干貨分享給更多人。 showImg(http://7xqy7v.com1.z0.glb.clouddn.com/colorful/blog/feday2.png); 前戲 2016/3/21 補(bǔ)上參會(huì)的完整記錄,這個(gè)問(wèn)題從一開(kāi)始我就是準(zhǔn)備自問(wèn)自答的,希望可以通過(guò)這種形式把大會(huì)的干貨分享給更多人。 ...
摘要:從零開(kāi)始搭建同構(gòu)應(yīng)用二項(xiàng)目工程化瀏覽器端在從零開(kāi)始同構(gòu)開(kāi)發(fā)一中我們已經(jīng)能實(shí)現(xiàn)基本的配置和編譯了。接下來(lái)我們需要將編譯工作工程化。配置作用自動(dòng)生成自動(dòng)在引入,。文件內(nèi)容如下同構(gòu)開(kāi)發(fā)配置自動(dòng)刷新這里我們用到的修飾器特性。 從零開(kāi)始搭建React同構(gòu)應(yīng)用(二) 項(xiàng)目工程化(瀏覽器端) 在從零開(kāi)始React同構(gòu)開(kāi)發(fā)(一)中我們已經(jīng)能實(shí)現(xiàn)基本的React配置和編譯了。接下來(lái)我們需要將編譯工作工程...
閱讀 2825·2023-04-25 22:51
閱讀 2084·2021-10-11 10:58
閱讀 3323·2019-08-30 10:49
閱讀 1889·2019-08-29 17:09
閱讀 3147·2019-08-29 10:55
閱讀 854·2019-08-26 10:34
閱讀 3513·2019-08-23 17:54
閱讀 997·2019-08-23 16:06