React.js-带有useState挂钩的功能组件未按预期重新呈现

时间:2019-07-17 15:25:43

标签: javascript reactjs react-hooks

这是一个开放式问题,因为我确定我要解决的方法不正确。但是我很好奇为什么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>
  );
};

广泛地,有两个逻辑小部件:FoosBars。左侧的Foos,右侧的Bars。我想让用户选择一个“ foo”,并且在右侧显示了与所述“ foo”相关联的不同条形列表。用户可以向每个相应的“ foo”添加新的小节。可以想到foobar有父级关系。

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

这看起来很奇怪,但是也许我没有想到以正确的方式来处理React功能组件和钩子。很想了解为什么会出现这种行为,而其他人则希望听到提出的更有意义的方法。

任何见识都将受到赞赏!

2 个答案:

答案 0 :(得分:0)

如果要反映层次结构,请初始化酒吧的状态,以便它与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>
  );
}

Edit foos and bars hierarchy example

我可能对备忘录挂钩有些过分,但这应该使您至少可以了解如何以及如何记忆各种值。

请记住,第二个参数是依赖项数组,它确定是重新计算已存储的值还是从高速缓存中检索已存储的值。挂钩的依赖关系通过引用进行检查,就像PureComponent一样。

我还选择将selectedIndex初始化为0,以避免解决未选择Bars时如何处理foo的呈现功能的问题。我会将其留给您练习。

答案 1 :(得分:0)

如果要显示所选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>
    );
};
相关问题