[React]Context

Mia:)·2021년 2월 11일
0

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

context는 데이터를 컴포넌트의 트리를 통해 전달하는 한 방법으로, 원칙적으로는 props를 통해 전달해야하지만, props 없이도 context를 통해 보낼 수 있다.

기존에 데이터를 보낼때는 top-down방식으로, 부모의 컴포넌트에서 자식의 컴포넌트로 데이터를 props를 통해 전달하는 방법을 사용했다. 하지만 여러 컴포넌트에서 전달해줘야 하는 props의 경우 이러한 과정이 번거롭다. 그래서 context를 사용하여, 트리 단계마다 명시적으로 props를 넘겨주지 않아도, 많은 컴포넌트가 데이터를 공유하도록 도와준다.

react 공식문서에는 context는 React 컴포넌트 트리 안에서 전역적 global이라고 볼 수 있는 데이터를 공유할 수 있도록 고안되었다고 한다. context를 써보니, context가 있는 컴포넌트 안에 감싸진 하위 컴포넌트들에게 데이터를 보내고, 쓸 수 있어서 global이라고 할 수 있는 거 아닌가 그런생각...

내가 따로 유튜브를 보고 공부한, 쇼핑몰 앱을 만드는데, context는 중요한 부분이었다. mock data가 여러곳에 쓰였는데, 이를 context를 사용하여 데이터를 넘겨 주었다. 그래서 그 부분을 뜯어보고, context가 뭔지 공부를 해보려고 블로그에 글을 쓴다!

import React, { Component } from 'react';
import { storeProducts, detailProduct } from './data';

const ProductContext = React.createContext();
//provider
//consumner
const ProductConsumer = ProductContext.Consumer;

class ProductProvider extends Component {
  state = {
    products: [],
    detailProduct: detailProduct,
    cart: [],
  }
  componentDidMount() {
    this.setProduct();
  }
  setProduct = () => {
    let tempProduct = [];
    storeProducts.forEach(item => {
      const singleItem = { ...item };
      tempProduct = [...tempProduct, singleItem];
    })
    this.setState(() => {
      return { products: tempProduct };
    });
  };

  getItem = (id) => {
    const product = this.state.products.find(item => item.id === id);
    console.log(product);
    return product;
  }

  handleDetail = (id) => {
    const product = this.getItem(id);
    this.setState(() => {
      return { detailProduct: product }
    })

  }
  addToCart = (id) => {
    let tempProducts = [...this.state.products];
    const index = tempProducts.indexOf(this.getItem(id));
    const product = tempProducts[index];
    product.inCart = true;
    product.count = 1;
    const price = product.price;
    product.total = price;

    this.setState(() => {
      return { products: tempProducts, cart: [...this.state.cart, product] };
    }, () => { console.log(this.state) })


  }

  render() {
    return (
      <ProductContext.Provider value={{ ...this.state, handleDetail: this.handleDetail, addToCart: this.addToCart }}>
        {this.props.children}
      </ProductContext.Provider>
    );
  }
}

export { ProductConsumer, ProductProvider };
const ProducContext = React.createContext();
  1. context를 만들어주기 위해 React.createContext()를 통해 ProductContext를 만들어 주었다.
 <ProducContext.Provider value={{ ...this.state, handleDetail: this.handleDetail, addToCart: this.addToCart }}>
  1. Provider을 통해 이 context를 구독하는 다른 컴포넌트들에서 context의 변화를 알린다.
    Provider 컴포넌트는 value prop를 받아시 이 값을 하위에 있는 컴포넌트에게 전달하고, 값을 전달 받을 수 있는 컴포넌트의 수에는 제한이 없다. Provider 하위에 또 다른 Provider를 배치하는 것도 가능하고, 하위 Provider값이 우선시 된다.

Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value prop가 바뀔 때마다 다시 렌더링 됩니다.

ReactDOM.render(
  <ProductProvider>
  **  <Router>
      <App />
    </Router>
  </ProductProvider>,**

  document.getElementById('root')
);

ProductProvider가 감싸고 있는 Router,App 컴포넌트에 value props를 전달한다.

  1. {this.props.children}
 <ProductContext.Provider value={{ ...this.state, handleDetail: this.handleDetail, addToCart: this.addToCart }}>
        {this.props.children}
      </ProductContext.Provider>

{this.props.children}이 없으면 하위의 컴포넌트들이 웹상에 뿌려지지 않는다! 상위 컴포넌트가 감싸고 있는 하위컴포넌트를 보여줄때, 이런식으로 사용하면 될 것 같다! 또한 앞에서 언급했듯이 App과 Router에 props를 넘겨주지 않아도, 데이터를 보낼 수있다!

조금 이해하기 어려웠던점.

const ProductContext = React.createContext();
const ProductConsumer = ProductContext.Consumer;

class ProductProvider extends Component {
...중략 
}
  render() {
    return (
      <ProductContext.Provider value={{ ...this.state, handleDetail: this.handleDetail, addToCart: this.addToCart }}>
        {this.props.children}
      </ProductContext.Provider>
    );
    
    
export { ProductConsumer, ProductProvider };

편의를 위해 몇개를 중략했지만, 이해하기 어려웠던 점은 바로 Consumer...!
1.

const ProductContext = React.createContext();
const ProductConsumer = ProductContext.Consumer;

createRef()처럼 이렇게 context를 만드는건, 이런 로직이니까 넘어가겠는데, 그다음 느닷없이 나온 Consumer....?

React 공식문서:
context 변화를 구독하는 React 컴포넌트입니다. 함수 컴포넌트안에서 context를 읽기 위해서 쓸 수 있습니다.
Context.Consumer의 자식은 함수여야합니다. 이 함수는 context의 현재값을 받고 React 노드를 반환합니다. 이 함수가 받는 value 매개변수 값은 해당 context의 Provider 중 상위 트리에서 가장 가까운 Provider의 value prop과 동일합니다. 상위에 Provider가 없다면 value 매개변수 값은 createContext()에 보냈던 defaultValue와 동일할 것입니다.

흐음...? 분명 데이터가 바뀔 수 있으니까, 변화를 감지하는 기능이 필요할거라 생각은 드는데, 그래서 Consumer을 어떻게 쓰라는거지..?

이렇게 쓰네?!

<ProductContext.Provider value={{ ...this.state, handleDetail: this.handleDetail, addToCart: this.addToCart }}>

우선 ProductProvider 컴포넌트에서, ProductContext에서 Provider를 이용하여 value값을 함수들을 보내주었다.

    <ProductConsumer>
            {(value) => (
              <div
                className="img-container p-5"
               ** onClick={() => value.handleDetail(id)}>**
                <Link to="/details">
                  <img src={img} alt="product" className="card-img-top" />
                </Link>
                <button
                  className="cart-btn"
                  disabled={inCart ? true : false}
                 _ onClick={() => { value.addToCart(id) }} >_
                  {inCart ? (<p className="text-capitalize mb-0">{""}in Cart</p>) : (<i className="fas fa-cart-plus"></i>)}
                </button>
              </div>
            )}

          </ProductConsumer>

{(value) => ()} 정말 함수가 쓰이네요! 그래서 value를 통해 .을 해서 앞에서 보내준 함수를 onclick시 사용할 수 있게 되는 것!

0개의 댓글