[원티드 프리온보딩] 과제회고 및 소감

이창훈·2022년 11월 22일
0

회고

목록 보기
3/9
post-thumbnail

도전했던 점 1. Next.js를 이용한 ServerSideRendering

CRA 와 Next.js, CSR과 SSR. SEO와 같은 키워드들이 어떤 것을 의미하는지는 알고 있었지만 실제 그것이 어떻게 내가 만든 코드와 어플리케이션에 적용되는지 체험하지 못했다. 막연히
Next.js를 사용하면 개발자 도구에서 태그와 그 내용들이 채워지는 줄 알았고, CRA와 Next.js의 차이점은 라우팅 방식의 차이라고 생각했다. 하지만 코드스테이츠 수료 이후 Next.js에 대한 발표 준비를 하면서 Next.js가 어떻게 동작하는지 조사하였고, SSR과 CSR이 결합된 Universal Rendering방식이라는 것을 알게 되었다.

1-1. CSR에서도 동적으로 meta태그를 바꾸면 SEO문제를 해결 할 수 있지 않을까?

2주차 첫 번째 과제 동적으로 meta 태그 변경해주는 코드
CSR에서 위 링크와 같이 meta 태그를 변경하면 실제로 개발자도구에서 확인 했을 경우 meta 태그가 변경되었다. 하지만 카카오톡, facebook에 링크를 보낼 경우 미리보기에는 meta 태그의 정보가 적용되어 나오지 않았다. 그 이유는 클라이언트에서 meta태그를 변경하더라도 SEO는 서버에서 보내주는 meta태그만 수집하여 갔기 때문이다.


이러한 경험을 통해 Next.js와 SEO의 작동방식을 실감했고 그 강력함과 필요성을 느끼는 계기가 되었다.

1-2 Next.js도 그냥 SEO를 떠먹여주지는 않는다.

참고
쿼리 캐시에 데이터 채워 넣기
react-query공홈
React-Query 어떻게 써야 할까요?
Next.js를 CSR 사용하듯이 코드를 작성하였고 당연히 SEO가 잘 적용되었고 개발자 도구에서 내가 작성한 코드들이 잘 담겨 있을거라 기대했다. 하지만 그렇지 않았고 나는 Next.js를 그저 다른 방식의 라우팅을 제공하는 프레임워크로만 이용하고 있었다.

Next.js에 getStaticProps와 getServerSideProps 를 이용하여 pre-rendering을 할 수 있다.

  1. getStaticProps는 빌드 시에 딱 한번만 호출되고 이후에 수정이 되지 않는다.

  2. getServerSideProps는 page가 요청받을 때 마다 호출되어 pre-renderig을 해준다. pre-render가 필요한 동적 데이터가 있는 page에 사용한다. 예를 들어 이번 과제에서 '/list'에서 계좌를 생성할 경우 페이지에 추가된 계좌가 바로 적용할 경우, '/list/[id]'에서 계좌를 수정할 때 수정내용이 적용된 화면을 보여줄 경우 사용하였다.

React-Query의 prefetchQuery와 dehydratedState 이용하여 pre-fetch를 진행하였다. prefetchQuery는 server side에서 데이터를 미리 받아 오거나, 화면 전환시 컴포넌트 마운트 전에 데이터를 미리 받아오기 위해 사용한다. 다시 말해 컴포넌트가 렌더링 되기 전에 페칭을 시작할 수 있다. prefetchQueries는 자바스크립트 번들이 평가되는 즉시 실행된다.

이렇게 멋지게 데이터들이 들어찬것을 볼 수 있다.

도전했던 점 2. React-Query

React-Query를 이용하기 전에 비동기 데이터들을 관리할 때 http요청으로 받아온 데이터를 useState로 관리하던지, recoil의 경우 useRecoilValueLoadble, redux의 경우 thunk나 redux-saga를 통해서 사용해서 관리 하였다. 그리고 블로그에서 recoil과 React-Query의 조합이 좋다는 게시글을 봤을 때도 그래서 이렇게 사용하면 어떤 시너지가 나서 좋은건데? 라는 의문이 떠나지 않았다.

recoil과 react-query를 이해하기전 작성한 비동기 데이터관리
useRecoilValueLoadable을 이용한 비동기데이터 관리

const ToDo = () => {
  const [toDoState, setToDoState] = useRecoilState(todo)
  const queryClient = useQueryClient()
  const {data, isLoading, isError, error } = useQuery(
    ['todo'], getTodos,{
      onError : (err) => {
        console.log(err)
      },
      onSuccess : (data) => {
        console.log(data)
        setToDoState(data)
      }
    }
  )

useQuery를 통해 받아온 data를 다시 recoil을 통해 사용했었다.

React-Query를 사용하면서 깨닫게된 Recoil과 React-Query를 함께 사용할 때의 시너지는
client state는 recoil로 server state는 React-Query로 관리하면 좋다는 의미 였다.

다크 모드의 상태와 같이 클라이언트 UI에 관여된 component들에게 필요한 상태는 recoil을 이용하고 서버로 부터 받아오는 데이터들은 React-Query와 queryKey를 이용하여 전역상태처럼 사용할 수 있다.

또한 React-Query를 사용하면 캐싱관리에 도움을 받을 수 있다.
지금까지 작성한 코드들은 매 번 데이터를 새로 패칭 받아오는 것의 비용에 대해서는 전혀 고려하지 않았다. 그래서 데이터가 필요한 순간이라면 고민하지 않고 데이터를 받아오도록 하였다.
하지만 그게 이미 받아왔던 데이터라면 그것을 기억했다가 같은 데이터 요청이 올 경우 꺼내 써보자는 생각을 하지 않았었다. 하지만 앞선 과제들에서 데이터를 패치받아서 보여주는 것과 캐싱된 데이터를 꺼내서 보여주는 것이 화면상에서 깜빡이는 현상과 같은 차이를 준다는 것을 피부로 느낄 수 있었고 캐싱의 중요성을 실감하게 되었다.

2-1. useQuery, useQueries

이 React-Query를 사용하는 과정에서 가장 이해하기 어려웠던 부분은 queryKey였다.

나는 한가지 이슈가 더 있었다. Next.js를 http요청을 보내는 과정에서 CORS에러가 발생하였고 이에 대한 해결 방법으로 next.js에 fallback을 걸어두었다코드.

하지만 이에 따라 클라이언트에서는 'http://localhost:4000//list'로 요청을 보낼 때 http://localhost:4000을 생략 할 수 있게된다.

하지만 getServerSideProps, 즉 서버에서 요청을 보낼 때는 'http://localhost:4000'을 생략할 경우 요청이 보내지지 않는 이슈가 발생했다.
그래서 서버에서 요청을 보낼 때와 클라이언트에서 요청을 보낼 때의 엔드포인트를 다르게 해야했고 서버와 클라이언트에서 쿠키를 꺼내는 방법이 달랐기에 같은 요청에 대해서 클라이언트에서 보내는 요청과 서버에서 보내는 요청을 구분해서 처리해야 했다.

이렇게 할 경우에 useQuery에 같은 queryKey지만 QueryFn이 달라도 되는지 궁금하여 조사를 하였다.

getServerSideProps에서 prefetchQuery를 사용하는 상황에서 동일한 쿼리키에 다른 요청을 보낼 수 있었다.

그리고 쿼리키에 대한 결과가 캐싱되어 있을 경우 클라이언트에서의 요청은 가지 않게 된다.
useMutation

참고 : 김맥스 블로그 React-Query 살펴보기
참고 : 지나가던 개발자(React Query Key 관리)
참고 : 쿼리(Query)는 무엇일까 , querykey??

2-2. useMutation

앞선 두 번의 프로젝트에서 나의 최대 고민은 http요청을(post, patch, put) 보냈을 때 새로운 데이터가 반영된 화면을 렌더링 하는 것 이었다.

  1. 프리 프로젝트에서는 http 요청 이후 다시 한번 get 요청을 보내 새로운 정보를 받아오려 했다. 데이터를 받아오기도 했지만 때에 새로운 데이터를 받아오지 못하기도하여 새로고침을 하는 방식으로 구현하였다.

  2. 메인 프로젝트에서는 위의 문제를 해결하기 위해 http요청의 응답으로 새로운 데이터가 적용된 데이터를 받아오게 하였다. 그리고 받아온 데이터를 setState를 통해 화면을 새롭게 렌더링 하였다. 위와 같은 방식을 optimistic update(낙관적 업데이트 : 서버 state가 http 요청에 잘 반영될 것이라 낙관적으로 생각하고 UI를 업데이트 하는 것)라고 한다는 것을 이후에 알게 되었다.

  3. mutation을 직역하면 돌연변이라는 뜻이다.
    invalidate를 직역하면 무효화하다라는 뜻이다.

const { data, isLoading, mutate, mutateAsync } = useMutation(mutationFn, options);

onEdit이 실행되면 돌연변이가 발생하여 ['account']쿼리키를 가지는 데이터의staleTiem,cashTime을 무시하고 무조건 새로운 데이터를 가져올 수 있게 됩니다. 그리고 위의 동작을 실행시키는 것 즉 useMutation을 조작하는 것이 mutate이다.

  const { mutate } = useMutation(onEdit, {
    onSuccess: () => queryClient.invalidateQueries(['account']),
  })
  
<form onSubmit={handleSubmit(mutate)}> </form>

그외 사용했던 라이브러리

react-form-hook

라이브러리 없이 React에서 input의 value를 다루는 대중적인 두 가지 방법이 있다.

1.useState와 onChange 속성에서 setState를 걸어주는 방법

const [value, setValue] = useState('')

const handleOnchange = (event) =>{
	setValue(event.target.value)
}
<input value={value} onChange={handleOnChange} />
  1. useRef를 사용하는 방법
const inputRef = useRef()
<input ref={inputRef} />
const print=()=>{
  const value = inputRef.current.value.
  console.log(value)
}

1번 방법을 사용하게 될 경우, 다루는 value가 많아 질 경우 중복되는 코드가 많이 질 수 있다.(hook을 만들 거나 state를 객체로 다루는 방법으로 그 문제를 해결할 수 있지만 치명적인 단점은 값이 바뀔 때 마다 화면이 새로 렌더링 된다는 것이다.

2번 방법을 사용하게 될 경우, 값이 바뀌더라도 새로 렌더링 되지 않는다.

react-form-hook을 이용하게 될 경우, 값이 바뀌더라도 새로 렌더링 되지 않는다. 이번 과제에서는 유효성 검사를 다루지 않았지만 유효성검사코드 관리에 적합할 것으로 기대된다.

//EditFrom
import { useForm } from 'react-hook-form'
const { register, handleSubmit } = useForm()
...
  <form onSubmit={handleSubmit(mutate)}>
   <select{...register('broker_id')}>
  {BROKER_LIST_OPTON.map((option, idx) => (
    <option 
      key={idx} 
      value={option.brokerCode} 
      selected={option.brokerCode === data?.broker_id}>
      {option.brokerName}
    </option>
  ))}
    </select>
  </form>

react-hook-form을 적용한 부분

table 에러

참고 :
table에는 thead와 tbody가 필요해요(feat. react 형님들)

<div> cannot appear as a child of <tbody>
<td> cannot appear as a child of <a>

위와 같은 테이블 태그들의 부모 자식 관계에대한 에러가 발생하였다.

<a><td>{convertBrokerId(account.broker_id)}</td></a>

위의 코드를 아래와 같이 바꾸어주었다.

<td><a>{convertBrokerId(account.broker_id)}</a></td>

table을 사용할 경우에는 table 태그들 사이의 관계에 유의하며 사용해야 한다.

발생 했던 에러

!를 이용한 에러 해결

참고 [TS] 📘 타입스크립트 느낌표(!) - Non-null 단언 연산자

object is possibly 'undefined'.ts(2532) config.headers

느낌표를 넣어주지 않았을 때 위와 같은 에러가 발생하였다.

axiosInstance.interceptors.request.use(
  function (config: AxiosRequestConfig<any>) {
    const accessToken = typeof window !== 'undefined' ? sessionStorage.getItem('token') : null;
    if (accessToken) {
      config.headers!.Authorization =accessToken
    }
    return config;
  },
  function (error) {
    return Promise.reject(error);
  }
);

타입스크립트에서 변수 앞이 아닌, 뒤에 느낌표(!)를 사용하면 피연산자가 Nullish(null이나 undefined) 값이 아님을 단언할 수 있다.

마치며

메인프로젝트에서는 스스로 부족하다고 느꼈고 꼭 사용해보고 싶었던 기술스택들(TypeScript, React-Query, Next.js)를 모두 사용해 볼 수 있었어서 스스로 많은 공부를 하는 기회가 되었으며 한편으로는 지금 까지 짜온 코드들에 대해서 되돌아보는 계기가 되었다. 내가 지금까지 갈증을 느끼고 있었던 것들을 해소한 느낌이었다. 아는 만큼 보인다라는걸 느낀다. 분명 주변에서 이러한 정보들에 대해서 이야기했더라도 내가 필요성을 느끼지 못하고 문제 해결방법이라는걸 인지하지 못한다면 지식은 해결책이 되어주지 못한다. 또한 그러한 문제와 갈증을 마주하기 위해서는 부딪혀야 한다. 내가 킨 콘솔과 터미널이 나의 가장 큰 스승님이다.

profile
실패를 두려워하지 않고 배우고 기록하여 내일의 밑거름 삼아 다음 단계로 성장하겠습니다.

0개의 댓글