2015-11-08 3 views
2

Я создал веб-приложение среднего размера в Node.js. Сначала я не планировал, что он будет расти до среднего размера и не знает, будет ли он вообще использоваться. Теперь, когда пользователи клиента начали использовать его, существует потребность в 2-х дополнительных функций:Одностраничное приложение с архитектурой API REST

  • мобильные приложения
  • одностраничных приложений

Поскольку я до сих пор используемые серверные шаблоны (т.е. все мои маршруты API отвечали HTML), мне нужно сделать огромные изменения, чтобы поддерживать API, отвечая JSON только для мобильных приложений, поэтому я решил сделать рефакторинг всего приложения для поддержки обеих этих двух вещей одновременно.

Чтение через некоторые онлайн-ресурсы (а именно Single Page App Book) и сравнение доступных фреймворков JavaScript (Angular vs Backbone vs React vs Ember). Я пришел к выводам, представленным ниже. Мой вопрос: я что-то упускаю? Итак, вот как я планирую расширить свой веб-приложение:

  • Я буду переписывать все мои компоненты пользовательского интерфейса в React
  • Всех текущих API маршрутов будет по-прежнему отвечать HTML и HTML будет server-rendered с помощью сервера -side React, но эти компоненты React UI также будут включены на стороне браузера, что будет поддерживать функции одностраничных приложений.
  • Я напишу специальный RESTAPI, возможно, основанный на стандарте JSON API для взаимодействия сервера с одностраничным приложением и мобильными веб-приложениями.
  • Оба API маршрутов (API реагирует со страницами - HTML, а также с данными - JSON) будут осуществляться с помощью Express маршрутизатора, который будет проводить контроллер (серверные компоненты) для инкапсулирования в сочетании манипулирования данных со слоем доступа данных (сервер компонент).
  • Уровень доступа к данным в основном состоит из моделей Mongoose.

Поскольку для реализации и рефакторинга это займет больше времени, я хотел бы быть уверенным, что я на правильном пути. Я что-то упустил?

ответ

1

Это упростит создание и поддержку вашего приложения, если вы включите шаблон потока. Я предлагаю вам взглянуть на некоторые из стартовых проектов и выбрать тот, который дополняет ваш собственный стиль. Вот пример из https://github.com/calitek/ReactPatterns React.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); });
routes.js

'use strict'; 
 

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

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

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

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

 
module.exports = router;

getsetdata.JS

'use strict'; 
 

 
var fs = require('fs'); 
 

 
var rootDataPath = './data'; 
 

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

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

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

index.html

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

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

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

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

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

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

 
\t </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 = { 
 
\t newData: { 
 
\t \t "React version": "0.14", 
 
\t \t "Project": "ReFluxSuperAgent", 
 
\t \t "currentDateTime": new Date().toLocaleString() 
 
\t }, 
 
\t listenables: Actions, 
 
\t apiInit() { ApiFct.setData(this.newData); }, 
 
\t apiInitDone() { ApiFct.getData(); }, 
 
\t 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 = { 
 
\t getData() { request.get(uri + '/routes/getData').end((err, res) => { this.gotData(res.body); }); }, 
 
\t gotData(data) { Actions.gotData1(data); Actions.gotData2(data); Actions.gotData3(data); }, 
 
\t 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 = { 
 
\t init() { this.listenTo(AddonStore, this.onAddonTrigger); }, 
 
\t data1: {}, 
 
\t listenables: Actions, 
 
\t mixins: [MixinStoreObject], 
 
\t onGotData1: _GotData, 
 
\t onAddonTrigger() { BasicStore.trigger('data2'); }, 
 
\t getData1() { return this.data1; }, 
 
\t getData2() { return AddonStore.data2; }, 
 
\t 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 = { 
 
\t height: '100%', 
 
\t padding: '0 10px 0 0' 
 
} 
 

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

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

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

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

Смежные вопросы