[토이 프로젝트] 솔로몬 Day 2

도깨비젤리

·

2021. 6. 16. 23:11

들어가는 말


A가 사용자에게 질문하는 페이지를 그리고, 내가 추천 결과에 맞는 페이지를 그리기로 했는데, 각고의 노력을 기울인 끝에, 드디어 내가 담당한 페이지의 스케치를 끝냈다. 

 

내 파트 중에서 가장 어려웠던 부분은 동기/비동기 함수의 타이밍이였다. 동기/비동기 개념은 과거에 실습하면서 대충 감을 잡았다고 생각했으나, 비동기적으로 동작하는 함수가 많아지니 이 타이밍을 잡는게 어려웠다.

 

아무튼, 이제 끝이 보이는것 같다. 배포하는 그 날 까지 화이팅.

 

P.S) 지난 글에서 말하는걸 까먹었는데, 시리즈의 제목인 "솔로몬"란 우리 프로젝트의 이름이다.

 

 

 

 

 

TIL


1. 함수 컴포넌트에 쓸 수 있는 useEffect Hook은 componentDidMount, componentDidUpdatecomponentWillUnmount가 합쳐진 것이다.


비록 React 16.8 버전부터 Effect Hook이 정식으로 도입되어, 종래의 함수 컴포넌트도 클래스 컴포넌트처럼 상태 관리를 할 수 있게 되었지만, 이 Hook들이 정확히 Class의 lifeCycle과 1대1로 매치되지는 않는다. 다만, useEffect Hook을 이용하면 대표적인 lifeCycle Method인 componentDidMount, componentDidUpdatecomponentWillUnmount을 흉내낼 수 있다. 

 

  •  useEffect 기본 구조
useEffect(function , deps)


- function : 컴포넌트가 mount/unmount/update 등으로 트리거 되었을때, 수행하고자 하는 작업

- deps: 배열 형태로 구성되어 있으며, 이 안에 있는 element들이 변경되면 useEffect가 다시 시행된다.

 

 

2번째 인자인 deps를 어떻게 설정하느냐에 따라서, useEffect는 Mount도 될 수 있고, Unmount도 될 수 있고 그런다.

 

이번 페이지에서 useEffect를 쓰는 이유는, 처음에 페이지가 마운트 되거나, boardgameId가 변경 되었을 때 API 로 요청을 쏘아서 현재 상태를 바꾸기를 희망하기 때문이다. 그렇기 때문에, useEffect를 다음과 같이 구현해주자

 

 

 const boardgameId = match.params.boardgameId
 const [game, setgameInfo] = useState({image_url:'',name:'',youtubeURL:''})
 const [loading, setloading] = useState(false)


  useEffect(()=>{
    const fetchData = async()=>{ 
      const result = await getBoardgames({ids:boardgameId})
      const gamedata = await getgameDetail(result[0])
      const youtubeURL = await YoutubeRequest(gamedata.name)
      setgameInfo({...gamedata,youtubeURL})
      setloading(true)
    }
    fetchData()
  },[boardgameId])

 

useEffect의 함수부는 익명 함수로 선언되어있고, deps는 [boardgameId]로 설정되어있다. 이 말은 useEffect가 boardgameId에 deps를 가진다는 뜻으로 (deps === dependency), 마운트 되었을 뿐만 아니라 boardgameId가 업데이트 되었을 때만 실행된다는 뜻이다. deps를 빈 배열로 선언하면, 어떤 변수에도 deps를 가지지 않는 다는 뜻이 되어, 마운트 되었을때만 실행된다. (componentDidMount와 유사)

 

근데 주의해야할 점은, 배열 자체를 선언하지 않으면 대참사가 날 수 있다! deps 부분이 비어있으면 useEffect는 어떤 랜더가 일어날때마다 다시 실행되는데, 만약에 useEffect Hook 안에서 setState를 이용해 현재 State를 바꿔준다면???

 

 

 

무한으로 즐겨요~ useEffect Hook!!

 

useEffect 실행 ➡ 훅 안에서 setState ➡ state 업데이트 ➡ render() 실행 ➡ useEffect 실행 (componentDidMount와 같은 이유로) ➡ 훅 안에서 setState ➡ state 업데이트 ➡ render() 실행 ➡ useEffect 실행 ➡ ....

 

 

useEffect 안에서 state를 업데이트 해줄 때는, 반드시 deps로 의존관계를 만들어주도록 하자. 나는 이거 안넣고 테스트 돌렸다가 API 키 짤릴 뻔했다.

 

 

이 코드에서 한 가지 더 특이한 점이 있다면, 함수부 안에 fetchData라는 또 다른 함수가 있는데, 이건 useEffect Hook에서 비동기 함수를 사용하고 싶을때 사용하는 일종의 꼼수이다. 이런 꼼수를 왜 부리냐고??? useEffect 자체를 비동기적으로 만들 수 없기 때문이다.  

 

useEffect Hook에서 비동기 함수를 사용하려면 비동기 함수를 nest 해놓고, 함수부가 끝나기 직전에 호출해야한다는 사실. 잊지 말자

 

 

2. axios 사용법


이상하게 axios는 자주 사용하는데, 어째 쓸 때마다 새로운지 모르겠다. 이번 기회를 통해 axios를 깔끔하게 정리하고 가자.

 

 

Axios : 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리

 

axios의 구성을 axios()에 전달하여 요청할 수 도 있으나 (코드 1), 제공되는 별칭 메서드로 더 간편하게 이용할 수 있다. (코드 2)

 

// 코드 1
axios({
  method: 'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
});

 

// 코드 2

axios.get(url[, config])            // GET
axios.post(url[, data[, config]])   // POST
axios.put(url[, data[, config]])    // PUT
axios.patch(url[, data[, config]])  // PATCH
axios.delete(url[, config])         // DELETE

axios.request(config)
axios.head(url[, config])
axios.options(url[, config])

 

url : 요청에 사용될 서버 URL

data : 요청 본문 (request body)로 전송할 데이터, "PUT" / "POST" / "PATCH" 일때만 사용가능

config : 구성 설정 옵션 . 대표적으로 요청과 함께 전송될 URL 매개변수인 'params'가 있다.

 

  • url을 제외한 나머지 요소들은 전부 객체 꼴이여야한다.

 

3. CORS ( Cross-Origin Resource Sharing, 교차 출처 정책)


사실 이 주제는 따로 글 하나 쓸 정도로 큰 주제인데, 오늘 프로젝트 하면서 CORS에 대해 크게 느낀바가 있어 짧게나마 정리한다.

 

CORS를 이해하려면 먼저 SOP (Same Origin Policy , 동일 출처 정책 ) 에 대해 이해를 하고 있어야한다. SOP는 어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한 하는 보안방식이다. 이게 무엇인지 아는것보다 이게 왜 중요한지를 알아야하는데, 대부분 그 이유를 설명할 때 사용자의 요청을 해커가 가로채서 악용할 수 있다고 설명을 한다.

 

근데 나는 아따마가 굉장히 좋지 않아서 더 쉬운 비유를 만들어내는데 성공했다.

 

만약 당신이 교촌 허니콤보 세트를 주문했는데, 배달부가 네네치킨 박스를 가져왔다고 생각해보자. 어떻게 할껀가???

뭔가 잘못되었다는걸 느끼고 치킨을 건드리지 않은 채 주문을 다시 확인한다면, 당신은 SOP를 적용한 것이다!

 

우리가 치킨 포장지로 치킨이 어떤 브랜드인지 구분하는 것 처럼, 브라우저는 Protocol / Host / Port 3가지로 자료의 출처를 판단한다. 그런데, 클라이언트 단과 서버단이 분리되어있는 앱에서는 양 측이 동일한 출처를 가지지 않는다. 그렇기 때문에, CORS 정책이 있는 것이다.

 

CORS 정책은 비록 자료의 출처가 다를지라도, 사전에 허락한 몇몇 출처에 대해서는 상호작용을 허용하는 식이다. 위의 치킨 예시를 다시 쓰자면, 치킨집이 당신에게 "아 저희 교촌치킨 맞는데, 박스가 다 떨어져서 네네치킨 박스에 보내요" 라고 알려주는 것이라고 볼 수 있겠다.

 

 

웹 개발 하다보면 질리게 많이 보는 이것

즉, 위 에러가 나오게 되는 것은, 우리의 치킨집 (서버)이 당신(클라)에게 치킨(자료)가 동일한 곳에서 만들어진 것이라고 알려주지 않아서 나오게 되는 것이다. 따라서, Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해주면 말끔히 해결이 되는데, 이 방법 외에도 클라측에서 서버로 요청을 보낼 때 동일한 출처로 바꾸는 프록싱을 사용하는 방법도 있다.

 

이 이야기는 나중에 좀 더 깊고 자세히 다뤄보도록 하겠다. 이번 시간에는 브라우저는 기본적으로 동일한 출처를 가지는 정보만 냠냠한다 라는 점만 알아두자

 

P.S) 얘기만 들어보면 CORS가 SOP보다 늦게 나온 정책 같은데, 사실 반대랜다. CORS가 SOP보다 빨리 나왔다고 한다... 대박사건....

 

 

4. String.prototype.substring(start, end)


 

String 객체의 시작 인덱스 (start) 부터 종료 인덱스(end) 까지의 문자열을 반환해주는 메서드이다.

 

  • 만약 indexEnd 가 생략된 경우, substring() 문자열의 끝까지 모든 문자를 추출합니다.
  • 만약 indexStart 가 indexEnd와 같을 경우, substring() 빈 문자열을 반환합니다.
  • 만약 indexStart 가 indexEnd보다 큰 경우, substring() 메서드는 마치 두 개의 인자를 바꾼 듯 작동하게 됩니다.

 

var anyString = 'Mozilla';

// Displays 'M'
console.log(anyString.substring(0, 1));
console.log(anyString.substring(1, 0));

// Displays 'Mozill'
console.log(anyString.substring(0, 6));

// Displays 'lla'
console.log(anyString.substring(4));
console.log(anyString.substring(4, 7));
console.log(anyString.substring(7, 4));

// Displays 'Mozilla'
console.log(anyString.substring(0, 7));
console.log(anyString.substring(0, 10));

 

출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/substring

 

🔥Day 2을 마치며🔥


나의 Day2 결과물

오늘 완성된 추천 페이지 스케치이다. 여기서 추가로 댓글 목록을 pagenation으로 바꾸는 정도만 구현해주면, 이 페이지는 바로 스타일링 단계로 넘어가도 될 것 같다.

 

 

A군의 Day2 결과물

 

한편 A군도 추천 로직은 다 설계했다고 한다. 사용자의 요청을 쿼리로 만들어서 API로 쏘는건데, 마지막에 뜨는 게임 추천 페이지를 react-router를 이용해서 내가 구현한 페이지로 연결해주면 구현은 완료가 된다.

 

근데 말이 쉽지, 앞으로 헤쳐나가야할 난관이 수두룩해보인다. 🤣🤣🤣