5월 31일, 6월 2일(React, 쇼핑몰 만들기)

JY·2022년 5월 31일
0
post-thumbnail

쇼핑몰 만들기

1. styled-components


장점

  • CSS파일을 열지 않아도 된다.
  • styled-components를 이용한 스타일링은 스타일이 다른 JS파일에 간섭하지 않는다. (Detail.js에서 사용했다면 Detail.js에서만 적용된다는 것!)
  • 페이지 로딩시간 단축
    • 파일을 별도로 만들어주는 것이 아니라 <style></style>에 주입해준다.

단점

  • JS파일이 매우 복잡해진다.
  • 중복 스타일은 컴포넌트 간 import해서 가져올텐데 이렇게 되면 CSS파일을 사용하는 것과 다를 바가 없다.
  • 협업 시 CSS 담당의 숙련도 이슈가 있을 수 있다.

🤔 CSS파일 생성 시 작명
컴포넌트명.module.css로 하면 해당 컴포넌트에만 종속되는 CSS파일을 생성할 수 있다.


버튼을 만들고 싶다면?

CSS파일 사용 시에는 <button>을 만들어서 className을 넣고 CSS파일에서 스타일을 지정해야 한다.

하지만 라이브러리를 사용하면 JS파일에서 전부 해결할 수 있다.
이렇게 생성되는 것은 컴포넌트 이므로 사용 시 <YellowBtn>버튼</YellowBtn>처럼 컴포넌트를 호출하는 방식으로 사용해야 한다.

let YellowBtn = styled.button`
 background: yellow;
 color: black;
 padding: 10px;
`

오렌지색 버튼이 필요하다면?

컴포넌트를 또 만드는 것이 아니라 props 문법을 사용하면 된다.

let YellowBtn = styled.button`
  background: ${props => props.bg};
  color: black;
  padding: 10px;
`

<YellowBtn bg="Yellow">Yellow</YellowBtn>
<YellowBtn bg="Orange">Orange</YellowBtn>

👉 결과

🤔 참고

  • 간단한 프로그래밍도 가능하다.
    color: ${props => props.bg == 'blue' ? 'white' : 'black'};
  • 기존 스타일도 복사 가능하다.
    let NewBtn = styled.button(YellowBtn)`
     ...
    `

2. Lifecycle, useEffect()


Lifecycle

컴포넌트의 Lifecycle
Mount(페이지에 장착된다.) → Update(가끔 업데이트도 된다.) → Unmount(필요 없으면 제거된다.)

이걸 알아서 뭘 하느냐? → 중간중간 코드를 실행할 수 있다.


useEffect()

useEffect 안에 있는 코드는 렌더링이 다 끝난 후(= HTML을 다 그려준 뒤), 실행된다. 따라서 useEffect에서는 주로 다음과 같은 작업을 한다.

  • 어려운 연산
  • 서버에서 데이터 가져오는 작업
  • Timer

🤔 clean up function
clean up function은 mount 시 실행 안 되고, unmount 시 실행된다.

clean up을 하지 않을 시, 만일 2초 사이에 재랜더링 된다면 useEffect 안에 있는 코드를 또 실행하고, 요청이 끝나기도 전에 또 요청할 것이다. → 버그 발생!

useEffect(() => {
  (서버로 데이터 요청하는 코드(2초 소요))
  return () => {
    (기존 데이터 요청은 제거해 주세요)
  }
})

+) input에 숫자가 아닌 값이 들어왔을 때 처리하기

useEffect를 사용해서 유저가 <input>에 숫자 말고 다른 것을 입력하면 "그러지마세요"라는 안내메시지를 출력하자.

// Detail.js

let [input, setInput] = useState("");

...

export default function Detail(props) {
  
  useEffect(() => {
    if(isNaN(input)) alert("그러지마세요");
  }, [input])

  return(
    ...
    <input onChange={(e) => setInput(e.target.value)} />
    ...
  )
}

🤔 isNaN()

  • JS의 문자열이 숫자인지 체크하는 함수이다.
  • isNaN()에 숫자가 들어오면 false를 반환, 숫자가 아닌 것이 들어오면 true를 반환한다.
  • input에 들어오는 값은 모두 string으로 인식하므로 위와 이 함수를 사용해서 판단해야 한다.

3. AJAX


서버에 데이터 요청 시

  • 방법(GET/POST): 보통 가져올 때 GET을 사용하고, 보낼 때 POST를 사용한다.
  • 어떤 자료(URL)

가 필요하다.

그런데 GET, POST 요청 시 페이지가 새로고침되는데, 이는 AJAX를 사용하면 해결할 수 있다. (새로고침 없이 GET/POST) 요청 가능)

🤔 AJAX 쓰려면 옵션 3개 중 택1
1. XMLHttpRequest
2. fetch()
3. axios


데이터 가져오기(GET)

GET 요청: axios.get('url')
요청결과: axios.get('url').then()

// App.js

<button onClick={() => {
  axios.get('https://codingapple1.github.io/shop/data2.json')
    .then((res) => {
    console.log(res.data);
  })
}}>버튼</button>


요청실패(예외처리)

요청실패: .catch()

<button onClick={() => {
  axios.get('https://codingapple1.github.io/sop/data2.json')
    .then((res) => {
    console.log(res.data);
  })
    .catch(() => {
    console.log("실패");
  })
}}>버튼</button>

+) 더보기 버튼

HTML을 생성하는 것이 아니라 state를 조작하면 된다. (products에 데이터를 몇 개 추가해 주세요.)
👉 이미 Card를 데이터 개수만큼 화면에 출력하도록 map으로 짜놨기 때문에!

// App.js

axios.get('https://codingapple1.github.io/shop/data2.json')
.then((res) => {
  let temp = [...products];
  temp.push(...res.data);
  setProducts(temp);
})

// 더 간편한 코드
axios.get('https://codingapple1.github.io/shop/data2.json')
  .then((res) => {
  let temp = [...products, ...res.data];
  setProducts(temp);
})


데이터 전송하기(POST)

  • POST 요청: axios.post('URL', {name: 'kim'})

  • 동시에 요청을 여러 개 하려면:

    Promise.all([axios.get('url1'), axios.get('url2')])
    .then(() => {
    
    })

🤔 원래는 서버와 문자만 주고 받을 수 있다.
object/array 같은 자료형을 주고 받을 수 없다.
그럼 방금 전까지 array 자료를 어떻게 받아온 것인지? 👉 object/array 자료에 따옴표를 쳐놓으면 된다.

"{"name" : "kim"}"

이를 JSON 이라고 한다.

  • axios
    JSON은 문자 취급을 받으므로 서버와 자유롭게 주고받을 수 있다.
    그래서 실제로 결과.data를 출력해보면 따옴표쳐진 JSON이 나와야 한다. 하지만 axios 라이브러리는 JSON → object/array 변환작업을 자동으로 해주므로 출력해보면 object/array가 나오게 되는 것이다.

  • fetch
    참고로, fetch를 이용해도 GET/POST 요청이 가능하지만 그건 JSON → object/array 변환작업을 자동으로 해주지 않아서 직접 바꾸는 작업이 필요하다.
fetch('URL').then(결과 => 결과.json()).then((결과) => { console.log(결과) } )

+) 더보기 버튼 2번 누르면 7,8,9번 상품 가져와서 보여주기

click 횟수를 저장하는 state를 만들어서 관리
처음 클릭했을 때는 데이터 요청을 '~data2'로, 두 번째 클릭에는 '~data3'으로 요청하므로 click+1

`https://codingapple1.github.io/shop/data${click+1}.json`

+) 더보기 버튼 3번 누르면 상품이 없다고 안내문 띄우기

이 예제에서는 클릭을 두 번 했을 때까지만 상품이 보여질 것이므로 click이 2보다 크면 버튼이 안 보이도록 했다.

{
  click < 2
    ? <button onClick={() => {handleClick()}}>더보기</button>
    : null
}

+) 버튼을 누른 직후 '로딩중입니다.' 글자 주변에 띄우기

loading state를 만들어서 관리
true일 때 보여주고, false일 때 숨기기(통신 시작할 때 true로 바꾸고, 통신이 끝나면 false)

// App.js

let [click, setClick] = useState(0);
let [loading, setLoading] = useState(false);

function App() {
  ...
  const handleClick = () => {
    setClick(click += 1);
    console.log(click);

    setLoading(true);
    axios.get(`https://codingapple1.github.io/shop/data${click+1}.json`)
      .then((res) => {
      console.log(res.data);
      let temp = [...products, ...res.data];
      setProducts(temp);
      setLoading(false);
    })
      .catch(() => {
      console.log("실패");
      setLoading(false);
    })
  }
  
  ...
  
  return (
    <Routes>
      <Route path="/" element={
      <>
      ...
      {
        click < 2
          ? <button onClick={() => {handleClick()}}>더보기</button>
          : null
      }
      {
        loading? <div>loading...</div> : null
      }
      </>
      } />
	  ...
    </Routes>
  )
}

👉 결과

4. 탭 UI 만들기


HTML 안에서는 if문을 사용할 수 없으므로 컴포넌트로 빼서 바깥에서 사용했다.
tab이라는 state는 Detail에 있으므로 props를 이용해 TabComponent로 전송해 주어야 한다.

// Detail.js

export default function Detail(props) {
  let [tab, setTab] = useState(0);

  ...

  <Nav fill variant="tabs" defaultActiveKey="link-0">
    <Nav.Item>
      <Nav.Link onClick={()=>setTab(0)} eventKey="link-0">Detail</Nav.Link>
    </Nav.Item>
    <Nav.Item>
      <Nav.Link onClick={()=>setTab(1)} eventKey="link-1">Review</Nav.Link>
    </Nav.Item>
    <Nav.Item>
      <Nav.Link onClick={()=>setTab(2)} eventKey="link-2">QnA</Nav.Link>
    </Nav.Item>
  </Nav>
  <TabComponent tab={tab} />
  
  ...
}

// return을 해줘야 화면에 출력됨!!!
const TabComponent = (props) => {
  if (props.tab === 0)
    return <div>상품정보 탭</div>
  if (props.tab === 1)
    return <div>리뷰 탭</div>
  if (props.tab === 2)
    return <div>QnA 탭</div>
}

🤔 Tip
위에서 if문 3개를 사용한 코드를 좀 더 간단하게 바꿀 수 있다.

const TabComponent = (props) => {
  return [<div>상품정보 탭</div>, <div>리뷰 탭</div>, <div>QnA 탭</div>][props.tab]
 }

👉 결과

5. 전환 애니메이션(transition)


🤔 전환 애니메이션 만드는 step
1. 애니메이션 동작 전 className 만들기
2. 애니메이션 동작 후 className 만들기
3. className에 transition 속성 추가
4. 원할 때 2번 className 부착


1, 2번

/* App.css */

.start {
  opacity: 0;
}
.end {
  opacity: 1;
  transition: opacity 0.5s;
}

3, 4번
tab이 클릭될 때(= tab state가 변경될 때), 즉, tab state가 변할 때 end를 뗐다가 부착하면 된다.
그런데 다음처럼 해도 뗐다 붙이는 게 되지 않는다. 👉 Automatic Batching 때문에!

const TabComponent = (props) => {
  let [fade, setFade] = useState('');

  useEffect(() => {
    setFade('end');

    return () => {
      setFade('');
    }
  }, [props.tab])

  return <div className={`start ${fade}`}>
    { [<div>상품정보 탭</div>, <div>리뷰 탭</div>, <div>QnA 탭</div>][props.tab] }
  </div>
}

🤔 Automatic Batching

setFade('end');
setFade('');

위처럼 state 변경함수들이 근처에 있다면 하나로 합쳐서 최종적으로 한 번만 state를 변경하는 것이다.

state변경함수() ← 재렌더링 X
state변경함수() ← 재렌더링 X
state변경함수() ← 재렌더링 X
state변경함수() ← 재렌더링 O

따라서 setTimeout을 이용해 미세한 시간 차를 두면 나중에 실행하게 할 수 있다.

const TabComponent = (props) => {
  let [fade, setFade] = useState('');

  useEffect(() => {
    setTimeout(() => {
      setFade('end');
    }, 100);
  }, [props.tab])

  return <div className={`start ${fade}`}>
    { [<div>상품정보 탭</div>, <div>리뷰 탭</div>, <div>QnA 탭</div>][props.tab] }
  </div>
}

👉 결과

6. Context API


나중에.. 들을 것...

profile
🙋‍♀️

0개의 댓글