简体   繁体   English

子组件可以从父组件接收旧数据进行操作吗?

[英]Can the child component receive old data for manipulation from parent component?

Let's define a Tags component (a fancy checkbox group).让我们定义一个标签组件(一个漂亮的复选框组)。

const Tags = ({ tags, selectedIds, onSelectionChange }) => {
  const createClickHandler = (id) => () => {
    const newSelectedIds = xor(selectedIds, [id]);
    const selectedTags = newSelectedIds.map((id) =>
      tags.find((tag) => tag.id === id)
    );
    onSelectionChange(selectedTags);
  };

  const isSelected = (id) => selectedIds.includes(id);

  return (
    <div>
      {tags.map(({ id, text }) => (
        <button
          key={id}
          type="button"
          style={{ backgroundColor: isSelected(id) ? "gray" : "white" }}
          onClick={createClickHandler(id)}
        >
          {text}
        </button>
      ))}
    </div>
  );
};

This allows us to consume it like this:这允许我们像这样使用它:

export default function App() {
  const tags = someUsers.map((user) => ({
    id: user.id,
    text: user.name,
    value: user
  }));
  const [selectedTags, setSelectedTags] = useState([]);
  const selectedIds = selectedTags.map((tag) => tag.id);

  return (
    <div>
      <Tags
        tags={tags}
        selectedIds={selectedIds}
        onSelectionChange={setSelectedTags}
      />
    </div>
  );
}

You can test this in https://codesandbox.io/s/musing-goldwasser-nmm13您可以在https://codesandbox.io/s/musing-goldwasser-nmm13 中对此进行测试

I believe this is a decent design of a component and its props (the main focus is on the ease of consuming for the other components).我相信这是一个不错的组件及其道具设计(主要关注其他组件的易用性)。 We could perhaps remove selectedIds and add a selected flag in the tags prop, however this is beyond the question scope.我们也许可以删除selectedIds并在tags属性中添加一个selected标志,但这超出了问题范围。


My colleague on the other hand insists that this can lead to bugs and should be avoided .另一方面,我的同事坚持认为这会导致错误并且应该避免

The reasoning is as follows: if we want to update the state we must use appropriate API - setState(oldState => //data manipulation to produce new state) from useState ( https://reactjs.org/docs/hooks-reference.html#functional-updates ) Since the parent passes the state directly to the children we can't be sure that the child component filters data based on the latest data.推理如下:如果我们想更新状态,我们必须使用适当的 API - setState(oldState => //data manipulation to produce new state) useState setState(oldState => //data manipulation to produce new state) from useState ( https://reactjs.org/docs/hooks-reference. html#functional-updates ) 由于父组件直接将状态传递给子组件,我们无法确定子组件是否根据最新数据过滤数据。 Basically, this issue: https://reactjs.org/docs/faq-state.html#why-is-setstate-giving-me-the-wrong-value基本上,这个问题: https : //reactjs.org/docs/faq-state.html#why-is-setstate-giving-me-the-wrong-value

His implementation would be something along these lines:他的实现将是这样的:

const Tags = ({ tags, selectedIds, onTagClick }) => {
  const isSelected = (id) => selectedIds.includes(id);

  return (
    <div>
      {tags.map(({ id, text }) => (
        <button
          key={id}
          type="button"
          style={{ backgroundColor: isSelected(id) ? "gray" : "white" }}
          onClick={() => onTagClick(id)}
        >
          {text}
        </button>
      ))}
    </div>
  );
};

In this case, we lift the whole filtering to a parent component在这种情况下,我们将整个过滤提升到父组件

const handleTagClick = (id) =>
  setSelectedTagsIds((oldIds) => {
    if (oldIds.includes(id)) return oldIds.filter((oldId) => oldId !== id);

    return [...oldIds, id];
  });

You can test this in: https://codesandbox.io/s/kind-cdn-j7cg3您可以在以下位置进行测试: https : //codesandbox.io/s/kind-cdn-j7cg3


or another version:或其他版本:

const Tags = ({ tags, selectedIds, setSelectedIds }) => {
  const isSelected = (id) => selectedIds.includes(id);

  const handleTagClick = (id) =>
    setSelectedIds((oldIds) => {
      if (oldIds.includes(id)) return oldIds.filter((oldId) => oldId !== id);

      return [...oldIds, id];
    });

  return (
    <div>
      {tags.map(({ id, text }) => (
        <button
          key={id}
          type="button"
          style={{ backgroundColor: isSelected(id) ? "gray" : "white" }}
          onClick={() => handleTagClick(id)}
        >
          {text}
        </button>
      ))}
    </div>
  );
};

in this case, we leave the filtering to the Tags component however we pass the function which allows modification of state based on old state.在这种情况下,我们将过滤留给标签组件,但是我们传递了允许基于旧状态修改状态的函数。

You can test this code in https://codesandbox.io/s/relaxed-leftpad-y13wo您可以在https://codesandbox.io/s/relaxed-leftpad-y13wo 中测试此代码


In my opinion, this case is a completely different scenario that React docs never specifically address.在我看来,这个案例是一个完全不同的场景,React 文档从未专门解决过这个问题。

As far as I understand React rendering engine will always ensure that the child nodes get the newest props so a situation where a child component filters (or does other manipulation) with stale data is simply impossible.据我了解,React 渲染引擎将始终确保子节点获得最新的道具,因此子组件过滤(或进行其他操作)使用陈旧数据的情况是根本不可能的。 I would like to quote some docs for this however I haven't found any information on this specific situation.我想为此引用一些文档,但是我没有找到有关这种特定情况的任何信息。

All I know is:我所知道的是:

  • with my many years of React experience I have yet to encounter any bugs with my approach凭借我多年的 React 经验,我的方法还没有遇到任何错误
  • other 3rd party libraries use the same design其他 3rd 方库使用相同的设计

Can someone (with deep React knowledge) provide more insight why I am correct or wrong in this instance?有人(具有深厚的 React 知识)能否提供更多见解,为什么我在这种情况下是对的或错的?

For you to notice the difference, you could simulate a delay in the update of the selection.为了让您注意到差异,您可以模拟选择更新的延迟。 ie, the user of your component needs to do some async stuff when selecting a tag即,您的组件的用户在选择标签时需要做一些异步的事情

  const [selectedTags, setSelectedTags] = useState([]);
  const selectedIds = selectedTags.map((tag) => tag.id);
  const asyncSelection = (tags) => {
    setTimeout(() => setSelectedTags(tags), 1000);
  };
  ...
      <Tags
        tags={tags}
        selectedIds={selectedIds}
        onSelectionChange={asyncSelection}
      />

You can try here clicking each option one by one, and when all updates run, not all options will be selected (which is not expected).您可以尝试在此处逐个单击每个选项,当所有更新运行时,并非所有选项都将被选中(这不是预期的)。 Since the component didn't render immediately, the handler was not updated and the second click is executed with an old state, therefore, the sequences of the selections are not correctly synced.由于组件没有立即渲染,处理程序没有更新,第二次点击以旧状态执行,因此,选择的序列没有正确同步。 Of course, this is a contrived example, but it could be the case in a very heavy UI, that 2 clicks happen without the Tags component being rerendered.当然,这是一个人为的示例,但在非常繁重的 UI 中可能会出现这种情况,即在没有重新渲染标签组件的情况下发生 2 次点击。

On the other hand, letting the user have more control over the state, it would be possible to handle this kind of situation.另一方面,让用户对状态有更多的控制权,可以处理这种情况。 Once again, if you try here clicking each option one by one, in the end all will be selected as expected再一次,如果您尝试在此处逐个单击每个选项,最终将按预期选择所有选项

  const handleTagClick = (id) => {
    setTimeout(() => {
      setSelectedTagsIds((oldIds) => {
        if (oldIds.includes(id)) return oldIds.filter((oldId) => oldId !== id);

        return [...oldIds, id];
      });
    }, 1000);
  };

As far as I can see the things you're talking about are two different issues.据我所知,你所说的事情是两个不同的问题。

  1. If the props of a child are updated it will trigger a rerender of that component.如果子组件的 props 更新,它将触发该组件的重新渲染。 There are edge cases where that gets tricky like with useRef or some callbacks but that's besides the point.有些边缘情况会像 useRef 或一些回调一样变得棘手,但这不是重点。 The filtering and things you're doing will never be different or affected in any way as long as it's dependent on the props changing and if the component receives new props it will rerender the child and reapply the filters without any issues.只要依赖于 props 的变化,过滤和你正在做的事情永远不会不同或受到任何影响,如果组件收到新的 props,它将重新渲染子项并重新应用过滤器,没有任何问题。
  2. The second issue is sort of different from the first one.第二个问题与第一个问题有些不同。 What could happen is that the tag state is repeatedly updated and only one of those states are passed to the child, that's what you want to avoid.可能发生的情况是标签状态被重复更新,并且只有这些状态之一被传递给孩子,这是您想要避免的。 Essentially you have to make sure the parent state has actually updated correctly before passing it to a child.本质上,在将父状态传递给子级之前,您必须确保父状态实际上已正确更新。 The child will always update and filter and do everything correctly exactly on what's passed to it, your problem here is making sure you're actually passing the correct props.孩子将始终根据传递给它的内容进行更新和过滤并正确执行所有操作,您的问题是确保您实际上传递了正确的道具。

There's no need to move anything to the parent component, the child will update itself correctly when the parent tag state updates and passes that new state to the child, the only thing you have to look out for here is that you don't update the parent state multiple times and cause https://reactjs.org/docs/faq-state.html#why-is-setstate-giving-me-the-wrong-value and end up passing the wrong props to the child.不需要将任何东西移动到父组件,当父标签状态更新并将新状态传递给子组件时,子组件将正确更新自己,这里唯一需要注意的是不要更新父状态多次并导致https://reactjs.org/docs/faq-state.html#why-is-setstate-giving-me-the-wrong-value并最终将错误的道具传递给孩子。 For example if someone spams the group checkbox on and off quickly.例如,如果有人快速打开和关闭组复选框。 Even then if you pass the wrong props to the child the child will still update itself and reapply the filtering and everything, just on the wrong props.即使这样,如果您将错误的道具传递给孩子,孩子仍然会自我更新并重新应用过滤和所有内容,只是在错误的道具上。

React will do its batch state update on something like a 10ms interval (I'm not exactly sure how long it is). React 会以 10 毫秒的间隔(我不确定它是多长时间)进行批量状态更新。 So if someone clicks the checkbox and it updates the tag state at 6/10ms it will rerender the component 4ms later when it does the batch state update.因此,如果有人单击复选框并在 6/10 毫秒时更新标签状态,它将在 4 毫秒后进行批量状态更新时重新渲染组件。 If hypothetically during those 4ms you click it off again, or if straight after it updated you click it off again, it's where weird things start happening.如果假设在这 4 毫秒内您再次单击它,或者在它更新后立即再次单击它,那么奇怪的事情就会开始发生。 This is why if you use the increment counter (like in that example) multiple times it won't actually increase it by 3, only by 1, since the code will execute all 3 times on 0 before it did the state update 10ms later.这就是为什么如果您多次使用增量计数器(如该示例中),它实际上不会将其增加 3,只会增加 1,因为代码将在 0 上执行所有 3 次,然后在 10 毫秒后进行状态更新。 That being said even if you spam that checkbox on and off all the time (spamming the tag array state), I don't see any way how it would go out of sync, every 10ms it will update and rerender the child and the moment you stop spamming it the child will finally rerender on the last current parent state and be correct.话虽如此,即使您一直打开和关闭该复选框(垃圾标签阵列状态),我也看不出它会如何不同步,每 10 毫秒它就会更新并重新渲染孩子和那一刻你停止向它发送垃圾邮件,孩子最终会在最后一个当前父状态上重新渲染并且是正确的。 I don't see how you could really have an issue with that in your example.我不明白在你的例子中你怎么会真的有问题。 It could cause an issue with something like a counter but not with your tags because of the fact that a counter is a cumulative addition on previous values whereas your tags is a static set of values (that is the key difference).它可能会导致诸如计数器之类的问题,但不会导致您的标签出现问题,因为计数器是先前值的累积加法,而您的标签是一组静态值(这是关键区别)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM