我已经找到了很多关于如何获取React和Flux数据的资源,博客和意见,但更不用说将数据写入服务器了。有人可以在构建一个持久更改RESTful Web API的简单编辑表单的上下文中为“首选”方法提供基本原理和一些示例代码吗?
具体来说,哪个Flux框应该调用$.post
,调用ActionCreator.receiveItem()
在哪里(以及它做了什么),以及商店注册方法中的内容是什么?
相关链接:
答案 0 :(得分:4)
简短回答
通过实施示例的长答案
apiUtils / BarAPI.js
var Request = require('./Request'); //it's a custom module that handles request via superagent wrapped in Promise
var BarActionCreators = require('../actions/BarActionCreators');
var _endpoint = 'http://localhost:8888/api/bars/';
module.exports = {
post: function(barData) {
BarActionCreators.savePending();
Request.post(_endpoint, barData).then (function(res) {
if (res.badRequest) { //i.e response returns code 400 due to validation errors for example
BarActionCreators.saveInvalidated(res.body);
}
BarActionCreators.savedSuccess(res.body);
}).catch( function(err) { //server errors
BarActionCreators.savedError(err);
});
},
//other helpers out of topic for this answer
};
动作/ BarActionCreators.js
var AppDispatcher = require('../dispatcher/AppDispatcher');
var ActionTypes = require('../constants/BarConstants').ActionTypes;
var BarAPI = require('../apiUtils/VoucherAPI');
module.exports = {
save: function(bar) {
BarAPI.save(bar.toJSON());
},
saveSucceed: function(response) {
AppDispatcher.dispatch({
type: ActionTypes.BAR_SAVE_SUCCEED,
response: response
});
},
saveInvalidated: function(barData) {
AppDispatcher.dispatch({
type: ActionTypes.BAR_SAVE_INVALIDATED,
response: response
})
},
saveFailed: function(err) {
AppDispatcher.dispatch({
type: ActionTypes.BAR_SAVE_FAILED,
err: err
});
},
savePending: function(bar) {
AppDispatcher.dispatch({
type: ActionTypes.BAR_SAVE_PENDING,
bar: bar
});
}
rehydrate: function(barId, field, value) {
AppDispatcher.dispatch({
type: ActionTypes.BAR_REHYDRATED,
barId: barId,
field: field,
value: value
});
},
};
存储/ BarStore.js
var assign = require('object-assign');
var EventEmitter = require('events').EventEmitter;
var Immutable = require('immutable');
var AppDispatcher = require('../dispatcher/AppDispatcher');
var ActionTypes = require('../constants/BarConstants').ActionTypes;
var BarAPI = require('../apiUtils/BarAPI')
var CHANGE_EVENT = 'change';
var _bars = Immutable.OrderedMap();
class Bar extends Immutable.Record({
'id': undefined,
'name': undefined,
'description': undefined,
'save_status': "not saved" //better to use constants here
}) {
isReady() {
return this.id != undefined //usefull to know if we can display a spinner when the Bar is loading or the Bar's data if it is ready.
}
getBar() {
return BarStore.get(this.bar_id);
}
}
function _rehydrate(barId, field, value) {
//Since _bars is an Immutable, we need to return the new Immutable map. Immutable.js is smart, if we update with the save values, the same reference is returned.
_bars = _bars.updateIn([barId, field], function() {
return value;
});
}
var BarStore = assign({}, EventEmitter.prototype, {
get: function(id) {
if (!_bars.has(id)) {
BarAPI.get(id); //not defined is this example
return new Bar(); //we return an empty Bar record for consistency
}
return _bars.get(id)
},
getAll: function() {
return _bars.toList() //we want to get rid of keys and just keep the values
},
Bar: Bar,
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
});
var _setBar = function(barData) {
_bars = _bars.set(barData.id, new Bar(barData));
};
BarStore.dispatchToken = AppDispatcher.register(function(action) {
switch (action.type)
{
case ActionTypes.BAR_REHYDRATED:
_rehydrate(
action.barId,
action.field,
action.value
);
BarStore.emitChange();
break;
case ActionTypes.BAR_SAVE_PENDING:
_bars = _bars.updateIn([action.bar.id, "save_status"], function() {
return "saving";
});
BarStore.emitChange();
break;
case ActionTypes.BAR_SAVE_SUCCEED:
_bars = _bars.updateIn([action.bar.id, "save_status"], function() {
return "saved";
});
BarStore.emitChange();
break;
case ActionTypes.BAR_SAVE_INVALIDATED:
_bars = _bars.updateIn([action.bar.id, "save_status"], function() {
return "invalid";
});
BarStore.emitChange();
break;
case ActionTypes.BAR_SAVE_FAILED:
_bars = _bars.updateIn([action.bar.id, "save_status"], function() {
return "failed";
});
BarStore.emitChange();
break;
//many other actions outside the scope of this answer
default:
break;
}
});
module.exports = BarStore;
组件/ BarList.react.js
var React = require('react/addons');
var Immutable = require('immutable');
var BarListItem = require('./BarListItem.react');
var BarStore = require('../stores/BarStore');
function getStateFromStore() {
return {
barList: BarStore.getAll(),
};
}
module.exports = React.createClass({
getInitialState: function() {
return getStateFromStore();
},
componentDidMount: function() {
BarStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
BarStore.removeChangeListener(this._onChange);
},
render: function() {
var barItems = this.state.barList.toJS().map(function (bar) {
// We could pass the entire Bar object here
// but I tend to keep the component not tightly coupled
// with store data, the BarItem can be seen as a standalone
// component that only need specific data
return <BarItem
key={bar.get('id')}
id={bar.get('id')}
name={bar.get('name')}
description={bar.get('description')}/>
});
if (barItems.length == 0) {
return (
<p>Loading...</p>
)
}
return (
<div>
{barItems}
</div>
)
},
_onChange: function() {
this.setState(getStateFromStore();
}
});
组件/ BarListItem.react.js
var React = require('react/addons');
var ImmutableRenderMixin = require('react-immutable-render-mixin')
var Immutable = require('immutable');
module.exports = React.createClass({
mixins: [ImmutableRenderMixin],
// I use propTypes to explicitly telling
// what data this component need. This
// component is a standalone component
// and we could have passed an entire
// object such as {id: ..., name, ..., description, ...}
// since we use all the datas (and when we use all the data it's
// a better approach since we don't want to write dozens of propTypes)
// but let's do that for the example's sake
propTypes: {
id: React.PropTypes.number.isRequired,
name: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired
}
render: function() {
return (
<li> //we should wrapped the following p's in a Link to the editing page of the Bar record with id = this.props.id. Let's assume that's what we did and when we click on this <li> we are redirected to edit page which renders a BarDetail component
<p>{this.props.id}</p>
<p>{this.props.name}</p>
<p>{this.props.description}</p>
</li>
)
}
});
组件/ BarDetail.react.js
var React = require('react/addons');
var ImmutableRenderMixin = require('react-immutable-render-mixin')
var Immutable = require('immutable');
var BarActionCreators = require('../actions/BarActionCreators');
module.exports = React.createClass({
mixins: [ImmutableRenderMixin],
propTypes: {
id: React.PropTypes.number.isRequired,
name: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired
},
handleSubmit: function(event) {
//Since we keep the Bar data up to date with user input
//we can simply save the actual object in Store.
//If the user goes back without saving, we could display a
//"Warning : item not saved"
BarActionCreators.save(this.props.id);
},
handleChange: function(event) {
BarActionCreators.rehydrate(
this.props.id,
event.target.name, //the field we want to rehydrate
event.target.value //the updated value
);
},
render: function() {
return (
<form onSubmit={this.handleSumit}>
<input
type="text"
name="name"
value={this.props.name}
onChange={this.handleChange}/>
<textarea
name="description"
value={this.props.description}
onChange={this.handleChange}/>
<input
type="submit"
defaultValue="Submit"/>
</form>
)
},
});
使用此基本示例,只要用户通过BarDetail
组件中的表单编辑了条形项,基础Bar
记录将在本地保持最新,并且在提交表单时我们尝试将其保存在服务器上。就是这样:))
答案 1 :(得分:1)
Should flux stores, or actions (or both) touch external services?方法对我来说似乎很自然。
此外,在某些情况下,由于某些其他操作被触发,您需要触发某些操作,您可以在此处触发相关商店的操作,从而导致存储和视图更新。