import React, { useContext, createContext, useRef, useState } from 'react'; import { fileValidator } from './upload/file'; import type { IUploadContextProps, IUploadContextState } from './types/context'; import type { ICnOSSUploadFile, ICnUploadFile } from './types/upload-file'; import { useControlled } from '../../hooks/use-controlled'; import { CN_UPLOAD_LIMIT_NUM } from './constant'; const CnUploadStateContext = createContext( {} as unknown as IUploadContextState, ); export const useUploadState = () => { return useContext(CnUploadStateContext); }; const emptyArray = []; export const CnUploadContext = (props: IUploadContextProps) => { const { defaultValue: defaultValueProps, value: valueProps, onChange: onChangeProps, onError, onRemove, limit = CN_UPLOAD_LIMIT_NUM, readOnly = false, children, useDetailValue, ...restProps } = props; // 管理请求实例,当删除正在上传中的文件时,结束请求 const uploadReqListRef = useRef void }>>({}); // 管理未上传完成的文件列表状态 const [innerValue, setInnerValue] = useState([]); const innerValueRef = useRef([]); innerValueRef.current = innerValue || []; // 管理已上传完成的文件列表状态 const [_outerValue, setOuterValue] = useControlled< ICnOSSUploadFile[], (value: ICnOSSUploadFile[], currentFile?: ICnOSSUploadFile) => void >(props); const outerValue = Array.isArray(_outerValue) ? _outerValue : emptyArray; // 由于已上传完成的状态外部管理,未完成的状态内部管理,无法保证顺序,故通过临时状态存储用户所选文件顺序 const fileSortRef = useRef>( outerValue?.map((item) => item.key) || [], ); // 通过借助 ref 临时存储文件完成文件合并 const outerValueRef = useRef(outerValue || []); outerValueRef.current = outerValue || []; const fileList = innerValueRef.current.filter((item) => { const hasUploaded = outerValueRef.current.some( ({ key }) => item.key === key, ); return !hasUploaded; }); if (fileList.length !== innerValueRef.current.length) { setInnerValue(fileList); } /** * 合并内部未完成的文件状态和外部已完成上传的文件状态 */ const getValue = () => { const list = outerValueRef.current.map((item) => item); innerValueRef.current.forEach((item) => { if (list.some(({ key }) => key && key === item.key)) return; list.push(item); }); return list.sort((a, b) => { const aIndex = fileSortRef.current.findIndex((item) => { if (typeof item === 'string') return item === a.key; return item === a.originFile; }); const bIndex = fileSortRef.current.findIndex((item) => { if (typeof item === 'string') return item === b.key; return item === b.originFile; }); return aIndex - bIndex; }); }; const _onChange = ( newFileList: ICnUploadFile[], currentFile: ICnUploadFile, ) => { // 当文件上传完成、或文件被删除时,同步更新内部状态 if (currentFile.status === 'done' || currentFile.status === 'remove') { setInnerValue((prev) => { return prev.filter((item) => item.key !== currentFile.key); }); } if (!newFileList.length) { setOuterValue(newFileList); return; } if (useDetailValue) { setOuterValue(newFileList, currentFile); return; } /** * useDetailValue === false 场景只暴露必要的数据 */ const _fileList = newFileList.map((item) => { return { key: item.key, name: item.name, size: item.size, url: item.url, status: item.status, }; }); setOuterValue(_fileList, currentFile); }; const limited = getValue().length >= limit; return ( { const uploadFileList = innerValueRef.current.map((item) => item); _fileList.forEach((item) => { uploadFileList.push(item); fileSortRef.current.push(item.originFile); restProps.onProgress?.(item); }); setInnerValue(uploadFileList); }, onUploadProcess: (currentFile: ICnUploadFile) => { const uploadFileList = innerValueRef.current.map((item) => item); const fileIndex = uploadFileList.findIndex((file) => { if (file.key) return file.key === currentFile.key; return file.originFile === currentFile.originFile; }); if (fileIndex >= 0) { uploadFileList[fileIndex] = currentFile; } else { uploadFileList.push(currentFile); } restProps.onProgress?.(currentFile); // 同步更新 ref,确保 onError 能立即获取到最新的文件列表(包含 key) innerValueRef.current = uploadFileList; setInnerValue(uploadFileList); }, onUploadSuccess: (currentFile: ICnUploadFile) => { fileSortRef.current = fileSortRef.current.map((item) => { if (item !== currentFile.originFile) return item; return currentFile.response.key; }); innerValueRef.current = innerValueRef.current.filter( (item) => item.key !== currentFile.key, ); const uploadFileList = outerValueRef.current.map((item) => item); uploadFileList.push(currentFile); restProps.onProgress?.(currentFile); _onChange(uploadFileList, currentFile); }, limited, fileValidator, onRemove: async (_currentFile: ICnUploadFile) => { const currentFile: ICnUploadFile = { ..._currentFile, status: 'remove', }; uploadReqListRef.current[currentFile.key]?.abort(); delete uploadReqListRef.current[currentFile.key]; if (onRemove) { const result = await onRemove(currentFile); if (result === false) return; } const uploadFileList = outerValueRef.current.filter( (item) => item.key !== currentFile.key, ); _onChange(uploadFileList, currentFile); }, onError: (currentFile) => { delete uploadReqListRef.current[currentFile.key]; onError?.(currentFile); const uploadFileList = innerValueRef.current.filter((item) => { return currentFile.key ? item.key !== currentFile.key : item.originFile !== currentFile.originFile; }); setInnerValue(uploadFileList); }, setUploadReq: (key, reqInstance) => { uploadReqListRef.current[key] = reqInstance; }, }} > {children} ); };