Реакция передачи реквизита из const в дочерний компонент - PullRequest
0 голосов
/ 25 августа 2018

Я новичок, и мне нужна помощь с корзиной, над которой я работаю.

Я пытаюсь получить кнопку, чтобы удалить элемент из массива объектов, которые у меня есть, но я не знаю, как получить к нему доступ из того места, где он находится в дереве.

Я сопоставил переменную 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 голосов
/ 25 августа 2018

Я понимаю, что мой другой ответ как бы пропускает вопрос о том, как onClickRemove узнать , какой элемент удаляется, когда вы зацикливаетесь на своих элементах. Есть три способа сделать это:

1. Передать анонимную функцию с переданным аргументом (или связать аргумент)

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

Это может прийти с довольно незначительным ударом производительности .

2. Используйте curry - функция, которая принимает ваш аргумент и возвращает другую функцию

Итак, в onClick вы фактически вызываете метод:

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

И этот метод является функцией, которая принимает элемент, а затем возвращает еще одну функцию, которая обрабатывает его удаление:

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

Это позволяет избежать снижения производительности, которое имеет вариант 1. Но, честно говоря, я никогда не видел его в дикой природе. только в качестве примера. Возможно, люди не знают или не заботятся о снижении производительности, или просто находят этот синтаксис менее привлекательным, или просто не думают об этом.

3. (рекомендуется) Избегайте ситуации вообще, вместо этого создавая новый компонент в каждом цикле (который принимает элемент в качестве опоры)

Вот что библиотека React ESLint рекомендует , и мы сделали с:

{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);

Поскольку вы передали только этому продукту, мы можем в этом методе жестко указать, какой продукт мы удаляем. (Возможно, это просто хорошее место, чтобы разбить ваши компоненты.)

0 голосов
/ 25 августа 2018

Иметь уникальный ключ для каждого объекта в массиве, как у вас для нулевого индекса объекта.

сохраняйте массив объектов в таком состоянии, чтобы вы могли удалить объект и снова выполните setState, чтобы увидеть обновленные данные.

Вы должны хранить свою функцию обработчика событий там, где вы выполняете итерации своих данных, и передавать функцию обработчика событий в качестве реквизитов в RemoveButton. Это будет работать как обратный вызов в реакции.

Я предпочитаю приведенный ниже способ реализации таких вещей

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>

      )
   }
  }
}
0 голосов
/ 25 августа 2018

Любая переменная, которая "жива", должна где-то находиться в состоянии. Вот пример приложения корзины, в котором ваша корзина находится в состоянии.

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. Мы передаем реквизиты обратного вызова, чтобы изменить состояние, которое находится где-то выше в нашем дереве. (этот обратный вызов использует наш уникальный идентификатор для передачи нового объекта с отфильтрованным удаленным элементом)

Однако я бы настоятельно рекомендовал бы избегать массивов объектов и выравнивать ваши данные . Вместо массива объектов у вас будет объект с ключом id (для простоты доступа) и массивы идентификаторов, которые представляют группы этих элементов. В полном приложении у вас, вероятно, есть что-то под названием products, представляющее все ваших товаров для продажи. И ваша корзина будет просто добавлена ​​для покупки. Так что products будет нашим идентификатором, а inCart будет просто массивом идентификаторов. Вот пример приложения корзины с такой формой (с продуктами наивысшего компонента, которые, вероятно, используются в вашем приложении):

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 вниз по дереву. Тем не менее, а) передача вещей по дереву - это дело React, б) вероятность того, что очень будет расти, вы начнете использовать какую-то глобальную библиотеку состояний (например, Redux) или контекстный API React. и подключите каждый компонент к этому глобальному состоянию при необходимости. На этом этапе вам не нужно продолжать передавать products вниз, но вместо этого «импортировать» его в компонент по мере необходимости (и вместо того, чтобы передавать обратные вызовы вниз, у вас будут глобальные действия для изменения глобального состояния).

0 голосов
/ 25 августа 2018

Эта структура кода не настолько эффективна, и я настоятельно рекомендую вам на самом деле не использовать ее в производстве, но все же, используя ту же архитектуру, которую вы настроили, вы можете передать индекс элемента компоненту.

например

<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, так как лямбда-выражение может потенциально иметь проблемы с производительностью. Подробнее о связывании здесь и о компонентах и ​​подпорках здесь

Это немного грязно. Лучше позволить родительскому элементу обрабатывать функцию onClick, поскольку он имеет доступ к текущему ItemList и может изменять его, если он не является const. Однако я не знаю вариант использования, так что это может быть совершенно справедливо!

...