2015-10-05 47 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之前,我沒有這個問題,但是我的代碼非常投入,我不得不保持傳遞不太好的方式的屬性。 –