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

如何将React Hook Form Controller获取的文件值存入Redux并复用

实现方案

1. 修正现有文件字段的RHF绑定逻辑

原生input[type=file]存在安全限制,不支持外部手动赋值value属性,你当前直接把RHF的value传给组件会触发控制台报错,先调整Controller代码:

<Controller
  name={name}
  control={control}
  defaultValue={null}
  render={({ field: { onChange, onBlur, ref } }) => (
    <ReactHookFormSelector
      selector={selector}
      type={type}
      multiline={multiline}
      inputRef={ref}
      onBlur={onBlur}
      onChange={(event) => {
        const selectedFiles = event.target.files;
        console.log(selectedFiles);
        onChange(selectedFiles?.length ? selectedFiles : null);
        // 如果需要支持重复选择同一个文件,打开下方注释
        // event.target.value = ''
      }}
    />
  )}
/>

不要给file类型的输入框传value属性,只需要在用户选文件时把拿到的FileList交给RHF托管即可。

2. 提交时将文件存入Redux

Redux默认的序列化检查会对File/FileList类实例抛出警告,需要先配置忽略规则,再在表单提交回调中dispatch存值逻辑。

Redux侧配置(Redux Toolkit为例)

export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        // 替换成你自己的存文件action type和store路径
        ignoredActions: ['form/saveFormFiles'],
        ignoredPaths: ['form.fileCache.fileList']
      }
    })
});

表单提交逻辑

const onSubmit = (formValues) => {
  // 替换成你实际的文件字段名
  const { uploadFile, ...restValues } = formValues;
  dispatch(
    saveFormFiles({
      fileList: uploadFile,
      relatedFormValues: restValues
    })
  );
};

3. 在另一个file输入组件中复用已存文件

浏览器安全规则禁止直接给input[type=file]赋值,所以要通过「自定义视觉展示层+透明原生输入框覆盖」的方式实现回显效果,不影响原生文件选择功能:

import { useSelector } from 'react-redux';
import { TextField, InputAdornment, IconButton, Typography } from '@mui/material';
import ClearIcon from '@mui/icons-material/Clear';

// 从Redux读取之前存的文件和关联值
const storedFileCache = useSelector(state => state.form.fileCache);

<Controller
  name="secondFileField"
  control={secondFormControl}
  defaultValue={storedFileCache?.fileList || null}
  render={({ field: { onChange, onBlur, ref, value } }) => {
    // 优先展示当前新选的文件,没有则用Redux存的历史文件
    const showFiles = value || storedFileCache?.fileList;
    const fileNameText = showFiles?.length
      ? Array.from(showFiles).map(file => file.name).join(', ')
      : '请选择文件';

    return (
      <TextField
        type="file"
        fullWidth
        label="上传文件"
        onBlur={onBlur}
        inputRef={ref}
        onChange={(e) => {
          const newFiles = e.target.files;
          onChange(newFiles?.length ? newFiles : null);
        }}
        InputProps={{
          startAdornment: (
            <InputAdornment position="start" sx={{ maxWidth: '75%', overflow: 'hidden' }}>
              <Typography variant="body2" noWrap>{fileNameText}</Typography>
            </InputAdornment>
          ),
          endAdornment: showFiles ? (
            <InputAdornment position="end">
              <IconButton
                size="small"
                onClick={(e) => {
                  e.stopPropagation();
                  onChange(null);
                }}
              >
                <ClearIcon fontSize="small" />
              </IconButton>
            </InputAdornment>
          ) : null
        }}
        // 将原生file输入框设为透明覆盖在表层,既保留原生选择能力,又隐藏默认的文件名显示
        inputProps={{
          sx: {
            opacity: 0,
            position: 'absolute',
            width: '100%',
            height: '100%',
            cursor: 'pointer'
          }
        }}
      />
    )
  }}
/>

如果你不需要在输入框上展示已存的文件名,只需要提交时带上之前的文件,直接从Redux取文件合并到新表单的提交参数里即可,不需要修改输入框代码。

注意:不要尝试通过DOM操作直接修改file输入框的value属性,所有现代浏览器都会拦截该操作并抛出InvalidStateError,自定义视觉层是目前唯一兼容的实现方案。

额外提示

  • 单页内跳转场景下,Redux存File对象可以正常使用;如果需要支持页面刷新后保留文件,选文件后用FileReader把文件转成base64字符串或ArrayBuffer存储,回显时再转回File对象即可。
  • 多文件字段场景下,按字段名做key分区存储,避免不同字段的文件数据互相覆盖。

内容的提问来源于stack exchange,提问作者Gulshan Jangra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 12:01:04