Next.js 기초 2 - component, props, state

동화·2023년 4월 29일
0

코딩애플

목록 보기
7/7
post-thumbnail

component

cart/page.js

export default function Cart() {
  return (
    <div>
    	<h4 className="title">Cart</h4>
        <div className="cart-item">
            <p>상품명</p>
            <p>$40</p>
            <p>1</p>
        </div>
    	<div className="cart-item">
   			<p>상품명</p>
    		<p>$40</p>
    		<p>1</p>
    	</div>
    </div>
  );
}

이렇게 생긴 cart-item 하나를 컴포넌트로 만들어 쉽게 반복 사용할 수 있다.

function CartItem() {
  return (
    <div className="cart-item">
      <p>상품명</p>
      <p>$40</p>
      <p>1</p>
    </div>
  );
}

이렇게 만들어주고,

export default function Cart() {
  return (
    <div>
      <h4 className="title">Cart</h4>
      <CartItem />
      <CartItem />
      <CartItem />
    </div>
  );
}

이렇게 추가해주면
원하는 개수만큼 생성할 수가 있다.

server component

위에 만든 것처럼 아무데나 대충 만든건 server component가 된다.
server component는 html안에 자바스크립트 기능(예를 들면 onClick={}같은..)을 넣을 수가 없다.
useEffect, useState 등을 사용할 수 없다.

Error: Event handlers cannot be passed to Client Component props.
<button onClick={function} children=...>
If you need interactivity, consider converting part of this to a Client Component.

근데 왜 server component 불편하게 쓰냐? 장점이 있으니깐!
일단 로딩이 빠르당 검색엔진 노출 등에도 유리하다.

client component

server component와 다르게 파일 상단에 'use client'라고 쓰고 만든 건, client component가 된다.
client component 안에서는 html 내 자바스크립트 기능이라든지, useEffect, useState 등을 사용할 수 있다.
client component는 자바스크립트가 많이 필요해 로딩 속도가 느리고, 또 hydration이 필요하기 때문에 또 느리다.

hydration : html 유저에게 보낸 후에 자바스크립트로 html 다시 읽고 분석하는 일

결론

큰 페이지는 server component,
자바스크립트 기능들이 필요한 페이지에만 client component를 이용!




변수 가져오기 📩

다른 파일에서 생성한 변수를 가져와 보자!

data.js

let age = 20;

export default age;

cart/page.js

import age from "./data";

...
<p>상품명 {age}</p>
...

import와 export를 적절히 해주면 쉽게 사용할 수 있다.



변수가 2개 이상일 때

data.js

let price = 20;
let name = "바나나";

export { price, name };

export 하고 싶은 변수가 두 개 이상이면, 중괄호{}를 사용하여 export 한다.

cart/page.js

import { price, name } from "./data";

...
	<div className="cart-item">
     	<p>{name}</p>
      	<p>${price}</p>
      	<p>1</p>
    </div>
...

그리고 불러올 때도 중괄호를 사용해 준다.






Props

cart/page.js

export default function Cart() {
  let shoppingCart = ["Tomatoes", "Pasta"];
 ...

상품의 data를 하나 만들어주고, 이를 출력하고 싶으면
아까 만들었던 컴포넌트에서 {product[0]}를 해주면 될 것 같지만,
fucntion에서 만든 변수는 그 함수 내에서만 사용할 수 있다.
부모컴포넌트->자식컴포넌트로 변수를 사용흐고 싶으면 props를 이용해주기!

	<CartItem product={shoppingCart[0]}/>
     // <자식컴포넌트 작명={전해줄데이터} /> 
    
...
	function CartItem(props) {
     return (
       <div className="cart-item">
         <p>{props.product}</p>
         <p>${price}</p>
         <p>1</p>
       </div>
 		);
	}	

이렇게 해주면 잘 가져올 수 있음.

  • props는 <CartItem 이런거={이런거} 저런거={저런거}> 이렇게 많이 전송가능
  • 일반 문자데이터 전송하려면 중괄호 없이 <CartItem 어쩌구="어쩌구"> 해도 가능






수량 버튼 ☎️

list/page.js

let [count, setCount] = useState(0);

useState를 이용해서 변수에 담아줬다.

일반변수는 변경되어도 변수가 들어있는 html에 자동으로 반영되지 않으나, state가 변경되면 state가 들어있는 html도 자동으로 재렌더링이 된다.

위에서 설명했듯 상단에

"use client";

를 꼭 써주어야 함! 왜냐면 useState는 client component에서만 쓸 수 있기 때문!
그리고 버튼에 onClick 함수를 추가해 화면에 수량 + - 가 실행되게 해주었다.

  <button
      onClick={() => {
      count <= 0
          ? (0, alert("더 줄일 수 없습니다!"))
          : setCount(count - 1);
      }}
  >
      -
  </button>
  <span>{count}</span>
  <button
      onClick={() => {
      setCount(count + 1);
      }}
  >
      +
  </button>

map으로 했더니 얘네 하나 누르니까 나머지 다 눌러진다...
이거 이런식으로 장사하면 순 사기꾼이 되니 수정해주어야 함.
그러려면 일단 state 부터 각각 넣어주어야 한다.
하지만 그러면 귀찮으니 이렇게 해보도록 함.

let [count, setCount] = useState([0, 0, 0]);

결과

{grocery.map((a, i) => {
    return (
        <div className="food" key={i}>
        <img src={"food" + i + ".png"} className="food-img" width={50} />
        <h4>{a} $40</h4>
        <button
            onClick={() => {
            let copy = [...count];
            count <= 0
                ? (0, alert("더 줄일 수 없습니다!"))
                : (copy[i]++, setCount(copy));
            }}
        >
            -
        </button>
        <span> {count[i]} </span>
        <button
            onClick={() => {
            let copy = [...count];
            copy[i]++;
            setCount(copy);
            }}
        >
            +
        </button>
        </div>
    );
})}

state를 ...문법(spread operator)으로 복사하고 state의 [i]번 항목을 +1 해준 다음 setCount로 넣어주면 된다.


spread operator

왜 ...를 이용해서 복사를 먼저 하냐면, set어쩌구()를 하게 되면 컴퓨터는 기존state와 신규state의 일치여부 부터 검사해본다.
검색해보고 같으면 state를 변경해주지 않음.
그러니까,

count[0]++
setCount[count]

이렇게 해봤자 복사해주지 않는다는 것이다.

++를 했는데 왜?.?

array/object자료를 let arr = [1,2,3] 요런식으로 하나 만들면
RAM에다가 몰래 저장이 되고, let arr 변수는 그 자료가 어디있는 거다~ 하는 화살표만 담겨있는 것이다.
그러니 그 자료를 수정한다고 해서 화살표를 타고 들어간 RAM값이 수정될 뿐 그 변수에 담긴 화살표는 변하지 않기 때문에,
기존state와 신규state를 비교하면 (==로 비교하기 때문에) 변수에 저장된 화살표만 비교해주기 때문에 같다고 나오는 것이다.

let arr = [1,2,3]
let arr2 = arr
arr2[0]++

arr에 있던 걸 arr2에 복사를 한다고 해도 화살표만 복사될 뿐이라,
콘솔창에 입력해보면
++ 된 값이 아닌 그대로 1이 출력될 뿐더러
arr == arr2 는 true가 나온다.

참고 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures

근데 여기서 만약에 스프레드 연산자를 이용하면,

let arr = [1,2,3]
undefined
let arr2 = [...arr]

완전 독립적인 복사본을 생성해주는 것~
깊은 복사에서 자세히 알아볼 수 있는 부분이다.


아무튼 이렇게 해주었는데 0 이하로 내려갔을 때 개수가 -가 되었다.
처음에 삼항연산자에서 간단히 copy부분만 수정해줘서
0 으로 입력했던게 사실상 아무 의미가 없게 된 것.

삼항연산자를 뜯어고쳐야 했다.

<button
    onClick={() => {
        let copy = [...count];
        copy[i] <= 0
           ? ((copy[i] = 0), alert("더 줄일 수 없습니다!"))
           : (copy[i]--, setCount(copy));
            }}
>
     -
</button>







0개의 댓글