具有 REST API 架构的单页应用程序

Posted

技术标签:

【中文标题】具有 REST API 架构的单页应用程序【英文标题】:Single page app with REST API architecture 【发布时间】:2016-02-09 10:44:52 【问题描述】:

我在Node.js 中构建了中型Web 应用程序。起初,我并没有计划它会长到中等大小,也不知道它是否会被使用。现在,随着客户的用户开始使用它,需要另外两个功能:

移动应用程序 单页应用

由于直到现在我使用服务器端模板(即我所有的API 路由都以html 响应),我需要做出巨大的改变以支持APIJSON 响应仅用于移动应用程序,所以我决定重构整个应用程序以同时支持这两件事。

阅读了一些在线资源(即Single Page App Book)并比较了可用的javascript 框架(Angular vs Backbone vs React vs Ember),我得出了以下结论。我的问题是,我错过了什么吗?所以,这就是我打算如何扩展我的网络应用程序:

我将在React 中重写我所有的 UI 组件 所有当前的API 路由仍会以HTML 响应,并且HTML 将使用服务器端server-rendered 响应server-rendered,但这些React UI 组件也将包含在浏览器端,这将支持单页应用程序功能。 我会写专门的RESTAPI,可能是基于JSON API服务器与单页应用程序和移动Web应用程序通信的标准。 API 路由(API 响应页面 - HTML 和数据 - JSON)都将使用 Express 路由器完成,该路由器将执行 控制器(服务器组件) 用于封装与数据访问层(服务器组件)的数据组合操作。 数据访问层基本上由Mongoose 模型组成。

由于实施和重构它需要更长的时间,我想确保我走在正确的轨道上。我在这里错过了什么吗?

【问题讨论】:

【参考方案1】:

如果您包含通量模式,它将使您的应用更易于构建和维护。我建议你看看一些入门项目,然后选择一个符合你自己风格的项目。这是来自https://github.com/calitek/ReactPatternsReact.14/ReFluxSuperAgent 的示例。它可能看起来很复杂,但该模式提供了良好的关注点和灵活性分离。

server.js

'use strict';

let bodyParser = require('body-parser');
let express = require('express');
let favicon = require('serve-favicon');

let path = require('path');
let port = Number(3500);

let routes = require('./routes');

let app = express();
let server = app.listen(port);

app.use(bodyParser.json());
app.use(bodyParser.urlencoded( extended: false ));

app.use('/', express.static('ui-dist'));
app.use('/routes', routes);

app.use(favicon(path.join(__dirname, '..', 'ui-dist', 'img', 'favicon.ico')));
app.get('/', function(req, res) res.sendfile(__dirname + '/index.html', [], null); );
路由.js

'use strict';

let express = require('express');
let router = express.Router();

let getSetData = require('./routes/getsetdata');

router.get('/getData', function(req, res) 
	let getDataDone = function(data) res.send(data); ;
	getSetData.getData(getDataDone);
);

router.post('/setData', function(req, res) 
	let setDataDone = function(data) res.send(data); ;
	console.log(req.body);
	getSetData.setData(req.body, setDataDone);
);

module.exports = router;

getsetdata.js

'use strict';

var fs = require('fs');

var rootDataPath = './data';

var getData = function(doneCallBack) 
	var filePath = rootDataPath + '/basic.json';
	var jsonReadCallBack = function(err, data)
		if (err) doneCallBack('Data readFile error ' + filePath);
		else 
			var jsonData = JSON.parse(data.toString());
			doneCallBack(jsonData);
		
	;
	fs.readFile(filePath, jsonReadCallBack);
;

var setData = function(data, doneCallBack) 
	var filePath = rootDataPath + '/basic.json';
	var writeFileCallBack = function (err) 
		if (err) console.log('error saving Data.json file ');
		doneCallBack('ok');
	;
	fs.writeFile(filePath, JSON.stringify(data, null, 2), writeFileCallBack);
;

module.exports.getData = getData;
module.exports.setData = setData;

index.html

<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>ReactPatterns-ReFluxWebSocket</title>

		<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.min.css">
		<link rel="stylesheet" href="app.min.css"/>

	</head>
	<body class="bodyStyle main">

		<header class="text-center header" >
			<span class="Title">ReactPatterns-ReFluxSuperAgent by Janaka</span>
		</header>

		<section id="react" class="content"></section>

		<script src="app.min.js"></script>

	</body>
</html>

app.js

'use strict';

import React  from 'react';
import ReactDom  from 'react-dom';

import AppCtrl from './components/app.ctrl.js';
import Actions from './flux/Actions';
import ApiStore from './flux/Api.Store';

window.ReactDom = ReactDom;

Actions.apiInit();

ReactDom.render( <AppCtrl />, document.getElementById('react') );

api.store.js

import Reflux from 'reflux';

import Actions from './Actions';
import ApiFct from './../utils/api.js';

let ApiStoreObject = 
	newData: 
		"React version": "0.14",
		"Project": "ReFluxSuperAgent",
		"currentDateTime": new Date().toLocaleString()
	,
	listenables: Actions,
	apiInit()  ApiFct.setData(this.newData); ,
	apiInitDone()  ApiFct.getData(); ,
	apiSetData(data)  ApiFct.setData(data); 

const ApiStore = Reflux.createStore(ApiStoreObject);
export default ApiStore;

api.js

import request from 'superagent';

import Actions from '../flux/Actions';

let uri = 'http://localhost:3500';

module.exports = 
	getData()  request.get(uri + '/routes/getData').end((err, res) =>  this.gotData(res.body); ); ,
	gotData(data)  Actions.gotData1(data); Actions.gotData2(data); Actions.gotData3(data); ,
	setData(data)  request.post('/routes/setData').send(data).end((err, res) =>  Actions.apiInitDone(); ) ,
;

basic.store.js

import Reflux from 'reflux';

import Actions from './Actions';
import AddonStore from './Addon.Store';
import MixinStoreObject from './Mixin.Store';

function _GotData(data)  this.data1 = data; BasicStore.trigger('data1'); 

let BasicStoreObject = 
	init()  this.listenTo(AddonStore, this.onAddonTrigger); ,
	data1: ,
	listenables: Actions,
	mixins: [MixinStoreObject],
	onGotData1: _GotData,
	onAddonTrigger()  BasicStore.trigger('data2'); ,
	getData1()  return this.data1; ,
	getData2()  return AddonStore.data2; ,
	getData3()  return this.data3; 

const BasicStore = Reflux.createStore(BasicStoreObject);
export default BasicStore;

app.ctrl.js

import React from 'react';

import BasicStore from './../flux/Basic.Store';

let AppCtrlSty = 
	height: '100%',
	padding: '0 10px 0 0'


const getState = () => 
	return 
		Data1: BasicStore.getData1(),
		Data2: BasicStore.getData2(),
		Data3: BasicStore.getData3()
	;
;

class AppCtrlRender extends React.Component 
 	render() 
		let data1 = JSON.stringify(this.state.Data1, null, 2);
		let data2 = JSON.stringify(this.state.Data2, null, 2);
		let data3 = JSON.stringify(this.state.Data3, null, 2);
		return (
			<div id='AppCtrlSty' style=AppCtrlSty>
				React 1.4 ReFlux with SuperAgent<br/><br/>
				Data1: data1<br/><br/>
				Data2: data2<br/><br/>
				Data3: data3<br/><br/>
			</div>
		);
	


export default class AppCtrl extends AppCtrlRender 
	constructor() 
		super();
		this.state = getState();
	

	componentDidMount()  this.unsubscribe = BasicStore.listen(this.storeDidChange.bind(this)); 
	componentWillUnmount()  this.unsubscribe(); 
	storeDidChange(id) 
		switch (id) 
			case 'data1': this.setState(Data1: BasicStore.getData1()); break;
			case 'data2': this.setState(Data2: BasicStore.getData2()); break;
			case 'data3': this.setState(Data3: BasicStore.getData3()); break;
			default: this.setState(getState());
		
	

【讨论】:

以上是关于具有 REST API 架构的单页应用程序的主要内容,如果未能解决你的问题,请参考以下文章

在具有不同主页的单页应用程序中路由

基于Vue.js 与 WordPress Rest API 构建单页应用

单页应用程序 (SPA) 与全栈应用程序。约束和优势。

基于Redux架构的单页应用开发总结

具有基于 HttpOnly cookie 的身份验证和会话管理的单页应用程序

使用 AngularJS 和 Spring Security 的单页应用程序中需要 AntMatchers