2015-10-05 44 views
2

嗯..我创建了一个上传组件,当用户上传图片时,组件使用FileReader API显示图片预览。RefluxJS“单一”商店?

但是,如果我在另一个组件中使用了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; 

其结果是:

store repeat result

任何想法?

谢谢!

回答

3

不幸的是,商店的行为类似于单例,即只有一个UploadStore实例。

你可以做的是引入一个额外的参数来保持上传。您的商店现在将采用一系列上传内容,但每个上传内容都会标记为该类别,并且您的组件也会有一个类别,并且只会从属于同一类别的商店中获取图像。这是通过使用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 */ 
}); 

但我怀疑你的行动将触发的所有实例你的上传商店,但它是值得一试。但是这带来了很多缺点,比如其他组件无法轻松地听这个商店。

this stackoverflow提出了一个类似的问题,并且概念性的正确方法是使用一个存储桶/商店,并将商品保存在商店中,以便让它们保持分开。

请记住,商店也会清除重新填充的内容,例如,如果您创建了一个带有产品和不同类别的网上商店,则每次用户切换到另一个类别时,您都会清除并重新填写ProductStore。如果您还有一个可能显示“您可能喜欢的产品”的侧边栏,那么我会将其作为单独的商店进行建模,即ProductSuggestionStore,但都包含“产品”类型的对象。

如果商店的语义不同但共享很多上传逻辑,您也可以尝试为您的商店构建基本原型/类,然后扩展特定商店或将上传逻辑外包给服务类。

如果您担心性能问题,即一次上传会导致所有组件重新呈现,您可以在shouldComponentUpdate之内添加支票。

一个很好的例子,为什么只使用一个商店可能是用户想要关闭窗口,但在您的网站的某个地方上传仍然未决的情况,那么您的主应用程序视图只需要检查一个商店。同时上传也可以轻松排队,以便带宽不会耗尽,因为所有上传都通过一个商店。

另外请记住,您可以拥有其他商店的商店,例如UploadHistoryStore保留最近10次上传的时间戳记录。所有上传进入同一个桶,但如果你有“Last 10 Uploads”组件,它只需要听“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); 
    } 
}); 
+0

Thanks @sled!这是唯一的方法?如果如果我有其他类似行为的组件,我总是需要这样做?在Flux中,我也一样吗? –

+1

在不断变化的过程中,它会是一样的 - 我添加了一个示例,了解如何创建倾听其他商店的商店,并只取其一部分商店。 – sled

+0

再次感谢@sled!我正在使用第一个选项,使用connectFilter。问题是我的很多组件都是可重用的,并且使用单个存储,代码可能会更长,因为我几乎所有的东西都必须使用过滤器。在使用Reflux之前,我没有这个问题,但是我的代码非常投入,我不得不保持传递不太好的方式的属性。 –