如何在Angular的NgRx Store中存储FileList?
可以在NgRx Store中存储File或FileList,但需要处理序列化限制——这也是你在Redux开发者工具中看到files: {0: {}}的核心原因:Redux DevTools默认用JSON.stringify序列化状态,而File/FileList作为浏览器原生对象,包含不可枚举、无法被JSON序列化的内部属性和原型链,导致工具无法完整展示对象内容,但实际状态中的File/FileList是正常可用的。
以下是三种具体实现方案:
方案1:直接存储原生File/FileList
如果不需要Redux DevTools完整展示文件信息,仅需业务逻辑中正常使用文件对象,可直接将FileList/File存入状态。
实现代码
定义Action
import { createAction, props } from '@ngrx/store'; export const uploadFiles = createAction( '[Files] Upload Files', props<{ files: FileList }>() );
定义Reducer
import { createReducer, on } from '@ngrx/store'; import { uploadFiles } from './file.actions'; export interface FileState { files: FileList | null; } export const initialState: FileState = { files: null, }; export const fileReducer = createReducer( initialState, on(uploadFiles, (state, { files }) => ({ ...state, files, })) );
组件中触发Action
import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { uploadFiles } from './file.actions'; @Component({ selector: 'app-file-upload', template: `<input type="file" (change)="onFileSelected($event)">`, }) export class FileUploadComponent { constructor(private store: Store) {} onFileSelected(event: Event) { const input = event.target as HTMLInputElement; if (input.files?.length) { this.store.dispatch(uploadFiles({ files: input.files })); } } }
注意:此时Redux DevTools仍会显示不完整的结构,但在组件、Effect中读取store.select(state => state.files)时,能正常获取原生File/FileList对象,用于上传等操作。
方案2:转换为可序列化的普通对象
如果需要Redux DevTools能完整展示文件信息,或要求状态完全可序列化(比如用于持久化),可以将File转换为包含关键元数据的普通对象,避免存储不可序列化的原生对象。
实现代码
工具函数:转换File为可序列化对象
// 将单个File转换为可序列化对象 export function fileToSerializable(file: File): Promise<{ name: string; type: string; size: number; lastModified: number; }> { // 若不需要文件内容,仅返回元数据即可;如需内容可通过FileReader读取dataUrl(大文件不建议) return Promise.resolve({ name: file.name, type: file.type, size: file.size, lastModified: file.lastModified, }); } // 转换FileList为可序列化对象数组 export async function fileListToSerializable(files: FileList): Promise<ReturnType<typeof fileToSerializable>[]> { const result = []; for (let i = 0; i < files.length; i++) { result.push(await fileToSerializable(files[i])); } return result; }
更新Action与Reducer
// Action export const uploadFiles = createAction( '[Files] Upload Files', props<{ files: ReturnType<typeof fileToSerializable>[] }>() ); // State与Reducer export interface FileState { files: ReturnType<typeof fileToSerializable>[] | null; } export const initialState: FileState = { files: null, }; export const fileReducer = createReducer( initialState, on(uploadFiles, (state, { files }) => ({ ...state, files, })) );
组件中转换并触发Action
onFileSelected(event: Event) { const input = event.target as HTMLInputElement; if (input.files?.length) { fileListToSerializable(input.files).then(serializableFiles => { this.store.dispatch(uploadFiles({ files: serializableFiles })); }); } }
注意:如果需要存储文件内容,可通过FileReader.readAsDataURL获取base64格式的内容,但大文件会显著增加状态体积,建议仅存储元数据,文件内容在上传时直接从原生File对象读取。
方案3:自定义Redux DevTools序列化规则
如果希望既保留原生File/FileList对象,又让Redux DevTools能展示完整信息,可以自定义DevTools的序列化函数,让它正确解析File/FileList。
实现代码
在Store配置中添加自定义序列化规则:
import { StoreModule } from '@ngrx/store'; import { fileReducer } from './file.reducer'; @NgModule({ imports: [ StoreModule.forRoot( { files: fileReducer }, { devTools: { serialize: { replacer: (key, value) => { // 处理File对象 if (value instanceof File) { return { __type: 'File', name: value.name, type: value.type, size: value.size, lastModified: value.lastModified, }; } // 处理FileList对象 if (value instanceof FileList) { const filesArr = []; for (let i = 0; i < value.length; i++) { filesArr.push({ __type: 'File', name: value[i].name, type: value[i].type, size: value[i].size, lastModified: value[i].lastModified, }); } return { __type: 'FileList', length: value.length, files: filesArr, }; } return value; }, }, }, } ), ], }) export class AppModule {}
配置后,Redux DevTools会显示包含文件元数据的结构化信息,同时状态中仍保留原生File/FileList对象,不影响业务使用。
内容的提问来源于stack exchange,提问作者jedda ler

