如何处理(绑定到状态)一个输入数组?

huangapple go评论74阅读模式
英文:

How to handle (bind to state) an array of inputs?

问题

我有一个在我的React应用程序中的表单。表单本身有一些字段,但也允许用户上传图片并对其进行评论。该表单会呈现已上传图片的列表(预览)以及每个图片的评论输入字段(ImageList 组件,它呈现多个 ImageItem)。

我将已上传图片的数据存储在Redux(使用Toolkit)存储中(files)。

files 是一个 IFile 数组:

interface IFile {
  file: { name: string; url: string; size: number };
  dto: { comment?: string };
}

组件大致如下:

// CreateForm.tsx

const { files } = useSelector((state: RootState) => state.createSpot);

return (
  <form>
    {/* 其他输入 */}
    <ImageList files={files}/>
  </form>
)
// ImageList.tsx

return (
  <div>
    {files.map((file, i) => (
      <ImageItem
        key={file.file.url}
        file={file}
        index={i}
      />
    ))}
  </div>
)

ImageItem 只是一个 img 和一个文本 input

提交表单时,我还会提交图片和相应的评论。我希望以某种方式存储这些评论,或者至少在提交时接收它们并与表单一起提交。

我尝试将每个文件的输入明确绑定到Redux存储。我创建了一个reducer来通过其唯一的URL更新文件的评论:

// createSpot.reducer.ts

updateFileComment(
  state,
  action: PayloadAction<{ url: IFile["file"]["url"]; value: string }>
) {
  const file = state.files.find((f) => f.file.url === action.payload.url);
  if (file) file.dto.comment = action.payload.value;
},

我的 ImageItem 如下所示:

// ImageItem.tsx
const ImageItem: React.FC<ImageItemProps> = ({ file }: { file: IFile }) => {
  const dispatch = useAppDispatch();

  return (
    <>
      <img src={file.file.url} alt={file.file.name} />
      <textarea
        placeholder="Comment"
        value={file.dto.comment}
        onChange={(e) => {
          dispatch(
            updateFileComment({
              url: file.file.url,
              value: e.target.value,
            })
          );
        }}
      />
    </>
  );
};

虽然它似乎按预期工作,但显然在每次键入字符时触发这样的操作是非常昂贵的。

所以有没有一种优雅和优化的方式来解决这个问题?我感觉我可能漏掉了一些明显的东西。

提前感谢您。

英文:

I have a form in my React App. The form has some fields itself, but also allows the user to upload images and comment them. The form renders a list of uploaded images (previews) and an input field for comment for each of them (ImageList component, which renders multiple ImageItems).
I store data for the uploaded images in a Redux (with Toolkit) store (files).

files is an array of IFile:

interface IFile {
  file: { name: string; url: string; size: number };
  dto: { comment?: string };
}

Components look roughly like this:

// CreateForm.tsx

const { files } = useSelector((state: RootState) =&gt; state.createSpot);

return (
  &lt;form&gt;
    {/* other inputs */}
    &lt;ImageList files={files}/&gt;
  &lt;/form&gt;
)
// ImageList.tsx

return (
  &lt;div&gt;
    {files.map((file, i) =&gt; (
      &lt;ImageItem
        key={file.file.url}
        file={file}
        index={i}
      /&gt;
    ))}
  &lt;/div&gt;
)

ImageItem is just an &lt;img/&gt; and a text &lt;input/&gt;.

Submitting the form I also submit the images and corresponding comments. I want to somehow store those comments or, at least, receive them on submit and submit with the form.

I tried explicitly binding the inputs of each file to Redux store. I created a reducer to update a file's comment by its unique url:

// createSpot.reducer.ts

updateFileComment(
  state,
  action: PayloadAction&lt;{ url: IFile[&quot;file&quot;][&quot;url&quot;]; value: string }&gt;
) {
  const file = state.files.find((f) =&gt; f.file.url === action.payload.url);
  if (file) file.dto.comment = action.payload.value;
},

My ImageItem looked like this:

// ImageItem.tsx
const ImageItem: React.FC&lt;ImageItemProps&gt; = ({ file }: { file: IFile }) =&gt; {
  const dispatch = useAppDispatch();

  return (
    &lt;&gt;
      &lt;img src={file.file.url} alt={file.file.name} /&gt;
      &lt;textarea
        placeholder=&quot;Comment&quot;
        value={file.dto.comment}
        onChange={(e) =&gt; {
          dispatch(
            updateFileComment({
              url: file.file.url,
              value: e.target.value,
            })
          );
        }}
      /&gt;
    &lt;/&gt;
  );
};

While it seems to work as intended, it is obiously very expensive to dispatch such action on each character typed.

So is there some elegant and optimized way around this issue? I feel I'm missing something plain.

Thanks in advance.

答案1

得分: 1

我提议两件事:

  1. 规范化 redux 状态(参见 https://redux.js.org/usage/structuring-reducers/normalizing-state-shape#designing-a-normalized-state)以获得类似以下的结构:
{
    images : {
        byId : {
            "image1" : {
                id : "image1",
                name : "",
                url : "",
                size: "",
                comments : ["comment1", "comment2"]
            },
        }
    },
    comments : {
        byId : {
            "comment1" : {
                id : "comment1",
                comment : "",
            },
            "comment2" : {
                id : "comment2",
                comment : "",
            },
        },
    }
}
  1. 当用户输入评论文本时,对 dispatch 进行防抖处理。

附言:如果每张图片只有一个评论,可能可以在状态中省去单独的 comments 部分。

英文:

I would propose 2 things:

  1. Normalize the redux state (see https://redux.js.org/usage/structuring-reducers/normalizing-state-shape#designing-a-normalized-state) to have something like:

    {
     images : {
         byId : {
             &quot;image1&quot; : {
                 id : &quot;image1&quot;,
                 name : &quot;&quot;,
                 url : &quot;&quot;,
                 size: &quot;&quot;,
                 comments : [&quot;comment1&quot;, &quot;comment2&quot;]
             },
         }
     },
     comments : {
         byId : {
             &quot;comment1&quot; : {
                 id : &quot;comment1&quot;,
                 comment : &quot;&quot;,
             },
             &quot;comment2&quot; : {
                 id : &quot;comment2&quot;,
                 comment : &quot;&quot;,
             },
         },
     }
    }
    
  2. Debounce dispatch when a user inputs text of comment.

P.S. if you have just one comment per image that may be ok to make state without separate comments slice

答案2

得分: 1

你可能在寻找的解决方案是对textarea元素的onChange处理程序进行去抖,或者更准确地说,对dispatch函数进行去抖,以便不会为每个单独的更改触发一个操作。

如果您不想从lodash或类似的库中导入去抖实用程序,这里是一个我使用的简单的去抖高阶函数。

const debounce = (fn, delay) => {
  let timerId;
  return (...args) => {
    clearTimeout(timerId);
    timerId = setTimeout(fn, delay, ...args);
  }
};

使用debounce高阶函数装饰dispatch函数。我还建议在第一次调度之前设置最小字符计数,例如至少3个字符。

const ImageItem: React.FC<ImageItemProps> = ({ file }: { file: IFile }) => {
  const dispatch = useAppDispatch();

  const debouncedDispatch = debounce(dispatch, 500);

  const onChangeHandler = e => {
    const { value } = e.target;

    if (value.length >= 3) {
      debouncedDispatch(updateFileComment({
        url: file.file.url,
        value,
      }));
    }
  };

  return (
    <>
      <img src={file.file.url} alt={file.file.name} />
      <textarea
        placeholder="Comment"
        value={file.dto.comment}
        onChange={onChangeHandler}
      />
    </>
  );
};
英文:

The solution you are likely looking for is to debounce the textarea element's onChange handler, or rather, the dispatch function, so you are not dispatching an action for each individual change.

If you don't want to import a debouncing utility from lodash or similar library, here's a simple debouncing Higher Order Function I use.

const debounce = (fn, delay) =&gt; {
  let timerId;
  return (...args) =&gt; {
    clearTimeout(timerId);
    timerId = setTimeout(fn, delay, ...args);
  }
};

Decorate the dispatch function with the debounce HOF. I might also suggest a minimum character count before the first dispatch, e.g. at least 3 characters.

const ImageItem: React.FC&lt;ImageItemProps&gt; = ({ file }: { file: IFile }) =&gt; {
  const dispatch = useAppDispatch();

  const debouncedDispatch = debounce(dispatch, 500);

  const onChangeHandler = e =&gt; {
    const { value } = e.target;
    
    if (value.length &gt;= 3) {
      debouncedDispatch(updateFileComment({
        url: file.file.url,
        value,
      }));
    }
  };

  return (
    &lt;&gt;
      &lt;img src={file.file.url} alt={file.file.name} /&gt;
      &lt;textarea
        placeholder=&quot;Comment&quot;
        value={file.dto.comment}
        onChange={onChangeHandler}
      /&gt;
    &lt;/&gt;
  );
};

huangapple
  • 本文由 发表于 2023年1月9日 17:06:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/75055072.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定