[React]Context API 사용하는 방법
하위 컴포넌트의 개수가 늘어나게 되면 props로 state 값을 전송하기 매우 복잡해진다. 이러한 복잡한 작업을 간략하게 해주는 것이 하위 컴포넌트가 props 전송 없이 부모의 값을 공유하게 하는 방법이다. Context API 혹은 redux를 사용함으로써 이를 가능하게 만든다.
Context API 사용하는 방법
Context 생성 & 사용
1.같은 값을 공유할 범위를 생성한다.
let 재고context = React.createContext();
App 밖에 선언한다.
2. 같은 값을 공유할 범위를 지정한다.
<재고context.Provider value={재고}>
<div className="row">
{
shoes.map((a,i)=>{ // a : shoes 배열 안의 각각의 데이터
return <Card shoes={shoes[i]} i={i} key={i} /> // shoes와 i를 props로 전송
})
}
</div>
</재고context.Provider>
value={재고}
: 공유하고 싶은 데이터
3.컴포넌트 내에서 만들어둔 범위를 변수에 담아서 사용한다.
import React, { useContext, useState } from 'react';
function Card(props){
let 재고 = useContext(재고context);
return(
<div className="col-md-4">
<Link to={"/detail/"+(props.shoes.id)}>
<img src={"https://codingapple1.github.io/shop/shoes"+(props.shoes.id+1)+".jpg"} width="100%" alt="이미지"/>
<h4>{props.shoes.title}</h4>
<p>{props.shoes.content}</p>
<p>{props.shoes.price}</p>
</Link>
재고 : {재고[props.shoes.id]}
</div>
)
}
같은 파일의 중첩 컴포넌트의 경우
function Card(props){
let 재고 = useContext(재고context);
return(
<div className="col-md-4">
<Link to={"/detail/"+(props.shoes.id)}>
<img src={"https://codingapple1.github.io/shop/shoes"+(props.shoes.id+1)+".jpg"} width="100%" alt="이미지"/>
<h4>{props.shoes.title}</h4>
<p>{props.shoes.content}</p>
<p>{props.shoes.price}</p>
</Link>
<Test></Test>
</div>
)
function Test(){
let 재고 = useContext(재고context);
return <p>재고 : {재고[props.i]}</p>
}
}
App > Card > Test 컴포넌트가 있다. 여기서 App에 선언된 state를 최하위 컴포넌트인 Test에서도 사용할 경우 props로 App에서 Card로 Card에서 Test로 값을 두번 전송하지 않고 Context 범위를 지정하여 값을 최하위 컴포넌트에 한번에 공유할 수 있다.
다른 파일의 중첩 컴포넌트의 경우
App.js
export let 재고context = React.createContext();
<Route path="/detail/:id">
<재고context.Provider value={재고}>
<Detail shoes={shoes} 재고={재고} 재고변경={재고변경}/>
</재고context.Provider>
</Route>
Detail.js
import React, { useContext, useEffect, useState } from "react";
import {재고context} from './App'
function Detail(props){
...
let 재고 = useContext(재고context);
...
<p>재고 : {재고[props.shoes[id].id]}</p>
...
}
전체 코드
App.js
// eslint-disable
import React, { useContext, useState } from 'react';
import { Navbar, Nav, NavDropdown, Container, Alert} from 'react-bootstrap';
import { Link, Route, Switch} from 'react-router-dom';
import './App.css';
import Data from './data';
import Detail from './Detail';
import axios from 'axios';
export let 재고context = React.createContext();
function App() {
let [shoes, shoes변경] = useState(Data);
let [재고, 재고변경] = useState([10,11,12]);
return (
<div className="App">
<Navbar bg="light" expand="lg">
<Container>
<Navbar.Brand href="/">Shoe Shop</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<Link to="/">Home</Link>
<Link to="/detail">Detail</Link>
<NavDropdown title="Dropdown" id="basic-nav-dropdown">
<NavDropdown.Item href="#action/3.1">Action</NavDropdown.Item>
<NavDropdown.Item href="#action/3.2">Another action</NavDropdown.Item>
<NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item href="#action/3.4">Separated link</NavDropdown.Item>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
<Switch>
<Route exact path="/">
<Alert variant="success" className="background">
<Alert.Heading>20% Season off</Alert.Heading>
<p>
Aww yeah, you successfully read this important alert message. This example
text is going to run a bit longer so that you can see how spacing within an
alert works with this kind of content.
</p>
<hr />
<p className="mb-0">
Whenever you need to, be sure to use margin utilities to keep things nice
and tidy.
</p>
</Alert>
<div className="container">
<재고context.Provider value={재고}>
<div className="row">
{
shoes.map((a,i)=>{ // a : shoes 배열 안의 각각의 데이터
return <Card shoes={shoes[i]} i={i} key={i} /> // shoes와 i를 props로 전송
})
}
</div>
</재고context.Provider>
<button className='btn btn-primary more' onClick={()=>{
// 버튼 클릭시 로딩중 UI를 띄운다.
document.querySelector(".more").textContent = '로딩중';
// 서버에 Get 요청
axios.get('https://codingapple1.github.io/shop/data2.json')
// 요청 성공시 실행할 코드
.then((result)=>{
// 요청 성공하면 로딩중 UI를 지운다.
document.querySelector(".more").textContent = '더보기';
shoes변경([...shoes, ...result.data]); // ... : 대괄호를 벗겨준다 , [] : 대괄호로 다시 감싸준다.
})
// 요청 실패시 실행할 코드
.catch(()=>{
// 요청 실패하면 로딩중 UI를 지운다.
console.log('실패했어요.');
})
}}>더보기</button>
</div>
</Route>
<Route path="/detail/:id">
<재고context.Provider value={재고}>
<Detail shoes={shoes} 재고={재고} 재고변경={재고변경}/>
</재고context.Provider>
</Route>
</Switch>
</div>
)
}
function Card(props){
let 재고 = useContext(재고context);
return(
<div className="col-md-4">
<Link to={"/detail/"+(props.shoes.id)}>
<img src={"https://codingapple1.github.io/shop/shoes"+(props.shoes.id+1)+".jpg"} width="100%" alt="이미지"/>
<h4>{props.shoes.title}</h4>
<p>{props.shoes.content}</p>
<p>{props.shoes.price}</p>
</Link>
재고 : {재고[props.shoes.id]}
<Test></Test>
</div>
)
function Test(){
let 재고 = useContext(재고context);
return <p>재고 : {재고[props.i]}</p>
}
}
export default App;
Detail.js
import React, { useContext, useEffect, useState } from "react";
import { useHistory, useParams } from 'react-router-dom';
import styled from 'styled-components';
import './Detail.scss';
import {재고context} from './App'
let 박스 = styled.div`
padding : 20px;
`;
let 제목 = styled.h4`
font-size : 25px;
color : ${ props => props.색상 }
`
function Detail(props){
let { id } = useParams();
let history = useHistory();
let [alert, alert변경] = useState(true);
let 재고 = useContext(재고context);
useEffect(()=>{
let 타이머 = setTimeout(()=>{
alert변경(false)
console.log("안녕")
return ()=>{clearTimeout(타이머)}
},2000)
},[]);
return(
<div className="container">
<박스>
<제목 className="red">상세페이지</제목>
</박스>
{
alert === true
? (
<div className="my-alert-yellow">
<p>재고가 얼마 남지 않았습니다.</p>
</div>
)
: null
}
<div className="row">
<div className="col-md-6">
<img src={"https://codingapple1.github.io/shop/shoes"+(props.shoes[id].id+1)+".jpg"} width="100%" alt="이미지"/>
</div>
<div className="col-md-6 mt-4">
<h4 className="pt-5">{props.shoes[id].title}</h4>
<p>{props.shoes[id].content}</p>
<p>{props.shoes[id].price}</p>
<p>재고 : {재고[props.shoes[id].id]}</p>
<button className="btn btn-danger" onClick={()=>{ props.재고변경([...props.재고(9,11,12)]) }}>주문하기</button>
<button className="btn btn-primary" onClick={()=>{history.goBack();}}>뒤로가기</button>
</div>
</div>
</div>
)
}
function Info(props){
return(
<p>재고 : {props.재고[0]}</p>
)
}
export default Detail;
App.css
.App {
text-align: center;
}
.background {
background-image: url("../public/background.jpg");
background-size: cover;
color: #fff;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
Detail.scss
@import "./reset.scss";
$메인칼라: #ff0000;
.red {
color: $메인칼라;
}
@mixin 함수 {
background-color: #eee;
padding: 15px;
border-radius: 5px;
max-width: 500px;
width: 100%;
margin: auto;
}
.my-alert {
@include 함수();
}
.my-alert-yellow {
@extend .my-alert;
background-color: #ffe591;
}
.my-alert p {
margin-bottom: 0;
}
Data.js
/* eslint-disable import/no-anonymous-default-export */
export default [
{
id : 0,
title : "White and Black",
content : "Born in France",
price : 120000
},
{
id : 1,
title : "Red Knit",
content : "Born in Seoul",
price : 110000
},
{
id : 2,
title : "Grey Yordan",
content : "Born in the States",
price : 130000
}
]