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

时间:2015-11-08 19:31:30

标签: javascript node.js api reactjs

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

  • 移动应用
  • 单页应用

由于到目前为止我使用了服务器端模板(即我的所有API路由都使用HTML进行了回复),我需要做出巨大更改以支持API以{{1}进行响应仅适用于移动应用,所以我决定对整个应用进行重构,以同时支持这两个方面。

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

  • 我将在JSON
  • 中重写我的所有UI组件
  • 所有当前React路由仍将使用API回复,而HTMLserver-rendered使用服务器端HTML,但这些React UI组件将也包括在浏览器端,它将支持单页面应用程序功能。
  • 我会写一些特殊的React REST,可能基于JSON API服务器标准与单页面应用程序和移动Web应用程序进行通信。
  • 两个API路由(API响应页面 - API,以及数据 - HTML)将使用JSON路由器完成,该路由器将执行< em> controllers (服务器组件),用于封装与数据访问层(服务器组件)的数据组合操作。
  • 数据访问层基本上由Express个模型组成。

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

1 个答案:

答案 0 :(得分:1)

如果包含助焊剂图案,它将使您的应用程序更易于构建和维护。我建议你看看一些初学者项目并选择一个赞美你自己风格的项目。以下是https://github.com/calitek/ReactPatterns React.14 / ReFluxSuperAgent的示例。这可能看起来很复杂,但这种模式可以很好地分离关注点和灵活性。

server.js

&#13;
&#13;
'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); });
&#13;
&#13;
&#13; routes.js

&#13;
&#13;
'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;
&#13;
&#13;
&#13;

getsetdata.js

&#13;
&#13;
'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;
&#13;
&#13;
&#13;

的index.html

&#13;
&#13;
<!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>
&#13;
&#13;
&#13;

app.js

&#13;
&#13;
'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') );
&#13;
&#13;
&#13;

api.store.js

&#13;
&#13;
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;
&#13;
&#13;
&#13;

api.js

&#13;
&#13;
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(); }) },
};
&#13;
&#13;
&#13;

basic.store.js

&#13;
&#13;
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;
&#13;
&#13;
&#13;

app.ctrl.js

&#13;
&#13;
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());
		}
	}
}
&#13;
&#13;
&#13;