@ws-serenity/use-data-transfer
v0.2.6
Published
Hook for handling remote and local files by unified API
Downloads
32
Readme
UseDataTransfer
Хук для предоставления единого api для удаленных и локальных файлов.
Валидация: поддерживает api для валидации файлов.
Гибкость: поддерживает любые представления для описаний файлов, ошибок валидации,
и конфигураций для скачивания файлов.
Кэширование: кэширует файлы, полученные для предпросмотра, до момента размонтирования компонента.
Состояние загрузки: предоставляет переменную,
отражающую текущий процент загрузки каждого из файлов на сервер.
Отложенная синхронизация: имеет возможность отложить синхронизацию состояния с сервером,
до вызова соответствующего метода.
Асинхронность: методы хука не блокируют поток.
Быстрый страт
Обобщения
В начале, нужно определить типы данных, используемые хуком.
TDataImage
TDataImage
(image от en - образ, а не от en - изображение) определяет представление,
в котором хранится информация о файле (но не сам файл).
Тип обязан иметь поле id: string
, его можно или указать вручную (допускается ⚠️),
или расширить имеющийся тип DataImage
(оптимально ✅).
Рекомендуется разбивать тип на type Additions
+ type CustomData = DataImage + Additions
,
тогда, можно будет использовать типизированный Object.assign<DataImage, Additions>(...)
,
для создания TDataImage
.
import { UseDataTransfer } from "@ws-serenity/use-data-transfer";
// Пусть каждый образ хранит название вложения и его размер
export type CustomDataImageAdditions = {
name: string
size: string
}
export type CustomDataImage = CustomDataImageAdditions & UseDataTransfer.DataImage
Хук расширяет интерфейс TDataImage
до HookDrivenDataImage<TDataImage>
.
const {add, images} = useDataTransfer<CustomDataImage /*..*/>(/*..*/);
add(file);
const hookDrivenImage = images[0];
console.log(hookDrivenImage.state); // Состояние данных, может принимать значения local | remote | uploading
const blob = hookDrivenImage.preview().blob; // Метод для скачивания данных в ОЗУ
hookDrivenImage.download(); // Метод для скачивания данных в ПЗУ
hookDrivenImage.remove(); // Метод для удаления данных
Таким образом, TDataImage не должен содержать следующие зарезервированные под расширение поля или методы:
state
, remove
, preview
, download
.
TError
TError
определяет представление ошибок, используемых при валидации локальных файлов.
Может быть представлен любым значением типа Record<any, any>
.
TError
, аналогично, будет расширен до HookDrivenValidationError<TError>
.
const {add, images, errors} = useDataTransfer<CustomDataImage, CustomError /*..*/>(/*..*/);
add(file);
const hookDrivenError = errors[0];
console.log(hookDrivenError.id); // Уникальный id ошибка
hookDrivenError.resolve(); // Уадлить ошибку из коллекции
TError
не должен содержать поля или методы: id
, resolve
.
Работа с Id
Хук создает уникальный идентификатор для каждого из добавленных образов. Образы, полученные удаленно, должны иметь свой id, как часть контракта, на котором построена работа с сервером. Когда локальный файл будет успешно отправлен на сервер, его id будет подменен на id присвоенный сервером.
Ошибки валидации, в отличие от образов, не могут прийти с сервера, их id на протяжении всего жизненного цикла контролируется хуком. Поэтому дляTDataImage
id
это необходимое поле, а дляTError
расширяемое хуком.
TPreviewConfig и TDownloadConfig
TPreviewConfig
и TDownloadConfig
определяют конфиги, с которыми могут быть запрошены удаленные файлы.
Могут быть представлены чем угодно, в том числе и отсутствовать,
те, иметь значение, установленное по умолчанию, undefined
.
Например:
export type CustomPreviewConfig = 'original' | 'thumbnail'
export type CustomDownloadConfig = {
width: number
height: number
}
// Также, может использоваться один тип
export type AnotherDownloadConfig = CustomPreviewConfig
Вспомогательные обработчики
Для того чтобы хук мог работать с TDataImage
, ему необходимо предоставить маппер с сигнатурой
(file: File, image: DataImage) => TDataImage
, называемый genericDataImageBuilder
.
// Для определенного выше `CustomDataImage`, может быть представлен так
const customDataImageBuilder = (file: File, image: DataImage) => {
return Object.assign<DataImage, CustomDataImageAdditions>(image, {
name: file.name,
size: file.size
})
}
Хук использует кэширование для каждого hookDrivenImage.preview(previewConfig)
запроса.
Кэшируется не только файл, но и конфиг запроса. Так как хук ничего не знает об используемом
TPreviewConfig
, то ему нужен вспомогательный обработчик cacheWorker: CacheWorker
.
export type CacheWorker<TPreviewConfig> = {
search: (cache, config?) => CachedEntity | undefined // 1
getLocalFileQualityConfig: (file: File) => TPreviewConfig // 2
}
Для разрешения случаев, когда вместо запроса на сервер, можно предоставить кэшированный файл от
запроса с другим конфигом, используется search
(1) метод. Например, такое поведение может быть
полезно, когда кэширован оригинал изображения, и необходима версия с меньшим разрешением.
Если search
находит подходящую сущность, то он возвращает её, в ином случае - undefined
.
При добавлении локального файла, необходимо знать какому TPreviewConfig
он соответствует.
Для этого используется метод getLocalFileQualityConfig
(2).
Для общего случая TPreviewConfig = undefined
, cacheWorker
может быть таким:
const cacheWorker: CacheWorker<undefined> = {
getLocalFileQualityConfig: () => undefined,
search: (entities) => entities[0]
}
Провайдер для удаленных файлов
Для работы с сервером хуку требуется реализация абстрактного класса RemoteDataProvider
.
export abstract class RemoteDataProvider<
TDataImage extends DataImage = DataImage,
TPreviewConfig = undefined,
TDownloadConfig = undefined
> {
// Отменяет выполняющуюся загрузку файла на сервер
abstract abort(imageId: string): Promise<void>
// Возвращает описания всех файлов на сервере
abstract getAllDataImages(): Promise<TDataImage[]>
// Скачивание файлов
abstract preview(imageId: string, config: TPreviewConfig): Promise<Blob>
abstract download(imageId: string, config: TDownloadConfig): Promise<void>
// Изменение состояния на сервере
abstract commit(
config: {
// Commit конфиг содержит коллекцию из id файлов, которые необходимо удалить
toDelete?: string[],
// и коллекцию { локальный id (присвоенный хуком) -> файл }, которую необходимо загрузить
toUpload?: Record<string, Blob>
},
// Событие для отслеживания состояния загрузки
onProgress?: (event: ProgressEvent) => void
// Обязано вернуть коллекцию { локальный id (присвоенный хуком) -> удаленный id (присвоенный сервером) }
): Promise<Record<string, string>>
}
Использование хука
Определив все обобщения, подготовив вспомогательные обработчики и реализацию RemoteDataProvider
,
можно использовать хук.
const {
images, // Все образы
errors, // Все ошибки
resolve, // Удалить ошибку по id
remove, // Удалить файл по id
add, // Добавить коллекцию локальных файлов
dataTransferProgress, // Коллекция { локальный id -> доля отправленных данных }
isLoading, // true до обработки remoteDataProvider.getAllImages()
commit // Синхронизирует изменения с сервером
} = useDataTransfer<CustomDataImage, CustomError>(
remoteDataProvider,
customDataImageBuilder,
cacheWorker
)
Конфигурация
Необязательный четвертый параметр useDataTransfer
- конфигурация, дополнительно
настраивающая поведение хука.
| Параметр | Тип | По умолчанию | Описание |
|----------------------------------|----------------------|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| readonly | boolean | false | Устанавливает поведение "только для чтения". |
| strictReadonly | boolean | true | Если true
, то, при попытке изменения состояния хука, будет вызвано исключение. |
| dataTransferBehaviour | DataTransferBehavior | execute-on-commit | Если execute-on-commit
, то синхронизация с сервером будет произведена после вызова commit()
. Если execute-on-action
, то синхронизация с сервером будет происходить после каждого изменения, а вызов commit()
приведет к исключению. |
| dataTransferAccumulationInterval | number | 500 | Период в миллисекундах, с которым будет обновляться состояние dataTransferProgress
при наличии активных загрузок. |
| validators | ValidationFunction[] | | Коллекция правил для валидации файлов. |
| onErrorResolved | ActionCallback | | Коллбэк для resolve()
. |
| onImageRemoved | ActionCallback | | Коллбэк для remove()
. |
| onImageAdded | ImageCallback | | Коллбэк для add()
, вызывается отдельно для каждого файла. |
Валидация
Функция валидации имеет следующую сигнатуру:
export type Context = {
images: HookDrivenDataImage[]
}
// В случае, если ошибок нет, то функция ничего не возвращает
export type ValidationFunction = (file: File, ctx: Context) => TError | void
Функции валидации будут вызваны в порядке объявления, как только валидатор вернет ошибку, проверка остановится. Например, если файл не удовлетворяет функциям валидации А и Б (объявленным в соответствующем порядке), то в errors попадет только ошибка из валидатора А.
Ограничения
Изменение параметров хука, отличных от config
, в реальном времени может привести к некорректному поведению.