장바구니 만들기 - 응용편

아데스티·2023년 3월 15일
0

React

목록 보기
7/7

저번시간 코드 리뷰

저번 시간 이론이여서 어디까지 구현했는지 다까먹었을 테니
map까지 구현된 현재 코드를 리뷰하고 넘어가겠습니다.

import { useState } from 'react';

function App() {
  let [과자이름] = useState(['허니버터칩', '홈런볼', '꼬북칩']);

  return (
    <>
			<h4>과자목록</h4>{
        과자이름.map((과자이름, idx) => <p key={idx}>{과자이름}</p>)
      }
    </>
	)
}

export default App;

이 코드를 보시면 아래와 같은 구조입니다.

Statemap
변수과자이름keyidx
['허니버터칩', '홈런볼', '꼬북칩']내용과자이름의 각 요소

state로 돌 때 변수를 과자이름, 값을 배열로 주었고
그 배열을 map 메서드로 돌면서 각 과자의 이름이 나오도록 작성하였습니다.
이게 지금 구현된 부분의 전부예요

저번시간에도 말했지만 과자 이름가지고 뭔가를 만들었다기에는 너무 부족하잖아요
그래서 최소한의 쇼핑몰 느낌을 낼 수 있도록 각 과자의 요소를 담을 수 있도록 장바구니를 만들거예요
그 담는 방법으로는 체크박스를 이용할 거구요
담고 나서는 과자의 갯수까지 바꿀 수 있도록 구현해보겠습니다.

장바구니 & 체크박스 추가

우선 장바구니 state와 장바구니가 들어갈 공간을 확보해 줄게요

import { useState } from 'react';

function App() {
  let [과자이름] = useState(['허니버터칩', '홈런볼', '꼬북칩']);
  let [장바구니, 장바구니담기] = useState([]);

  return (
    <>
      <h4>과자목록</h4>
      {
        과자이름.map((과자이름, idx) => <p key={idx}>{과자이름}</p>)
      }

      <h4>장바구니</h4>
    </>
  )
}

export default App;

let [장바구니, 장바구니담기] = useState([]);
과자를 담을 장바구니를 만들고

<h4>장바구니</h4>
장바구니의 이름도 추가하였습니다

이로써 장바구니를 담기위한 밑그림이 그려졌습니다.
제가 장바구니에 담기위한 방법으로 체크박스를 이용한다고 했죠
map으로 돌면서 각 요소의 이름 옆에 체크박스를 달아 줘 볼게요

import { useState } from 'react';

function App() {
  let [과자이름] = useState(['허니버터칩', '홈런볼', '꼬북칩']);
  ...

  return (
    <>
      <h4>과자목록</h4>
      {
        과자이름.map((과자이름, idx) => { 
					<p key={idx}>{과자이름}</p>
					<input type='checkbox' />
				})
      }

      ...
    </>
  )
}

export default App;

자 체크박스를 추가 했습니다.

근데 체크박스를 해도 아무런 변화가 없어요
저희는 이 박스가 체크상태로 변하면 장바구니에 담고 싶은거란 말이죠
그러므로 onChange함수를 추가해줍니다.

<input type='checkbox' onChange={(e) => {
    if (e.target.checked) {
        const 새바구니 = [...장바구니, 과자이름];
        장바구니담기(새바구니);
    }
    else {
        const 새바구니 = 장바구니.filter(el => el !== 과자이름);
        장바구니담기(새바구니);
    }
	}
} />

뭔가 갑자기 복잡한 코드가 나왔죠
보통 이런 코드는 분기점을 잘 파악해야 무엇을 하려고 하는건지 이해하기 쉽습니다.
if 와 else로 나뉘는 곳이 그 분기점이 될 수 있는 데

if (e.target.checked) 는 내가 event를 발생 시킨 target의 checked 값이 true라면 ~ 이라는 가정이고
그렇지 않을 경우라면 false값이 되겠죠

HTML에서 checkbox 는 기본적으로 checked라는 속성을 가지고 있고 그 값이 true 아니면 false라는 점을 이용한 부분입니다.

혹시라도 여기서의 e가 무엇인지 궁금하시다면 아래처럼 코드를 작성해서 확인하시면 됩니다.

<input type='checkbox' onChange={(e) => {
		console.log('e >>',e);
		console.log('e.target >>',e.target);
		console.log('e.target.checked >>',e.target.checked);
	}
} />

자 이제 e.target.checked 이 true일때 어떤 코드를 실행시킬 지 봅시다

if (e.target.checked) {
    const 새바구니 = [...장바구니, 과자이름];
    장바구니담기(새바구니);
}

위에서 만들어줬던 state 장바구니를 가져와서
현재 선택한 항목의 과자 이름을 함께 담는 새 배열을 만듭니다.

여기서 새 배열을 만든다는 점에 주목해야 하는데
기존의 state는 불변성을 유지해 주어야만 합니다. 내가 바꾸고 싶은 대로 막 바꾸면 에러를 띄우겠다는 소리죠

실제로 반영이 안되기도 합니다
그래서 state변경 함수라는걸 이용해서 state를 변경해야하는데
state변경 함수는 state를 새로운 값으로 덮어 씌우는 함수예요

이 덮어 씌울 값을 만들기 위해 새 배열을 만들 필요가 있는 겁니다.
이렇게 새 배열을 만들어 장바구니를 갱신하면 실제로 장바구니가 바뀌게 될텐데
이를 한번 확인해 볼까요

import { useState } from 'react';

function App() {
    let [과자이름] = useState(['허니버터칩', '홈런볼', '꼬북칩']);
    let [장바구니, 장바구니담기] = useState([]);

		console.log(장바구니);

    return (
        <>
            <h4>과자목록</h4>
            {
                과자이름.map((과자이름, idx) => { 
									<p key={idx}>{과자이름}</p>
									<input type='checkbox' onChange={(e) => {
                        if (e.target.checked) {
                            const 새바구니 = [...장바구니, 과자이름];
                            장바구니담기(새바구니);
                        }
                        else {
                            const 새바구니 = 장바구니.filter(el => el !== 과자이름);
                            장바구니담기(새바구니);
                        }
                        }
                    } />
                })
            }

            <h4>장바구니</h4>
        </>
    )
}

export default App;

자 이런식으로 리렌더링이 될 때마다 장바구니가 바뀌는 것을 확인해 볼 수 있습니다.

자 그럼 한번 더 눌러서 장바구니에서 있는 과자 이름을 빼는 것도 구현해보겠습니다.
장바구니에 있는 과자이름을 뺀다는 것은
장바구니에서 과자이름이 아닌 애들만 담은 새로운 배열을 준다는 말과 같죠

지금 과자 이름 빼고 만든거나
지금 과자 이름을 원래에서 빼는거나 같은 거잖아요

['허니버터칩', '홈런볼', '꼬북칩'] 에서 홈런볼 뺀다고 하면
허니버터칩이랑 꼬북칩만 담겨있는 배열 ['허니버터칩', '꼬북칩'] 이 홈런볼 뺀거랑 같은 말 아니곘습니까

이런 논리대로 현재 선택한 과자이름만 뺀 배열로 장바구니를 갱신해보겠습니다.

else {
    const 새바구니 = 장바구니.filter(el => el !== 과자이름);
    장바구니담기(새바구니);
}

filter는 조건식이 참인 요소만을 담는 새로운 배열을 만들어주는데
현재 선택한 과자 이름과 같지 않은 요소만 참으로 받겠다는 뜻이니까
과자 이름 빼고 배열로 만들겠다는 뜻입니다.

한번 더 정리하고 갈게요
과자이름 추가할때는 const 새바구니 = [...장바구니, 과자이름];
과자이름 뺄 때는 const 새바구니 = 장바구니.filter(el => el !== 과자이름);
이렇게 이해하고 넘어가시면 됩니다.

컴포넌트 분리

아 근데 코드 너무 길어요
코드의 길이도 줄이고 싶고 혹시 모를 재 사용성을 위해 컴포넌트로 분리하고 싶어요

재사용성이 무슨 말이냐구요
재사용성이 좋다는 말은 원하는 곳에 편하게 코드를 가져와 쓸 수 있다는 말이예요
내가 원하는 구간의 코드를 하나의 개념으로 묶어서 정리해놨다가
필요한 곳에 아무렇게나 갖다 붙혀도 붙을 수 있게끔 컴포넌트로 만들던가 해서
편하게 쓸 수 있으면 같은 코드 반복해서 안쓰니 개발 속도도 빨라지고 오타같은 것도 줄일 수 있고 여러므로 좋겠죠

그래서 저는 과자이름을 항목별로 나열해주는 기능을 하나의 컴포넌트로 묶을거예요
바로 이 부분이요

{
    과자이름.map((과자이름, idx) => { 
                        <p key={idx}>{과자이름}</p>
                        <input type='checkbox' onChange={(e) => {
            if (e.target.checked) {
                const 새바구니 = [...장바구니, 과자이름];
                장바구니담기(새바구니);
            }
            else {
                const 새바구니 = 장바구니.filter(el => el !== 과자이름);
                장바구니담기(새바구니);
            }
            }
        } />
    })
}

네 그럼 바로 공사 들어갑니다
현재 있는 Src 폴더에 Component라는 이름의 폴더를 추가하고
그 안에 ContentSnack.js 이라는 이름으로 자바스크립트 파일을 하나 만들어줬어요

const ContentSnack = () => {
    return (
        <div>			    
						<p key={idx}>{과자이름}</p>
						<input type='checkbox' onChange={(e) => {
	            if (e.target.checked) {
                const 새바구니 = [...장바구니, 과자이름];
                장바구니담기(새바구니);
	            }
	            else {
                const 새바구니 = 장바구니.filter(el => el !== 과자이름);
                장바구니담기(새 바구니);
	            }
            }
		      />
	       })
        </div>
    )
}

export default ContentSnack;

기존에 있던 코드를 그대로 가져왔습니다
근데 과자이름이 뭔지 장바구니가 뭔지 이 스크립트 상에서는 뭐 만들어준게 하나도 없기 때문에
이 함수가 많이 당황을 합니다.

그니까 과자 이름과 장바구니, 장바구니담기 같은 것들을 줄거라고 알려 주기만 하면 돼요

const ContentSnack = ({ 과자이름, 장바구니, 장바구니담기 }) => {
    return (
        <div>
            <span>{과자이름}</span>
            <input type='checkbox' onChange={(e) => {
                if (e.target.checked) {
                    const 새바구니 = [...장바구니, 과자이름];
                    장바구니담기(새바구니);
                }
                else {
                    const 새바구니 = 장바구니.filter(el => el !== 과자이름);
                    장바구니담기(새바구니);
                }
            }
            } />
        </div>
    )
}

export default ContentSnack;

위에 코드와 또 바뀐 점이 있다면
<p key={idx}>{과자이름}</p><span>{과자이름}</span> 로 바뀌었습니다.

밑에서 나올거지만 어차피 key는 컴포넌트 자체에 부여받는 속성이다보니 여기서 사용 하는 것이 아니라서 뺐고
그냥 p 대신 span으로 수정해보고 싶어서 작성하였습니다.

저는 span이 좀더 시맨틱적인 개념에서 낫지 않나 생각했는 데 지금 다시생각해보면 p가 더 낫지 않나 생각이 들기도 합니다 아직 시맨틱태그가 어렵네요

자 어쨋든 props 로 받아오는 과자이름, 장바구니, 장바구니담기를 구조분해할당으로 변수명으로 만들어 사용하고 있고
다시 컴포넌트 ContentSnack 로 내보내게 되었습니다.

다시 App.js로 이 깔끔하게 정리된 컴포넌트를 적용해보겠습니다

import { useState } from 'react';
import ContentSnack from './Component/ContentSnack';

function App() {
  let [과자이름] = useState(['허니버터칩', '홈런볼', '꼬북칩']);
  let [장바구니, 장바구니담기] = useState([]);
  ...

  return (
    <>
      <h4>과자목록</h4>
      {
        과자이름.map((과자이름, idx) => {
          return <ContentSnack 과자이름={과자이름} 장바구니={장바구니} 장바구니담기={장바구니담기} key={idx} />
        })
      }
		...
	)
}

과자이름을 map으로 돌면서 각 인덱스를 key로 사용하고
필요한 props들을 내려줍니다

ContentSnack 이라는 컴포넌트로 만드니 코드도 훨씬 짧아지고
편하게 다시 사용할 수 있게 되었죠? 재사용성이 좋아졌다는 말이예요

장바구니 나열하기

지금 어디까지 했는지 생각해볼게요

과자 이름을 나열하고 그 옆에 체크박스 넣어서 체크하면 장바구니에 해당하는 과자이름이 들어가는 컴포넌트를 만들어 줬어요

이제 이 장바구니를 보여줄 차례입니다.

import { useState } from 'react';
...

function App() {
  ...
  let [장바구니, 장바구니담기] = useState([]);

  return (
    <>
      <h4>과자목록</h4>
      ...

      <h4>장바구니</h4>
      {
        장바구니.map((과자이름, idx) => {
          return (
            <div key={idx}>
							<span>{과자이름}</span>
            </div>
          )
        })
      }

    </>
  )
}

export default App;

이렇게 장바구니에 map 메서드를 사용해서
과자이름이 순서대로 나열될 수 있도록 코드를 작성하였습니다!

아직 기뻐하긴 일러요
우리아직 안한게 하나 남아있습니다

장바구니에 담긴 과자의 갯수를 바꿔보는걸 구현하고 마무리해볼 거예요

장바구니 수량 변경

Input 태그 하나 만들어서 숫자를 직접 입력하거나 버튼 클릭으로 한개씩 늘리고 줄이는 기능을 구현해볼게요

import { useState } from 'react';
...

function App() {
  ...
  let [장바구니, 장바구니담기] = useState([]);
  let [수량, 수량변경] = useState(과자이름.map(el => 1));

  return (
    <>
      <h4>과자목록</h4>
      ...

      <h4>장바구니</h4>
      {
        장바구니.map((과자이름, idx) => {
          return (
            <div key={idx}>
              <span>{과자이름}</span>
              <input type='number' min={1} value={수량[idx]} onChange={(e) => {
						    const 현재수량 = [...수량];
						    현재수량[idx] = e.target.value;
						    수량변경(현재수량);
							}} />
            </div>
          )
        })
      }

    </>
  )
}

export default App;

이중에서 중요한 부분만 빼서 볼게요

let [수량, 수량변경] = useState(과자이름.map(el => 1));

과자의 항목이 추가가 될것을 고려하여 과자이름의 수(=과자 항목 수)만큼의 길이를 가진 배열으 만들고
그 값을 1로 채웁니다

현재 과자항목이 ['허니버터칩', '홈런볼', '꼬북칩'] 이니
수량은 [1, 1, 1]이 되겠네요

<span>{과자이름}</span>
<input type='number' min={1} value={수량[idx]} />

일단은 number타입의 input으로 입력도 되고 하나씩 늘리고 줄일 수도 있게 했구요

최소값은 1로 고정하구요
과자이름이란 배열과 수량이란 배열의 index값은 서로 대응하니
값은 현재 해당하는 idx의 값을 가지고 수량이라는 배열에서 값을 가져옵니다.

이렇게 하면 각 항목별로 1씩은 가져올 수 있는 데
내가 원하는 값으로 변화시키기지 못합니다
그래서 onChange함수를 추가해볼게요

<span>{과자이름}</span>
<input type='number' min={1} value={수량[idx]} onChange={(e) => {
    const 현재수량 = [...수량];
    현재수량[idx] = e.target.value;
    수량변경(현재수량);
}} />

수량 변경함수에 새로운 배열로 덮어씌워줘야 하기 때문에 [...수량] 로 기존의 배열을 복사합니다.

그리고 현재 idx값만 쏙 바꿔치기 하고 다시 수량 변경에 반영만 하면
값을 늘리든 줄이던 원하는 값을 넣든 바뀌게 된다는 거죠

완성코드

이렇게 저번시간 코드 리뷰 부터 장바구니 & 체크박스 추가도 해보고
컴포넌트 분리, 장바구니 나열하기, 장바구니 수량 변경 등 다양한 작업을 해보았는데

현재 완성 코드는 이러합니다

App.js

import { useState } from 'react';
import ContentSnack from './Component/ContentSnack';

function App() {
  let [과자이름] = useState(['허니버터칩', '홈런볼', '꼬북칩']);
  let [장바구니, 장바구니담기] = useState([]);
  let [수량, 수량변경] = useState(과자이름.map(el => 1));

  return (
    <>
      <h4>과자목록</h4>
      {
        과자이름.map((과자이름, idx) => {
          return <ContentSnack 과자이름={과자이름} 장바구니={장바구니} 장바구니담기={장바구니담기} key={idx} />
        })
      }

      <h4>장바구니</h4>
      {
        장바구니.map((과자이름, idx) => {
          return (
            <div key={idx}>
              <span>{과자이름}</span>
              <input type='number' min={1} value={수량[idx]} onChange={(e) => {
                const 현재수량 = [...수량];
                현재수량[idx] = e.target.value;
                수량변경(현재수량);
              }} />
            </div>
          )
        })
      }

    </>
  )
}

export default App;

Component → ContentSnack.js

const ContentSnack = ({ 과자이름, 장바구니, 장바구니담기 }) => {
    return (
        <div>
            <span>{과자이름}</span>
            <input type='checkbox' onChange={(e) => {
                if (e.target.checked) {
                    const 새바구니 = [...장바구니, 과자이름];
                    장바구니담기(새바구니);
                }
                else {
                    const 새바구니 = 장바구니.filter(el => el !== 과자이름);
                    장바구니담기(새바구니);
                }
            }
            } />
        </div>
    )
}

export default ContentSnack;

오늘은 아주 헤비하게 많은 내용들을 다뤄보았습니다

기능 하나를 구현할 수 있을 정도의 많은 문법을 사용해 보았는 데
다음시간에는 모달창 하나 구현하는 가벼운정도의 구현을 가져와볼테니 이번 글이 너무 어렵다고 실망하면 안됩니다.

사실 그렇게 어렵지도 않잖아요.. 그렇다고 해주세요 ㅋㅋㅋㅋ

오늘도 긴 글 읽느라 수고 많으셨습니다.

💻 세줄 요약

반복되는 DOM요소는 Component로 빼는 게 좋다
e.target 마냥 헷갈리는 부분있으면 console.log 찍으면서 내가 뭘 건드려야하는지 확인해라
filter(el => el !== 뺄 거)

profile
종착지이자 거점 A Destination

0개의 댓글