簡體   English   中英

React.js-帶有useState鈎子的功能組件未按預期重新渲染

[英]React.js - Functional component with useState hook doesn't re-render as expected

這是一個開放式問題,因為我確定我要解決的方法不正確。 但是我很好奇為什么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組件維護的用戶添加的酒吧名單。 我的期望是,當選擇一個新的foo時, Bars組件將重新渲染內部的newBars集合。 但是,無論在左側選擇了什么“ foo”,該狀態都將徘徊並顯示。

這看起來很奇怪,但是也許我沒有想到以正確的方式考慮React功能組件和掛鈎。 很想了解為什么會出現這種行為,而其他人則希望聽到提出的更有意義的方法。

非常感謝任何見識!

如果要反映層次結構,請初始化酒吧的狀態,以便它與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>
  );
}

編輯foos和bars層次結構示例

我可能對備忘錄掛鈎有些過分,但這應該使您至少可以了解如何以及如何記憶各種值。

請記住,第二個參數是依賴項數組,它確定是對備忘錄的值進行重新計算還是從緩存中檢索。 掛鈎的依賴關系通過引用進行檢查,就像PureComponent

我還選擇將selectedIndex初始化為0以避免解決沒有選擇foo時如何處理Bars的渲染功能的問題。 我將其留給您練習。

如果要顯示所選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>
    );
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM