【问题标题】:React.js - Functional component with useState hook doesn't re-render as expectedReact.js - 带有 useState 钩子的功能组件不会按预期重新呈现
【发布时间】:2019-07-23 18:03:53
【问题描述】:

这是一个有点开放式的问题,因为我确信我处理这个问题的方式是不正确的。但我很好奇为什么 React 没有像我预期的那样重新渲染。我怀疑这与 useState 钩子与功能组件配对的行为有关。

代码在这个CodeSandbox链接中,代码如下:

function App() {
  var foosList = ["foo1", "foo2", "foo3"];

  const [selectedFoo, setSelectedFoo] = useState(-1);
  const isSelected = i => i === selectedFoo;

  return (
    <div className="App">
      <FoosWrapper>
        <TitleSpan>Foos</TitleSpan>
        <ListGroup>
          {foosList.map((fooItem, i) => (
            <ListGroupItem
              key={fooItem}
              active={isSelected(i)}
              onClick={() => setSelectedFoo(i)}
            >
              {fooItem}
            </ListGroupItem>
          ))}
        </ListGroup>
      </FoosWrapper>
      <BarsWrapper>
        <TitleSpan>Bars</TitleSpan>
        <Bars foo={foosList[selectedFoo]} />
      </BarsWrapper>
    </div>
  );
}

const Bars = props => {
  const [pendingBar, setPendingBar] = useState("");
  const [newBars, setNewBars] = useState([]);

  const keyPress = e => {
    if (e.key === "Enter") {
      save(pendingBar);
    }
  };

  const save = bar => {
    newBars.push(bar);
    setNewBars([...newBars]);
  };

  return (
    <div>
      <ListGroup>
        <ListGroupItem key={props.foo}>{props.foo}</ListGroupItem>
      </ListGroup>
      <ListGroup>
        {newBars.map(newBar => (
          <ListGroupItem key={newBar}>{newBar}</ListGroupItem>
        ))}
      </ListGroup>
      <InputGroup>
        <Input
          placeholder="Add a bar"
          onChange={e => setPendingBar(e.target.value)}
          onKeyPress={keyPress}
        />
      </InputGroup>
    </div>
  );
};

广义上讲,有两个逻辑小部件:FoosBarsFoos 在左边,Bars 在右边。我想让用户选择一个“foo”,并在右侧显示与该“foo”相关联的不同条形列表。用户可以将新条添加到每个相应的“foo”。可以认为foobar 有父关系。

Bars 组件维护用户添加的条形列表。我的期望是Bars 组件会在选择新的 foo 时重新渲染内部的newBars 集合。但是,无论在左侧选择什么“foo”,该状态都会一直存在并显示。

这看起来很奇怪,但也许我没有以正确的方式考虑 React 功能组件和钩子。很想了解为什么会存在这种行为,并且其他人很想听听更有意义的建议方法。

非常感谢任何见解!

【问题讨论】:

  • "可以认为 foo 与 bar 有父关系。"这种关系在您的代码中没有反映。您有 2 个用于 foos 和用于 bar 的单独数组。您的期望是正确的 Bars 组件在您更改 foo 时重新渲染,但 useState 不会重新初始化新状态(因为这不是状态)。如果您想重新创建状态,请使用键 codesandbox.io/s/foos-and-bars-hierarchy-example-wqi0s 但这会在 foo 更改时擦除条。如果您想真正了解 foo 和 bar 之间的关系,或者将 bar 提升到 App 状态。
  • ^^^ 正确链接演示了密钥用法 codesandbox.io/s/foos-and-bars-hierarchy-example-rvoqq 尚未保存更改 :)

标签: javascript reactjs react-hooks


【解决方案1】:

如果您希望反映层次结构,请初始化 bar 的状态,使其与您的 foos 的状态同步。现在,您的Bars 是一个独立于App 保持其自身状态的单个组件。以下是我处理这种特殊关系的方式。

function App() {
  const foos = useMemo(() => ["foo1", "foo2", "foo3"], []);
  const [bars, setBars] = useState(foos.map(() => []));
  const [selectedIndex, setSelectedIndex] = useState(0);
  const setBar = useCallback(
    bar => {
      setBars(bars => Object.assign(
        [...bars],
        { [selectedIndex]: bar }
      ));
    },
    [setBars, selectedIndex]
  );
  const isSelected = useCallback(
    index => index === selectedIndex,
    [selectedIndex]
  );
  const foosList = useMemo(
    () => foos.map((foo, index) => (
      <ListGroupItem
        key={foo}
        active={isSelected(index)}
        onClick={() => setSelectedIndex(index)}
      >
        {foo}
      </ListGroupItem>
    )),
    [foos, isSelected, setSelectedIndex]
  );

  return (
    <div className="App">
      <FoosWrapper>
        <TitleSpan>Foos</TitleSpan>
        <ListGroup>{foosList}</ListGroup>
      </FoosWrapper>
      <BarsWrapper>
        <TitleSpan>Bars</TitleSpan>
        <Bars
          foo={foos[selectedIndex]}
          bars={bars[selectedIndex]}
          setBars={setBar}
        />
      </BarsWrapper>
    </div>
  );
}

function Bars({ foo, bars, setBars }) {
  const [pendingBar, setPendingBar] = useState("");
  const barsList = useMemo(
    () => bars.map(bar => (
      <ListGroupItem key={bar}>{bar}</ListGroupItem>
    )),
    [bars]
  );
  const save = useCallback(
    bar => { setBars([...bars, bar]); },
    [setBars, bars]
  );
  const change = useCallback(
    event => { setPendingBar(event.target.value); },
    [setPendingBar]
  );
  const keyPress = useCallback(
    event => { if (event.key === "Enter") save(pendingBar); },
    [pendingBar, save]
  );

  return (
    <div>
      <ListGroup>
        <ListGroupItem key={foo}>{foo}</ListGroupItem>
      </ListGroup>
      <ListGroup>{barsList}</ListGroup>
      <InputGroup>
        <Input
          placeholder="Add a bar"
          onChange={change}
          onKeyPress={keyPress}
        />
      </InputGroup>
    </div>
  );
}

我可能对记忆挂钩有点过火了,但这至少应该让您了解可以记忆什么以及如何记忆各种值。

请记住,第二个参数是依赖关系数组,它决定了记忆值是重新计算还是从缓存中检索。通过引用检查钩子的依赖关系,很像PureComponent

我还选择将selectedIndex 初始化为0,以避免在未选择foo 时解决如何处理Bars 的渲染函数的问题。我会把它作为练习留给你。

【讨论】:

    【解决方案2】:

    如果要显示所选 foo 的条形图,则需要相应地构建“条形图”数据。执行此操作的最简单方法是将“条”数据保持为对象格式而不是数组格式。您可以编写如下代码。

    const Bars = props => {
        const [pendingBar, setPendingBar] = useState("");
        const [newBars, setNewBars] = useState({});
        const { foo } = props;
    
        const keyPress = e => {
            if (e.key === "Enter") {
                save(pendingBar);
            }
        };
    
        const save = bar => {
            if (!foo) {
                console.log("Foo is not selected");
                return;
            }
            const bars = newBars[foo] || [];
            bars.push(bar);
            setNewBars({
                ...newBars,
                [foo]: [...bars]
            });
        };
    
        return (
            <div>
                <ListGroup>
                    <ListGroupItem key={props.foo}>{props.foo}</ListGroupItem>
                </ListGroup>
                <ListGroup>
                    {newBars[foo] &&
                        newBars[foo].map(newBar => (
                            <ListGroupItem key={newBar}>{newBar}</ListGroupItem>
                        ))}
                </ListGroup>
                <InputGroup>
                    <Input
                        placeholder="Add a bar"
                        onChange={e => setPendingBar(e.target.value)}
                        onKeyPress={keyPress}
                    />
                </InputGroup>
            </div>
        );
    };
    

    【讨论】:

      猜你喜欢
      • 2021-03-05
      • 1970-01-01
      • 2020-10-29
      • 2023-03-25
      • 2020-01-26
      • 2020-06-27
      • 2021-11-11
      • 1970-01-01
      • 2021-09-21
      相关资源
      最近更新 更多