反应从const传递到子组件的props

时间:2018-08-25 02:11:40

标签: reactjs const state map-function

我是一个反应型菜鸟,我需要我正在使用的购物车的帮助。

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

我映射到cartItems变量上,并使它们显示在页面上,但是现在有将其传递给另一个组件的情况。

请保持温柔,这是我的第一篇文章。

这是应用程序组件中的代码:

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>

    )
   }
  }

4 个答案:

答案 0 :(得分:0)

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

将对象数组保持在该状态,以便您可以删除对象并再次执行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>

      )
   }
  }
}

答案 1 :(得分:0)

任何“活着”的变量都需要在某个地方处于状态。这是一个示例购物车应用,可让您保持购物状态。

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. 我们传递了回调道具来修改树中较高位置的状态。 (此回调使用我们的唯一标识符来传递一个新对象,并过滤掉已删除的项目)

但是,我会强烈鼓励考虑使用avoiding arrays of objects, and flattening your data。除了使用对象数组之外,您还可以使用由ID(以便于访问)和代表这些项目组的ID数组作为键的对象。在完整的应用程序中,您可能有一个名为products的东西,代表您出售的商品所有。您的购物车就是被添加来购买的那些。因此products将是我们的id键对象,而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简短明了,对其进行修改只需要过滤数组即可。

一个看似“否定”的可能是,您必须不断将products传递到树上。但是,a)在树上传递东西是React的事,b)非常很有可能随着您应用的增长,您将开始使用某种全局状态库(例如Redux)或React's context API ,然后根据需要将每个组件连接到该全局状态。在那个阶段,您不需要一直向下传递products,而是可以根据需要将其“导入”到组件中(并且不需要向下传递回调,您可以执行全局操作来修改全局状态)。

答案 2 :(得分:0)

我意识到我的other answer跳过了如何让onClickRemove知道当您遍历项目时要删除哪个项目的问题。我知道这样做的三种方式:

1。通过传递的参数传递匿名函数(或绑定参数)

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

这可能带有fairly minor performance hit

2。使用currying-一个接受您的参数并返回另一个函数的函数

因此,在onClick中,您实际上是在调用方法:

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

该方法是一个接受项目的函数,然后返回另一个处理该项目的函数:

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 />组件可以使用<RemoveButton onClick={this.removeOnClick} />方法,来执行removeOnClick

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

由于您仅通过了 该产品,因此我们可以使用该方法对要删除的产品进行硬编码。 (可以说,这也是拆分组件的好地方。)

答案 3 :(得分:-1)

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

例如

<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的更多信息以及有关组件和道具here

的更多信息

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