两个功能性 React JS 组件之间的双向通信

Two way communication between two functional React JS components

我在 React 中内置了两个功能组件,一个是 Item 组件 - 它包含一些关于东西的数据,带有可选图形、一些文本数据和价格信息。在底部有一个按钮,允许您 select 这个特定的项目。它还在其道具中保留有关当前 selected Item 的 ID 的信息 - 这就是我计划解决此问题的方式。

我的第二个组件是 ItemList - 它基本上包含一个前面提到的项目列表 - 而且它对所有项目进行排序,并且必须保留有关当前哪个组件的信息 selected - selected 基本上看起来不同 - 一些东西比如边框和按钮的颜色通过 CSS.

切换

我的实现逻辑是这样的——当用户点击一个特定项目的“Select”按钮时,该项目应该改变它的外观(除非它已经 selected,然后做什么都没有),然后以某种方式将信息传播到 ItemList,以便它可以“禁用”先前 selected 的组件。只能有一个 selected Item,一旦用户决定 select 另一个,之前的 selected 应该改变它的状态并去回到未selected 标准图形样式。

我已经 运行 解决了 ItemList 组件中的状态加上通过 props 将函数传递给 Item ,但这并没有解决第二部分 - ItemList 需要获取有关更改的信息,因此它可以根据实际状态重新呈现所有组件。我应该深入研究 React 的哪一部分 API 来解决这个问题?

这是我的组件的代码:

项目

interface Props {
    receivedObject: itemToDisplay;
    selectedItemId: string;
    onClick?: () => void;
}

export default function Item(props: Props) {
    const {name, description, price} = props.receivedObject;
    const imageUrl = props.receivedObject?.media?.mainImage?.small?.url;
    const priceComponent = <Price price={price}/>;
    const [isItemSelected, setSelection] = useState(props.selectedItemId == props.receivedObject.id);
    const onClick = props.onClick || (() => {
        setSelection(!isItemSelected)
    });
    return (
        <>
            <div className="theDataHolderContainer">
            // displayed stuff goes here
                <div className="pickButtonContainer">
                    // that's the button which should somehow send info "upwards" about the new selected item
                    <Button outline={isItemSelected} color="danger" onClick={onClick}>{isItemSelected ? "SELECTED" : "SELECT"}</Button>
                </div>

            </div>
        </>)
};

项目列表

interface Props {
    packageItems: Array<itemToDisplay>
}

export default function ItemList(props: Props) {
    const itemsToDisplay = props.packageItems;
    itemsToDisplay.sort((a, b) =>
        a.price.finalPrice - b.price.finalPrice
    );
    let selectedItemId = itemsToDisplay[0].id;
    const [currentlySelectedItem, changeCurrentlySelectedItem] = useState(selectedItemId);

    const setSelectedItemFunc = () => { 
        /* this function should be passed down as a prop, however it can only 
         * have one `this` reference, meaning that `this` will refer to singular `Item`
         * how do I make it change state in the `ItemList` component?
         */
    
        console.log('function defined in list');
    };
    return(
        <div className="packageNameList">
            <Item
                key={itemsToDisplay[0].id}
                receivedObject={itemsToDisplay[0]}
                onClick={setSelectedItemFunc}
            />

            {itemsToDisplay.slice(1).map((item) => (
                <Item
                    key={item.id}
                    receivedObject={item}
                    onClick={setSelectedItemFunc}
                />
                ))}
        </div>
    );
}

在 React 中,the data flows down,因此您最好将状态数据保存在呈现表示组件的有状态组件中。

function ListItem({ description, price, selected, select }) {
  return (
    <li className={"ListItem" + (selected ? " selected" : "")}>
      <span>{description}</span>
      <span>${price}</span>
      <button onClick={select}>{selected ? "Selected" : "Select"}</button>
    </li>
  );
}

function List({ children }) {
  return <ul className="List">{children}</ul>;
}

function Content({ items }) {
  const [selectedId, setSelectedId] = React.useState("");
  const createClickHandler = React.useCallback(
    id => () => setSelectedId(id),
    []
  );

  return (
    <List>
      {items
        .sort(({ price: a }, { price: b }) => a - b)
        .map(item => (
          <ListItem
            key={item.id}
            {...item}
            selected={item.id === selectedId}
            select={createClickHandler(item.id)}
          />
        ))}
    </List>
  );
}

function App() {
  const items = [
    { id: 1, description: "#1 Description", price: 17 },
    { id: 2, description: "#2 Description", price: 13 },
    { id: 3, description: "#3 Description", price: 19 }
  ];

  return (
    <div className="App">
      <Content items={items} />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById("root")
);
.App {
  font-family: sans-serif;
}
.List > .ListItem {
  margin: 5px;
}
.ListItem {
  padding: 10px;
}
.ListItem > * {
  margin: 0 5px;
}
.ListItem:hover {
  background-color: lightgray;
}
.ListItem.selected {
  background-color: darkgray;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>