首页 > 解决方案 > 两个功能性 React JS 组件之间的双向通信

问题描述

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

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

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

我遇到了一个在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>
    );
}

标签: javascriptreactjsfrontendweb-frontend

解决方案


在 React 中,数据是向下流动的,因此您最好将状态数据保存在渲染表示组件的有状态组件中。

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>


推荐阅读