React圖片上傳元件react-fileupload的使用方法

spcming發表於2020-10-04

react-fileupload的安裝

使用npm install react-fileupload --saveyarn react-fileupload 安裝均可

使用方法

若在開發時用的是react@15.x的版本,直接引入一下程式碼即可使用

const FileUpload = require('react-fileupload');
...
render(){
	/*set properties*/
	const options={
		baseUrl:'http://127.0.0.1',
		param:{
			fid:0
		}
	}
	/*Use FileUpload with options*/
	/*Set two dom with ref*/
	return (
		<FileUpload options={options}>
			<button ref="chooseBtn">choose</button>
			<button ref="uploadBtn">upload</button>
		</FileUpload>
	)	        
}

但此程式碼並不適用於react@16.x的版本

屬性值

react-fileupload的屬性有很多,這裡只對常用的屬性進行介紹,其餘屬性詳見github官網

  1. baseUrl:圖片上傳後提交的目標地址
  2. fileFieldName:為此類提交的圖片起一個鍵名作為識別符號
  3. dataType:回應的格式
  4. chooseAndUpload:是否在選擇圖片時自動提交
  5. uploadSuccess:成功提交後的回撥函式
  6. uploadError:未能成功提交的回撥函式

要注意的是,在chooseAndUpload屬性為true的時候,提交按鈕的ref值需要改成chooseAndUpload
以下是程式碼案例

import React, { Component } from 'react'
// 這裡引入的是本地檔案而不是下載的模組,是為了適配React@16.x的版本,這一塊在後面會講到
import FileUpload from './react-fileupload.jsx'

class FileUploader extends Component {
    constructor(props) {
        super(props)
        this.state = {
            
        }
    }

    render() {
        /*set properties*/
        const options={
            baseUrl: '/manage/product/upload.do',
            fileFieldName: 'upload_file',
            dataType: 'json',
            chooseAndUpload:true,
            uploadSuccess: (res)=>{
                this.props.onSuccess(res.data)
            },
            uploadError: (err)=>{
                this.props.onError(err.message||'上傳失敗')
            }
        }
        /*Use FileUpload with options*/
        /*Set two dom with ref*/
        return (
            <FileUpload options={options}>
                <button className="btn btn-xs btn-default" ref="chooseAndUpload">請選擇圖片</button>
            </FileUpload>
        )
    }
}

React@16.x的版本相容

在上面提到,react-fileupload是基於react@15.x的版本開發的,而react的一大特點就是新版本對舊版本不相容,這裡給出在react@16.x版本下使用react-fileupload的方法
由於原始碼就已經不相容react@16.x,因此我們需要將原始碼取出進行改寫,首先找到node_modules裡面react-fileupload的原始碼檔案,即下圖中的FileUpload.js
原始碼檔案位置

將FileUpload.js中的內容複製出來,並存放到本地目錄的檔案中,然後開始改寫

這裡我先給出我改好後的檔案,直接複製到本地檔案後引入即可使用,具體修改方法放到程式碼後面,需要安裝prop-types,安裝方法直接使用npm install prop-types --saveyarn add prop-types即可

import React from 'react'
import PT from 'prop-types';
const emptyFunction = function() {}
/*當前IE上傳組的id*/
let currentIEID = 0
/*存放當前IE上傳組的可用情況*/
const IEFormGroup = [true]
/*當前xhr的陣列(僅有已開始上傳之後的xhr)*/
let xhrList = []
let currentXHRID = 0


class FileUpload extends React.Component{

    constructor(props){
        super(props);
        this.state={
            chooseBtn: {},       //選擇按鈕。如果chooseAndUpload=true代表選擇並上傳。
            uploadBtn: {},       //上傳按鈕。如果chooseAndUpload=true則無效。
            before: [],      //存放props.children中位於chooseBtn前的元素
            middle: [],      //存放props.children中位於chooseBtn後,uploadBtn前的元素
            after: []  
        }
    }

    /*根據props更新元件*/
    _updateProps(props) {
        this.isIE = !(this.checkIE() < 0 || this.checkIE() >= 10)
        const options = props.options
        this.baseUrl = options.baseUrl     //域名
        this.param = options.param     //get引數
        this.chooseAndUpload = options.chooseAndUpload || false      //是否在使用者選擇了檔案之後立刻上傳
        this.paramAddToField = options.paramAddToField || undefined  //需要新增到FormData的物件。不支援IE
        /*upload success 返回resp的格式*/
        this.dataType = 'json'
        options.dataType && (options.dataType.toLowerCase() == 'text') && (this.dataType = 'text')
        this.wrapperDisplay = options.wrapperDisplay || 'inline-block'     //包裹chooseBtn或uploadBtn的div的display
        this.timeout = (typeof options.timeout == 'number' && options.timeout > 0) ? options.timeout : 0     //超時時間
        this.accept = options.accept || ''  //限制檔案字尾
        this.multiple = options.multiple || false
        this.numberLimit = options.numberLimit || false //允許多檔案上傳時,選擇檔案數量的限制
        this.fileFieldName = options.fileFieldName || false //檔案附加到formData上時的key,傳入string指定一個file的屬性名,值為其屬性的值。不支援IE
        this.withCredentials = options.withCredentials || false //跨域時是否使用認證資訊
        this.requestHeaders = options.requestHeaders || false //要設定的請求頭鍵值對

        /*生命週期函式*/
        /**
         * beforeChoose() : 使用者選擇之前執行,返回true繼續,false阻止使用者選擇
         * @param  null
         * @return  {boolean} 是否允許使用者進行選擇
         */
        this.beforeChoose = options.beforeChoose || emptyFunction
        /**
         * chooseFile(file) : 使用者選擇檔案後的觸發的回撥函式
         * @param file {File | string} 現代瀏覽器返回File物件,IE返回檔名
         * @return
         */
        this.chooseFile = options.chooseFile || emptyFunction
        /**
         * beforeUpload(file,mill) : 使用者上傳之前執行,返回true繼續,false阻止使用者選擇
         * @param file {File | string} 現代瀏覽器返回File物件,IE返回檔名
         * @param mill {long} 毫秒數,如果File物件已有毫秒數則返回一樣的
         * @return  {boolean || object} 是否允許使用者進行上傳 (hack:如果是obj{
         *     assign:boolean 預設true
         *     param:object
         * }), 則對本次的param進行處理
         */
        this.beforeUpload = options.beforeUpload || emptyFunction
        /**
         * doUpload(file,mill) : 上傳動作(xhr send | form submit)執行後呼叫
         * @param file {File | string} 現代瀏覽器返回File物件,IE返回檔名
         * @param mill {long} 毫秒數,如果File物件已有毫秒數則返回一樣的
         * @return
         */
        this.doUpload = options.doUpload || emptyFunction
        /**
         * uploading(progress) : 在檔案上傳中的時候,瀏覽器會不斷觸發此函式。IE中使用每200ms觸發的假進度
         * @param progress {Progress} progress物件,裡面存有例如上傳進度loaded和檔案大小total等屬性
         * @return
         */
        this.uploading = options.uploading || emptyFunction
        /**
         * uploadSuccess(resp) : 上傳成功後執行的回撥(針對AJAX而言)
         * @param resp {json | string} 根據options.dataType指定返回資料的格式
         * @return
         */
        this.uploadSuccess = options.uploadSuccess || emptyFunction
        /**
         * uploadError(err) : 上傳錯誤後執行的回撥(針對AJAX而言)
         * @param err {Error | object} 如果返回catch到的error,其具有type和message屬性
         * @return
         */
        this.uploadError = options.uploadError || emptyFunction
        /**
         * uploadFail(resp) : 上傳失敗後執行的回撥(針對AJAX而言)
         * @param resp {string} 失敗資訊
         */
        this.uploadFail = options.uploadFail || emptyFunction
        /**
         * onabort(mill, xhrID) : 主動取消xhr程式的響應
         * @param mill {long} 毫秒數,本次上傳時刻的時間
         * @param xhrID {int} 在doUpload時會返回的當次xhr代表ID
         */
        this.onabort = options.onabort || emptyFunction

        this.files = options.files || this.files || false        //儲存需要上傳的檔案
        /*特殊內容*/

        /*IE情況下,由於上傳按鈕被隱藏的input覆蓋,不能進行disabled按鈕處理。
         * 所以當disabledIEChoose為true(或者func返回值為true)時,禁止IE上傳。
         */
        this.disabledIEChoose = options.disabledIEChoose || false

        this._withoutFileUpload = options._withoutFileUpload || false      //不帶檔案上傳,為了給秒傳功能使用,不影響IE
        this.filesToUpload = options.filesToUpload || []       //使用filesToUpload()方法代替
        this.textBeforeFiles = options.textBeforeFiles || false //make this true to add text fields before file data
        /*使用filesToUpload()方法代替*/
        if (this.filesToUpload.length && !this.isIE) {
            this.filesToUpload.forEach( file => {
                this.files = [file]
                this.commonUpload()
            })
        }

        /*放置虛擬DOM*/
        let chooseBtn, uploadBtn, flag = 0
        const before = [], middle = [], after = []
        if (this.chooseAndUpload) {
            React.Children.forEach(props.children, (child)=> {
                if (child && child.ref == 'chooseAndUpload') {
                    chooseBtn = child
                    flag++
                } else {
                    flag == 0 ? before.push(child) : flag == 1 ? middle.push(child) : ''
                }
            })
        } else {
            React.Children.forEach(props.children, (child)=> {
                if (child && child.ref == 'chooseBtn') {
                    chooseBtn = child
                    flag++
                } else if (child && child.ref == 'uploadBtn') {
                    uploadBtn = child
                    flag++
                } else {
                    flag == 0 ? before.push(child) : flag == 1 ? middle.push(child) : after.push(child)
                }
            })
        }
        this.setState({
            chooseBtn,
            uploadBtn,
            before,
            middle,
            after
        })
    }

    /*觸發隱藏的input框選擇*/
    /*觸發beforeChoose*/
    commonChooseFile() {
        const jud = this.beforeChoose()
        if (jud != true && jud != undefined) return
        this.refs['ajax_upload_file_input'].click()
    }
    /*現代瀏覽器input change事件。File API儲存檔案*/
    /*觸發chooseFile*/
    commonChange(e) {
        let files
        e.dataTransfer ? files = e.dataTransfer.files :
          e.target ? files = e.target.files : ''

        /*如果限制了多檔案上傳時的數量*/
        const numberLimit = typeof this.numberLimit === 'function' ? this.numberLimit() : this.numberLimit
        if(this.multiple && numberLimit && files.length > numberLimit) {
            const newFiles = {}
            for(let i = 0; i< numberLimit; i++) newFiles[i] = files[i]
            newFiles.length = numberLimit
            files = newFiles
        }
        this.files = files
        this.chooseFile(files)
        this.chooseAndUpload && this.commonUpload()
    }
    
    /*執行上傳*/
    commonUpload() {
        /*mill引數是當前時刻毫秒數,file第一次進行上傳時會新增為file的屬性,也可在beforeUpload為其新增,之後同一檔案的mill不會更改,作為檔案的識別id*/
        const mill = (this.files.length && this.files[0].mill) || (new Date).getTime()
        const jud = this.beforeUpload(this.files, mill)
        if (jud != true && jud != undefined && typeof jud != 'object') {
            /*清除input的值*/
            this.refs['ajax_upload_file_input'].value = ''
            return
        }



        if (!this.files) return
        if (!this.baseUrl) throw new Error('baseUrl missing in options')

        /*用於存放當前作用域的東西*/
        const scope = {}
        /*組裝FormData*/
        let formData = new FormData()
        /*If we need to add fields before file data append here*/
        if(this.textBeforeFiles){
           formData = this.appendFieldsToFormData(formData);
        }
        if (!this._withoutFileUpload) {
            const fieldNameType = typeof this.fileFieldName

            /*判斷是用什麼方式作為formdata item 的 name*/
            Object.keys(this.files).forEach(key => {
                if(key == 'length') return

                if(fieldNameType == 'function') {
                    const file = this.files[key]
                    const fileFieldName = this.fileFieldName(file)
                    formData.append(fileFieldName, file)
                }else if(fieldNameType == 'string') {
                    const file = this.files[key]
                    formData.append(this.fileFieldName, file)
                }else {
                    const file = this.files[key]
                    formData.append(file.name, file)
                }
            })

        }
        /*If we need to add fields after file data append here*/
        if(!this.textBeforeFiles){
          formData = this.appendFieldsToFormData(formData);
        }
        const baseUrl = this.baseUrl

        /*url引數*/
        /*如果param是一個函式*/
        const param = typeof this.param === 'function' ? this.param(this.files) : this.param

        let paramStr = ''

        if (param) {
            const paramArr = []
            param['_'] = mill
            Object.keys(param).forEach(key =>
              paramArr.push(`${key}=${param[key]}`)
            )
            paramStr = '?' + paramArr.join('&')
        }
        const targeturl = baseUrl + paramStr

        /*AJAX上傳部分*/
        const xhr = new XMLHttpRequest()
        xhr.open('POST', targeturl, true)

        /*跨域是否開啟驗證資訊*/
        xhr.withCredentials = this.withCredentials
        /*是否需要設定請求頭*/
        const rh = this.requestHeaders
        rh && Object.keys(rh).forEach(key =>
            xhr.setRequestHeader(key, rh[key])
        )

        /*處理超時。用定時器判斷超時,不然xhr state=4 catch的錯誤無法判斷是超時*/
        if(this.timeout) {
            xhr.timeout = this.timeout
            xhr.ontimeout = () => {
                this.uploadError({type: 'TIMEOUTERROR', message: 'timeout'})
                scope.isTimeout = false
            }
            scope.isTimeout = false
            setTimeout(()=>{
                scope.isTimeout = true
            },this.timeout)
        }

        xhr.onreadystatechange = () => {
            /*xhr finish*/
            try {
                if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 400) {
                    const resp = this.dataType == 'json' ? JSON.parse(xhr.responseText) : xhr.responseText
                    this.uploadSuccess(resp)
                } else if (xhr.readyState == 4) {
                    /*xhr fail*/
                    const resp = this.dataType == 'json' ? JSON.parse(xhr.responseText) : xhr.responseText
                    this.uploadFail(resp)
                }
            } catch (e) {
                /*超時丟擲不一樣的錯誤,不在這裡處理*/
                !scope.isTimeout && this.uploadError({type: 'FINISHERROR', message: e.message})
            }
        }
        /*xhr error*/
        xhr.onerror = () => {
            try {
                const resp = this.dataType == 'json' ? JSON.parse(xhr.responseText) : xhr.responseText
                this.uploadError({type: 'XHRERROR', message: resp})
            } catch (e) {
                this.uploadError({type: 'XHRERROR', message: e.message})
            }
        }
        /*這裡部分瀏覽器實現不一致,而且IE沒有這個方法*/
        xhr.onprogress = xhr.upload.onprogress = progress => {
            this.uploading(progress, mill)
        }

        /*不帶檔案上傳,給秒傳使用*/
        this._withoutFileUpload ? xhr.send(null) : xhr.send(formData)

        /*儲存xhr id*/
        xhrList.push(xhr)
        const cID = xhrList.length - 1
        currentXHRID = cID

        /*有響應abort的情況*/
        xhr.onabort = () => this.onabort(mill, cID)

        /*trigger執行上傳的使用者回撥*/
        this.doUpload(this.files, mill, currentXHRID)

        /*清除input的值*/
        this.refs['ajax_upload_file_input'].value = ''
    }

    /*組裝自定義新增到FormData的物件*/
    appendFieldsToFormData(formData){
        const field = typeof this.paramAddToField == 'function' ? this.paramAddToField() : this.paramAddToField
        field &&
            Object.keys(field).map(index=>
                formData.append(index, field[index])
            )
        return formData
    }

    /*iE選擇前驗證*/
    /*觸發beforeChoose*/
    IEBeforeChoose(e) {
        const jud = this.beforeChoose()
        jud != true && jud != undefined && e.preventDefault()
    }
    /*IE需要使用者真實點選上傳按鈕,所以使用透明按鈕*/
    /*觸發chooseFile*/
    IEChooseFile(e) {
        this.fileName = e.target.value.substring(e.target.value.lastIndexOf('\\') + 1)
        this.chooseFile(this.fileName)
        /*先執行IEUpload,配置好action等引數,然後submit*/
        this.chooseAndUpload && (this.IEUpload() !== false) &&
            document.getElementById(`ajax_upload_file_form_${this.IETag}${currentIEID}`).submit()
        e.target.blur()
    }
    /*IE處理上傳函式*/
    /*觸發beforeUpload doUpload*/
    IEUpload(e) {
        const mill = (new Date).getTime()
        const jud = this.beforeUpload(this.fileName, mill)
        if(!this.fileName || (jud != true && jud != undefined) ) {
            e && e.preventDefault()
            return false
        }
        const that = this

        /*url引數*/
        const baseUrl = this.baseUrl

        const param = typeof this.param === 'function' ? this.param(this.fileName) : this.param
        let paramStr = ''

        if (param) {
            const paramArr = []
            param['_'] = mill
            param['ie'] === undefined && (param['ie'] = 'true')
            for (const key in param) {
                if(param[key] != undefined) paramArr.push(`${key}=${param[key]}`)
            }
            paramStr = '?' + paramArr.join('&')
        }
        const targeturl = baseUrl + paramStr

        document.getElementById(`ajax_upload_file_form_${this.IETag}${currentIEID}`).setAttribute('action', targeturl)
        /*IE假的上傳進度*/
        const getFakeProgress = this.fakeProgress()
        let loaded = 0,
          count = 0

        const progressInterval = setInterval(() => {
            loaded = getFakeProgress(loaded)
            this.uploading({
                loaded,
                total: 100
            },mill)
            /*防止永久執行,設定最大的次數。暫時為30秒(200*150)*/
            ++count >= 150 && clearInterval(progressInterval)
        },200)


        /*當前上傳id*/
        const partIEID = currentIEID
        /*回撥函式*/
        window.attachEvent ?
          document.getElementById(`ajax_upload_file_frame_${this.IETag}${partIEID}`).attachEvent('onload', handleOnLoad) :
          document.getElementById(`ajax_upload_file_frame_${this.IETag}${partIEID}`).addEventListener('load', handleOnLoad)


        function handleOnLoad() {
            /*clear progress interval*/
            clearInterval(progressInterval)
            try {
                that.uploadSuccess(that.IECallback(that.dataType, partIEID))
            } catch (e) {
                that.uploadError(e)
            } finally {
                /*清除輸入框的值*/
                const oInput = document.getElementById(`ajax_upload_hidden_input_${that.IETag}${partIEID}`)
                oInput.outerHTML = oInput.outerHTML
            }
        }
        this.doUpload(this.fileName, mill)
        /*置為非空閒*/
        IEFormGroup[currentIEID] = false

    }
    /*IE回撥函式*/
    //TODO 處理Timeout
    IECallback(dataType, frameId) {
        /*回覆空閒狀態*/
        IEFormGroup[frameId] = true

        const frame = document.getElementById(`ajax_upload_file_frame_${this.IETag}${frameId}`)
        const resp = {}
        const content = frame.contentWindow ? frame.contentWindow.document.body : frame.contentDocument.document.body
        if(!content) throw new Error('Your browser does not support async upload')
        try {
            resp.responseText = content.innerHTML || 'null innerHTML'
            resp.json = JSON ? JSON.parse(resp.responseText) : eval(`(${resp.responseText})`)
        } catch (e) {
            /*如果是包含了<pre>*/
            if (e.message && e.message.indexOf('Unexpected token') >= 0) {
                /*包含返回的json*/
                if (resp.responseText.indexOf('{') >= 0) {
                    const msg = resp.responseText.substring(resp.responseText.indexOf('{'), resp.responseText.lastIndexOf('}') + 1)
                    return JSON ? JSON.parse(msg) : eval(`(${msg})`)
                }
                return {type: 'FINISHERROR', message: e.message}
            }
            throw e
        }
        return dataType == 'json' ? resp.json : resp.responseText
    }

    /*外部呼叫方法,主動觸發選擇檔案(等同於呼叫btn.click()), 僅支援現代瀏覽器*/
    forwardChoose() {
        if(this.isIE) return false
        this.commonChooseFile()
    }

    /**
     * 外部呼叫方法,當多檔案上傳時,用這個方法主動刪除列表中某個檔案
     * TODO: 此方法應為可以任意操作檔案陣列
     * @param func 使用者呼叫時傳入的函式,函式接收引數files(filesAPI 物件)
     * @return Obj File API 物件
     * File API Obj:
     * {
     *   0 : file,
     *   1 : file,
     *   length : 2
     * }
     */
    fowardRemoveFile(func) {
        this.files = func(this.files)
    }

    /*外部呼叫方法,傳入files(File API)物件可以立刻執行上傳動作,IE不支援。呼叫隨後會觸發beforeUpload*/
    filesToUpload(files) {
        if(this.isIE) return
        this.files = files
        this.commonUpload()
    }

    /*外部呼叫方法,取消一個正在進行的xhr,傳入id指定xhr(doupload時返回)或者預設取消最近一個。*/
    abort(id) {
        id === undefined ? 
            xhrList[currentXHRID].abort() :
            xhrList[id].abort()
    }

    /*判斷ie版本*/
    checkIE() {
        const userAgent = this.userAgent;
        const version = userAgent.indexOf('MSIE')
        if (version < 0) return -1

        return parseFloat(userAgent.substring(version + 5, userAgent.indexOf(';', version)))
    }

    /*生成假的IE上傳進度*/
    fakeProgress() {
        let add = 6
        const decrease = 0.3,
          end = 98,
          min = 0.2
        return (lastTime) => {
            let start = lastTime
            if (start >= end) return start

            start += add
            add = add - decrease
            add < min && (add = min)

            return start
        }
    }

    getUserAgent() {
        const userAgentString = this.props.options && this.props.options.userAgent;
        const navigatorIsAvailable = typeof navigator !== 'undefined';        
        if (!navigatorIsAvailable && !userAgentString) {
            throw new Error('\`options.userAgent\` must be set rendering react-fileuploader in situations when \`navigator\` is not defined in the global namespace. (on the server, for example)');
        }
        return navigatorIsAvailable ? navigator.userAgent : userAgentString;
    }

    componentWillMount() {
        this.userAgent = this.getUserAgent();
        this.isIE = !(this.checkIE() < 0 || this.checkIE() >= 10)
        /*因為IE每次要用到很多form組,如果在同一頁面需要用到多個<FileUpload>可以在options傳入tag作為區分。並且不隨後續props改變而改變*/
        const tag = this.props.options && this.props.options.tag
        this.IETag = tag ? tag+'_' : ''

        this._updateProps(this.props)
    }

    componentDidMount() {
    }

    componentWillReceiveProps(newProps) {
        this._updateProps(newProps)
    }

    render() {
        return this._packRender()
    }


    /*打包render函式*/
    _packRender() {
        /*IE用iframe表單上傳,其他用ajax Formdata*/
        let render = ''
        if (this.isIE) {
            render = this._multiIEForm()
        } else {
            const restAttrs = {
                accept: this.accept,
                multiple: this.multiple
            }

            render = (
                <div className={this.props.className} style={this.props.style}>
                    {this.state.before}
                    <div onClick={(e)=>{this.commonChooseFile(e)}}
                        style={{overflow:'hidden',postion:'relative',display:this.wrapperDisplay}}
                    >
                        {this.state.chooseBtn}
                    </div>
                    {this.state.middle}

                    <div onClick={(e)=>{this.commonUpload(e)}}
                        style={{
                            overflow: 'hidden',
                            postion: 'relative',
                            display: this.chooseAndUpload ? 'none' : this.wrapperDisplay
                        }}
                    >
                        {this.state.uploadBtn}
                    </div>
                    {this.state.after}
                    <input type="file" name="ajax_upload_file_input" ref="ajax_upload_file_input"
                        style={{display:'none'}} onChange={(e)=>{this.commonChange(e)}}
                        {...restAttrs}
                    />
                </div>
            )
        }
        return render
    }

    /*IE多檔案同時上傳,需要多個表單+多個form組合。根據currentIEID代表有多少個form。*/
    /*所有不在空閒(正在上傳)的上傳組都以display:none的形式插入,第一個空閒的上傳組會display:block捕捉。*/
    _multiIEForm() {
        const formArr = []
        let hasFree = false

        /* IE情況下,由於上傳按鈕被隱藏的input覆蓋,不能進行disabled按鈕處理。
         * 所以當disabledIEChoose為true(或者func返回值為true)時,禁止IE上傳。
         */
        const isDisabled =
          typeof this.disabledIEChoose === 'function' ? this.disabledIEChoose() : this.disabledIEChoose

        /*這裡IEFormGroup的長度會變,所以不能存len*/
        for(let i = 0; i<IEFormGroup.length;  i++) {
            _insertIEForm.call(this,formArr,i)
            /*如果當前上傳組是空閒,hasFree=true,並且指定當前上傳組ID*/
            if(IEFormGroup[i] && !hasFree) {
                hasFree = true
                currentIEID = i
            }
            /*如果所有上傳組都不是空閒狀態,push一個新增組*/
            (i==IEFormGroup.length-1) && !hasFree && IEFormGroup.push(true)

        }

        return (
            <div className={this.props.className} style={this.props.style} id="react-file-uploader">
                {formArr}
            </div>
        )

        function _insertIEForm(formArr,i) {
            /*如果已經push了空閒組而當前也是空閒組*/
            if(IEFormGroup[i] && hasFree) return
            /*是否display*/
            const isShow = IEFormGroup[i]
            /*Input內聯樣式*/
            const style = {
                position:'absolute',
                left:'-30px',
                top:0,
                zIndex:'50',
                fontSize:'80px',
                width:'200px',
                opacity:0,
                filter:'alpha(opacity=0)'
            }

            /*是否限制了檔案字尾,以及是否disabled*/
            const restAttrs = {
                accept: this.accept,
                disabled: isDisabled
            }

            const input =
                <input type="file" name={`ajax_upload_hidden_input_${i}`} id={`ajax_upload_hidden_input_${i}`}
                    ref={`ajax_upload_hidden_input_${i}`} onChange={(e)=>{this.IEChooseFile(e)}} onClick={(e)=>{this.IEBeforeChoose(e)}}
                    style={style} {...restAttrs}
                />

            i = `${this.IETag}${i}`
            formArr.push((
                <form id={`ajax_upload_file_form_${i}`} method="post" target={`ajax_upload_file_frame_${i}`}
                    key={`ajax_upload_file_form_${i}`}
                    encType="multipart/form-data" ref={`form_${i}`} onSubmit={(e)=>{this.IEUpload(e)}}
                    style={{display:isShow? 'block':'none'}}
                >
                    {this.state.before}
                    <div style={{overflow:'hidden',position:'relative',display:'inline-block'}}>
                        {this.state.chooseBtn}
                        {/*input file 的name不能省略*/}
                        {input}
                    </div>
                    {this.state.middle}
                    <div style={{
                        overflow:'hidden',
                        position:'relative',
                        display:this.chooseAndUpload?'none':this.wrapperDisplay
                        }}
                    >
                        {this.state.uploadBtn}
                        <input type="submit"
                            style={{
                                position:'absolute',
                                left:0,
                                top:0,
                                fontSize:'50px',
                                width:'200px',
                                opacity:0
                            }}
                        />
                    </div>
                    {this.state.after}
                </form>
            ))
            formArr.push((
                <iframe id={`ajax_upload_file_frame_${i}`}
                    name={`ajax_upload_file_frame_${i}`}
                    key={`ajax_upload_file_frame_${i}`}
                    className="ajax_upload_file_frame"
                    style={{
                        display: 'none',
                        width: 0,
                        height: 0,
                        margin: 0,
                        border: 0
                    }}
                >
                </iframe>
            ))
        }
    }

}


FileUpload.propTypes = {
    options: PT.shape({
        /*basics*/
        baseUrl: PT.string.isRequired,
        param: PT.oneOfType([PT.object, PT.func]),
        dataType: PT.string,
        chooseAndUpload: PT.bool,
        paramAddToField: PT.oneOfType([PT.object, PT.func]),
        wrapperDisplay: PT.string,
        timeout: PT.number,
        accept: PT.string,
        multiple: PT.bool,
        numberLimit: PT.oneOfType([PT.number, PT.func]),
        fileFieldName: PT.oneOfType([PT.string, PT.func]),
        withCredentials: PT.bool,
        requestHeaders: PT.object,
        /*specials*/
        tag: PT.string,
        userAgent: PT.string,
        disabledIEChoose: PT.oneOfType([PT.bool, PT.func]),
        _withoutFileUpload: PT.bool,
        filesToUpload: PT.arrayOf(PT.object),
        textBeforeFiles: PT.bool,
        /*funcs*/
        beforeChoose: PT.func,
        chooseFile: PT.func,
        beforeUpload: PT.func,
        doUpload: PT.func,
        uploading: PT.func,
        uploadSuccess: PT.func,
        uploadError: PT.func,
        uploadFail: PT.func,
        onabort: PT.func
    }).isRequired,
    style: PT.object,
    className: PT.string
};
export default FileUpload;

修改方法

  1. 將原始檔中const FileUpload = React.createClass方式建立的類改為class FileUpload extends React.Component(記得刪掉多餘的小括號)
  2. 使用yarn或npm命令安裝prop-types,在類外面指定FileUpload的propTypes屬性,FileUpload.propTypes = {xxx},並將原始檔中propTypes的屬性內容剪下到FileUpload.propTypes中,然後刪除原始檔裡面的propTypes
  3. 最後由於從方法轉化到了類,需要將原始檔中多餘的逗號刪除
  4. 將修改好的檔案引入,即可在react@16.x的版本里使用react-fileupload

這樣就完成了原始檔的修改,實現react@16.x版本的相容問題

相關文章