You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Angular的NgRx Store中存储FileList?

在NgRx Store中存储FileList/File的方案

可以在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 00:54:29