将 props 从 const 传递给子组件

React passing props from const to child component

我是一个 React 菜鸟,我需要帮助处理我正在处理的购物车。

我正在尝试获取按钮以从我拥有的对象数组中删除一个项目,但不知道如何从它在树中的位置访问它。

我映射了 cartItems 变量并让它们显示在页面上,但现在需要将它传递给另一个组件。

请轻点,这是我的第一个post。

这是 App 组件中的代码:

const cartItems =
[
 {
id: 1,
image: image1,
description: 'Cotton Tshirt',
style: 'MS13KT1906',
color: 'blue',
size: 'S',
price: 11.00,
button: <RemoveButton />
  },
{
image: image3,
description: 'Print Girls Tee',
style: 'MS13KT1906',
color: 'pink',
size: 'S',
price: 17.00,
button: <RemoveButton />
},
{
image: image2,
description: 'Flower Pattern Shirt',
style: 'MS13KT1906',
color: 'blue',
size: 'S',
price: 9.00,
button: <RemoveButton />
},
{
image: image4,
description: 'Check Pattern Shirt',
style: 'MS13KT1906',
color: 'red',
size: 'M',
price: 22.00,
button: <RemoveButton />
 }
];

return (
  <div>
 <ItemList         
  cartItems={cartItems.map((item, i) =>
        <ul key={i}>
          <li>
            <img src={item.image} />
          </li>
          <li>
            Description: {item.description}
          </li>
          <li>
            Color: {item.color}
          </li>
          <li>
            Size: {item.size}
          </li>
          <li>
            Price: {item.price}
          </li>
          <li>
            {item.button}
          </li>
        </ul>
      )} />
     <RemoveButton />
      </div>
    );
  }
 }

以及删除按钮组件中的代码

 onClickRemove(e, item) {
   console.log(e) <-- what do i do here?
 }

 render() {
  return(
    <div>
      <button onClick={this.onClickRemove}>
        X Remove
      </button>
    </div>

    )
   }
  }

此代码结构效率不高,我强烈建议您不要在生产中实际使用它,但仍使用您设置的相同架构,您可以将项目索引向下传递给组件。

例如

<ItemList         
  cartItems={cartItems.map((item, i) =>
        <ul key={i}>
          <li>
            <img src={item.image} />
          </li>
          <li>
            Description: {item.description}
          </li>
          <li>
            Color: {item.color}
          </li>
          <li>
            Size: {item.size}
          </li>
          <li>
            Price: {item.price}
          </li>
          <li>
            {item.button}
          </li>
        </ul>
      )} />
     <RemoveButton index={i} /> // i = index in the Object Array
      </div>
    );

RemoveButton 组件现在知道它关联的索引是什么。现在在您的组件中,您可以获取该索引并对其执行一些操作,例如

 onClickRemove(index) {
   // do something with index.
 }

 render() {
  return(
    <div>
      <button onClick={this.onClickRemove.bind(this, this.props.index}>
        X Remove
      </button>
    </div>

    )
   }
  }

最好使用 bind 方法,因为 lambda 表达式可能会产生负面的性能问题。详细了解绑定 here and more about Component and Props here

这有点乱。最好让父级处理 onClick 函数,因为它可以访问当前的 ItemList 并且如果它不是 const 则可以修改它。但是我不知道用例所以这可能是完全有效的!

数组中的每个对象都具有唯一键,就像对零索引对象一样。

将您的对象数组保持在状态中,以便您可以删除对象并再次执行 setState 以查看更新后的数据。

您应该在迭代数据的地方保留事件处理函数,并将事件处理函数作为道具传递给 RemoveButton。这将作为 reactjs 中的回调。

我更喜欢下面的实现方式

class CartItems extends Component{
    constructor(props){
      super(props);
      this.state = {
        cartItems: []
      }
    }

    componentWillMount(){
      const cartItems =
        [
         {
        id: 1,
        image: image1,
        description: 'Cotton Tshirt',
        style: 'MS13KT1906',
        color: 'blue',
        size: 'S',
        price: 11.00,
        button: <RemoveButton />
          },
        {
        id: 2,
        image: image3,
        description: 'Print Girls Tee',
        style: 'MS13KT1906',
        color: 'pink',
        size: 'S',
        price: 17.00,
        button: <RemoveButton />
        },
        {
        id: 3,
        image: image2,
        description: 'Flower Pattern Shirt',
        style: 'MS13KT1906',
        color: 'blue',
        size: 'S',
        price: 9.00,
        button: <RemoveButton />
        },
        {
        id: 4,
        image: image4,
        description: 'Check Pattern Shirt',
        style: 'MS13KT1906',
        color: 'red',
        size: 'M',
        price: 22.00,
        button: <RemoveButton />
         }
        ];
        this.setState({
          cartItems: cartItems
        });
    }

    onClickRemove(id, image) {
      let items = this.state.cartItems;
      items = items.filter((item) => {
        return item.id !== id;
      });
      this.setState({
        cartItems: items
      });
    }

    render(){
      return (
        <div>
          <ItemList cartItems={this.state.cartItems.length> 0 && this.state.cartItems.map((item, i) =>
              <ul key={i}>
                <li>
                  <img src={item.image} />
                </li>
                <li>
                  Description: {item.description}
                </li>
                <li>
                  Color: {item.color}
                </li>
                <li>
                  Size: {item.size}
                </li>
                <li>
                  Price: {item.price}
                </li>
                <li>
                  {item.button}
                </li>
              </ul>
            )} />
           <RemoveButton onClickRemove={this.onClickRemove(item.id, item.image)}/>
            </div>
          );
    }

} 

删除按钮组件

class RemoveButton extends Component{
  render() {
    return(
      <div>
        <button onClick={this.props.onClickRemove}>
          X Remove
        </button>
      </div>

      )
   }
  }
}

任何 "alive" 的变量都需要在某处处于状态。这是一个示例购物车应用程序,您可以在该状态下购物车。

function App() {
  return (
    <div className="App">
      <Cart />
    </div>
  );
}

class Cart extends React.Component {
  state = {
    inCart: [
      {
        id: 1,
        description: "Cotton Tshirt",
        style: "MS13KT1906",
        color: "blue",
        size: "S",
        price: 11
      },
      {
        id: 2,
        description: "Print Girls Tee",
        style: "MS13KT1906",
        color: "pink",
        size: "S",
        price: 17
      },
      {
        id: 9,
        description: "Check Pattern Shirt",
        style: "MS13KT1906",
        color: "red",
        size: "M",
        price: 22.5
      }
    ]
  };

  // or just pass the id, and make the check `(product.id !== removedProductId)`
  removeCallback = removedProduct => {
    this.setState(state => ({
      inCart: state.inCart.filter(product => product.id !== removedProduct.id)
    }));
  };

  render() {
    const { inCart } = this.state;

    if (!inCart.length) {
      return (
        <p>Cart is empty.</p>
      );
    }

    return (
      <table>
        {inCart.map(product => (
          <CartRow
            key={product.id}
            product={product}
            removeCallback={this.removeCallback}
          />
        ))}
      </table>
    );
  }
}

class CartRow extends React.Component {
  removeOnClick = removedProduct =>
    this.props.removeCallback(this.props.product);

  render() {
    const { product, removeCallback } = this.props;

    return (
      <tr>
        <td>{product.description}</td>
        <td>{product.price}</td>
        <td>
          <RemoveButton onClick={this.removeOnClick} />
        </td>
      </tr>
    );
  }
}

class RemoveButton extends React.Component {
  render() {
    return <button {...this.props}>Remove</button>;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.App {
  font-family: sans-serif;
  text-align: center;
}

td {
  border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="root"></div>

要强调的两件事是:

  1. 所有项目都需要一些唯一标识符(在本例中 id
  2. 我们传递回调 props 来修改树中较高位置的状态。 (此回调使用我们的唯一标识符传递一个新对象,并过滤掉已删除的项目)

但是,我会强烈鼓励考虑avoiding arrays of objects, and flattening your data。您将拥有一个由 id 键控的对象(以便于访问)和代表这些项目组的 id 数组,而不是对象数组。在一个完整的应用程序中,您可能有一个名为 products 的东西,代表 所有 您的待售商品。您的购物车将只是添加购买的商品。所以 products 将是我们的 id-keyed 对象,而 inCart 只是一个 id 数组。这是一个具有该形状的购物车应用示例(产品位于最高组件,因为它可能在您的整个应用中使用):

function App() {
  const products = {
    1: {
      id: 1,
      description: "Cotton Tshirt",
      style: "MS13KT1906",
      color: "blue",
      size: "S",
      price: 11
    },
    2: {
      id: 2,
      description: "Print Girls Tee",
      style: "MS13KT1906",
      color: "pink",
      size: "S",
      price: 17
    },
    4: {
      id: 4,
      description: "Flower Pattern Shirt",
      style: "MS13KT1906",
      color: "blue",
      size: "S",
      price: 9
    },
    9: {
      id: 9,
      description: "Check Pattern Shirt",
      style: "MS13KT1906",
      color: "red",
      size: "M",
      price: 22.5
    }
  };

  return (
    <div className="App">
      <Cart products={products} />
    </div>
  );
}

class Cart extends React.Component {
  state = {
    inCart: [1, 2, 9]
  };

  removeCallback = removedId => {
    this.setState(state => ({
      inCart: state.inCart.filter(productId => productId !== removedId)
    }));
  };

  // for testing only
  refillCart = () => {
    this.setState(state => ({
      inCart: [1, 2, 9]
    }));
  };

  render() {
    const { products } = this.props;
    const { inCart } = this.state;

    if (!inCart.length) {
      return (
        <p>
          Cart is empty. <button onClick={this.refillCart}>Refill?</button>
        </p>
      );
    }
    return (
      <table>
        {inCart.map(productId => (
          <CartRow
            key={productId}
            products={products}
            productId={productId}
            removeCallback={this.removeCallback}
          />
        ))}
      </table>
    );
  }
}

class CartRow extends React.Component {
  removeOnClick = removedId => this.props.removeCallback(this.props.productId);

  render() {
    const { products, productId, removeCallback } = this.props;
    const product = products[productId];

    return (
      <tr>
        <td>{product.description}</td>
        <td>{product.price}</td>
        <td>
          <RemoveButton onClick={this.removeOnClick} />
        </td>
      </tr>
    );
  }
}

class RemoveButton extends React.Component {
  render() {
    return <button {...this.props}>Remove</button>;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.App {
  font-family: sans-serif;
  text-align: center;
}

td {
  border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

这样,inCart简洁明了,修改它只需要过滤数组。

一个看似 "negative" 的可能是你必须不断地 products 下树。但是,a) 将东西向下传递到树上是 React 的事情,b) 它 非常 可能随着您的应用程序的增长,您将开始使用某种全局状态库(如 Redux)或 React's context API,并根据需要将每个组件连接到该全局状态。在那个阶段,您不需要继续向下传递 products,而是根据需要将其 "import" 传递到组件中(而不是向下传递回调,您可以使用全局操作来修改全局状态).

我意识到我的 有点跳过了如何让 onClickRemove 知道 哪个 项目在你循环播放时被删除的问题项目。我知道三种方法:

1.传递带参数的匿名函数(或绑定参数)

<RemoveButton onClick={() => this.onClickRemove(item)}>
// or: <RemoveButton onClick={this.onClickRemove.bind(this, item)}>

这可能带有 fairly minor performance hit

2。使用 currying - 一个接受你的参数的函数和 returns 另一个函数

所以在 onClick 你实际上调用了一个方法:

<RemoveButton onClick={this.onClickRemove(item)}>

那个方法是一个接受项目的函数,然后是 returns 另一个处理删除项目的函数:

onClickRemove = (item) => () => { /* do something with `item` */ }

这避免了选项 1 对性能的影响。但是,老实说,我从未在野外见过它;仅作为示例。也许人们不知道或不关心性能损失,或者只是觉得这种语法不那么吸引人,或者只是没有想到它。

3。 (推荐) 通过在每个循环中创建一个新组件(将项目作为道具)来完全避免这种情况

这就是 React ESLint library recommends 我们已经完成的:

{inCart.map(productId => (
    <CartRow
      key={productId}
      products={products}
      productId={productId}
      removeCallback={this.removeCallback}
    />
))}

既然它是自己的组件,您的 <CartRow /> 组件可以使用 removeOnClick 方法执行 <RemoveButton onClick={this.removeOnClick} />

removeOnClick = removedId => this.props.removeCallback(this.props.productId);

由于您 该产品,我们可以在该方法中硬编码要删除的产品。 (它也可以说是分解组件的好地方。)