摘要:使用做同构应用是用于开发数据不断变化的大型应用程序的前端框架,结合其他轮子例如和就可以开发大型的前端应用。然后客户端检测到这些已经生成的就不会重新渲染,直接使用现有的结构。
使用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...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/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配置和编译了。接下来我们需要将编译工作工程...
阅读 2765·2023-04-25 22:51
阅读 1972·2021-10-11 10:58
阅读 3287·2019-08-30 10:49
阅读 1803·2019-08-29 17:09
阅读 3115·2019-08-29 10:55
阅读 806·2019-08-26 10:34
阅读 3419·2019-08-23 17:54
阅读 963·2019-08-23 16:06