RefluxJS "одиночный" магазин?
Хорошо.. Я создал компонент загрузки, когда пользователь загружает изображение, компонент показывает предварительный просмотр изображения с помощью API FileReader.
Но если я использовал 3 компонента в другом компоненте, когда я загружаю изображение, это изображение также повторяется в 3 компонентах.
Пример:
... in render method
<UploadImage />
<UploadImage />
<UploadImage />
....
Мой компонент:
var React = require('react');
var Reflux = require('reflux');
// Actions
var actions = require('../../actions/Actions');
// Stores
var UploadStore = require('../../stores/ui/UploadStore');
var UI = require('material-ui');
var FlatButton = UI.FlatButton;
var Snackbar = UI.Snackbar;
var UploadImage = React.createClass({
mixins: [Reflux.connect(UploadStore, 'upload')],
propTypes: {
filename: React.PropTypes.string,
filesrc: React.PropTypes.string,
extensions: React.PropTypes.array.isRequired
},
getDefaultProps: function() {
return {
extensions: ['jpg', 'png', 'jpeg', 'gif']
};
},
_uploadImage: function () {
var file = {
file: this.refs.upload.getDOMNode().files[0] || false,
extensions: this.props.extensions
};
try {
actions.upload(file);
}
catch (e) {
console.log(e);
}
},
_uploadedImage: function() {
if (this.state.upload.filename) {
return (
<div className="upload-image">
<img src={this.state.upload.filesrc} />
<p>{this.state.upload.filename}</p>
</div>
);
}
},
render: function() {
return (
<div className="upload-image-container component-container">
<div className="upload-fields component-fields">
<h3>Imagem</h3>
<p>Arquivos PNG ou SVG no tamanho de XXXxYYYpx de até 50kb.</p>
<FlatButton label="Selecionar Imagem" className="upload-button">
<input
type="file"
id="imageButton"
className="upload-input"
ref="upload"
onChange={this._uploadImage} />
</FlatButton>
</div>
{this._uploadedImage()}
</div>
);
}
});
module.exports = UploadImage;
Мой магазин:
var Reflux = require('reflux');
var actions = require('../../actions/Actions');
var UploadStore = Reflux.createStore({
listenables: [actions],
data: {
filename: '',
filesrc: ''
},
getInitialState: function() {
return this.data;
},
onUpload: function (f) {
if (f) {
// Check extension
var extsAllowed = f.extensions;
if (this.checkExtension(extsAllowed, f.file.name)) {
// Crate the FileReader for upload
var reader = new FileReader();
reader.readAsDataURL(f.file);
reader.addEventListener('loadend', function() {
this.setData({
uploaded: true,
filename: f.file.name,
filesrc: reader.result
});
}.bind(this));
reader.addEventListener('error', function () {
actions.error('Não foi possível ler o seu arquivo. Por favor, verifique se enviou o arquivo corretamente.');
}.bind(this));
}
else {
actions.error('O arquivo que você está tentando enviar não é válido. Envie um arquivo nas seguintes extensões: ' + extsAllowed.join(', ') + '.');
}
}
else {
actions.error('File object not found.');
}
},
checkExtension: function (extensions, filename) {
var fileExt = filename.split('.').pop().toLowerCase();
var isSuccess = extensions.indexOf(fileExt) > -1;
if (isSuccess) return true;
return false;
},
setData: function(data) {
this.data = data;
this.trigger(data);
}
});
module.exports = UploadStore;
Результат:
Любая идея?
Спасибо!
1 ответ
К сожалению, магазин ведет себя как одиночный, то есть существует только один экземпляр UploadStore.
Что вы можете сделать, это ввести дополнительный параметр, чтобы разделить загрузки. Ваш магазин теперь будет принимать массив загрузок, но каждая загрузка будет помечена категорией, и ваш компонент также будет иметь категорию и будет принимать только изображения из магазина, которые принадлежат к той же категории. Это делается с помощью Reflux.connectFilter
Mixin.
Во-первых, я бы разделил загруженное изображение на его собственный компонент, например так:
var UploadedImage = React.createClass({
propTypes: {
upload: React.PropTypes.object.isRequired
},
render: function() {
return (
<div className="upload-image">
<img src={this.props.upload.filesrc} />
<p>{this.props.upload.filename}</p>
</div>
);
}
});
Тогда мы должны изменить некоторые вещи в вашем UploadImage
компонент, так что он будет фильтровать по категории:
var UploadImage = React.createClass({
// only select those uploads which belong to us
mixins: [
Reflux.connectFilter(UploadStore, "uploads", function(uploads) {
return uploads.filter(function(upload) {
return upload.category === this.props.category;
}.bind(this))[0];
})
],
propTypes: {
filename: React.PropTypes.string,
filesrc: React.PropTypes.string,
extensions: React.PropTypes.array.isRequired,
// an additional prop for the category
category: React.PropTypes.string.isRequired
},
_uploadImage: function () {
var file = {
file: this.refs.upload.getDOMNode().files[0] || false,
extensions: this.props.extensions
};
try {
// pass in additional parameter!
actions.upload(file, this.props.category);
}
catch (e) {
console.log(e);
}
},
render: function() {
return (
<div className="upload-image-container component-container">
<div className="upload-fields component-fields">
<h3>Imagem</h3>
<p>Arquivos PNG ou SVG no tamanho de XXXxYYYpx de até 50kb.</p>
<FlatButton label="Selecionar Imagem" className="upload-button">
<input
type="file"
id="imageButton"
className="upload-input"
ref="upload"
onChange={this._uploadImage} />
</FlatButton>
</div>
{this.state.uploads.map(function(upload, index) {
return <UploadedImage key={index} upload={upload}/>;
})}
</div>
);
}
});
И ваш магазин теперь содержит массив "файловых" объектов, каждый из которых помечен категорией:
var UploadStore = Reflux.createStore({
listenables: [actions],
// data is now an array of objects
data: [],
getInitialState: function() {
return this.data;
},
// here we get the file + category
onUpload: function (f, category) {
if (f) {
// Check extension
var extsAllowed = f.extensions;
if (this.checkExtension(extsAllowed, f.file.name)) {
// Crate the FileReader for upload
var reader = new FileReader();
reader.readAsDataURL(f.file);
reader.addEventListener('loadend', function() {
this.setData(this.data.concat([{
uploaded: true,
filename: f.file.name,
filesrc: reader.result,
category: category /* adding category here */
}]));
}.bind(this));
reader.addEventListener('error', function () {
actions.error('Não foi possível ler o seu arquivo. Por favor, verifique se enviou o arquivo corretamente.');
}.bind(this));
}
else {
actions.error('O arquivo que você está tentando enviar não é válido. Envie um arquivo nas seguintes extensões: ' + extsAllowed.join(', ') + '.');
}
}
else {
actions.error('File object not found.');
}
},
checkExtension: function (extensions, filename) {
var fileExt = filename.split('.').pop().toLowerCase();
var isSuccess = extensions.indexOf(fileExt) > -1;
if (isSuccess) return true;
return false;
},
setData: function(data) {
this.data = data;
this.trigger(data);
}
});
И, наконец, по вашему мнению, вы можете использовать UploadImage
компонент, как это:
Я написал код на лету, поэтому могут возникнуть некоторые проблемы - но это больше о концепции. Также возможно загрузить более одного изображения на категорию сейчас, если это нежелательно, тогда подумайте о замене массива в магазине хэш-картой, чтобы ключи соответствовали категории - тогда только одно изображение может быть загружено в каждая категория.
Ответ на ваш комментарий
Может быть, вы могли бы уйти с фабричным методом для магазина, то есть что-то вроде этого:
var UploadStoreFactory = function() {
return Reflux.createStore({
/* your existing code as it was originally */
});
};
var UploadImage = React.createClass({
mixins: [Reflux.connect(UploadStoreFactory(), 'upload')],
/* your existing code as it was originally */
});
но я подозреваю, что ваши действия приведут в действие все экземпляры ваших загрузочных магазинов, но стоит попробовать. Но это имеет много недостатков, таких как другие компоненты не могут слушать этот магазин легко.
В этом стековом потоке задается аналогичный вопрос, а также концептуально правильный способ сделать это - использовать один контейнер / хранилище для всех и хранить элементы в хранилище с тегами, чтобы вы могли хранить их отдельно.
Имейте в виду, что магазины также очищаются и пополняются, например, если вы создаете интернет-магазин с товарами и различными категориями, вы очищаете и пополняете ProductStore
каждый раз, когда пользователь переключается на другую категорию. Если у вас дополнительно есть боковая панель, которая может показывать "Продукты, которые могут вам понравиться", я бы смоделировал это как отдельный магазин, т.е. ProductSuggestionStore
но оба содержат объекты типа "Продукт".
Если хранилища ведут себя семантически по-разному, но разделяют большую логику загрузки, вы также можете попытаться создать базовый прототип / класс для своих магазинов, а затем расширить конкретные хранилища или передать аутсорсинг логики загрузки в класс обслуживания.
Если вас беспокоит производительность, то есть одна загрузка приводит к повторному рендерингу всех компонентов, тогда вы можете добавить проверку в shouldComponentUpdate
,
Хорошим примером того, почему использовать только один магазин, может быть случай, когда пользователь захочет закрыть окно, но где-то на вашем веб-сайте загрузка еще не завершена, тогда вашему представлению основного приложения просто нужно проверить один магазин. Также загрузки могут быть поставлены в очередь так, чтобы пропускная способность не была исчерпана, так как все загрузки проходят через один магазин.
Также имейте в виду, что у вас могут быть магазины, которые слушают другие магазины, например, UploadHistoryStore
сохраняет отметку времени последних 10 загрузок. Все загрузки идут в одну корзину, но если у вас есть компонент "Последние 10 загрузок", он просто должен прослушать "UploadHistoryStore"
var UploadStore = Reflux.createStore({
/* ... upload stuff and trigger as usual ... */
});
var UploadHistoryStore = Reflux.createStore({
// keep the last ten uploads
historyLength: 10,
init: function() {
// Register statusStore's changes
this.listenTo(UploadStore, this.output);
this.history = [];
},
// Callback
output: function(upload) {
this.history.push({
date: new Date(), // add a date when it was uploaded
upload: upload // the upload object
}).slice(1, this.historyLength);
// Pass the data on to listeners
this.trigger(this.history);
}
});