[슬랙 클론코딩] 2주차 회고록

jung moon chai·2022년 10월 18일
0

1. React hooks와 Custom hooks


1-1. 리액트 훅스

리액트의 컴포넌트 형식에는 크게 클래스형 컴포넌트와 함수형 컴포넌트로 나누어져 있다.
지금은 클래스형 컴포넌트보다는 함수형 컴포넌트를 권장 하고 있으며 클래스형 컴포넌트에서 라이프사이클을 함수형 컴포넌트에서 리액트 훅스로 대체 되었다.
(그렇다고 클래스형 컴포넌트가 아예 사라진것은 아니다.)

import React, { useState } from 'react';
const Components = () => {
	const [ value1, setValue1 ] = useState('');
	const [ value2, setValue2 ] = useState('');
	const onChangeValue1 = (e) => {
		setValue1(e.target.value)
	}
    const onChangeValue2 = (e) => {
		setValue2(e.target.value)
	}
    return (
    	<>
        	<input 
            	value={value1} 
                onChange={(e) => onChangeValue1(e)}
            />
            <input 
            	value={value2} 
                onChange={(e) => onChangeValue2(e)}
            />
        </>
    )
}

클래스형 컴포넌트에서 복잡하게 state객체 안에 작성하고 this.state.으로 직접 찾아서 setState함수를 썼던거에 비하면 훨씬 작성하기도 편해졌다고 생각한다.


1-2 커스텀 훅스

그렇다면 커스텀훅스는 무엇을 대체하기 위한 것일까?
커스텀 훅스는 고차컴포넌트(High Order Components)를 대체하게 되었지만 개인적으로 정확히 따지자면 대체방안을 위해 만들어진 것이 아니라, 로직을 재활용 하기위해 내가 만드는 훅스라고 생각한다. 그러다 보니 클래스형컴포넌트에서 사용되던 고차컴포넌트를 대체 하게 된 것이라 생각한다.

위 코드에서 중복되는 로직 onchange 함수들을 커스텀 훅스로 만들어보자.

import React, { useCallback, useState } from 'react';

const useInput = (initialValue = null) => {
	const [value, setValue] = useState(initialValue);
	const handler = useCallback((e) => {
		setValue(e.target.value);
  	}, []);
  	return [value, handler, setValue];
}

const Components = () => {
	const [ value1, onChangeValue1 ] = useInput('');
	const [ value2, onChangeValue2 ] = useInput('');
  
    return (
    	<>
        	<input 
            	value={value1} 
                onChange={onChangeValue1}
            />
            <input 
            	value={value2} 
                onChange={onChangeValue2}
            />
        </>
    )
}

중복 함수를 제거하고 useInput이라는 커스텀 훅스를 만들어 사용했다.
매개변수로 초기스테이트값을 받아와서 스테이트에 최초 저장하고, onChange함수를 만들고 스테이트가 변경될때 마다 업데이트 시켜 주었다. 그럼 이제 return값에서 새롭게 업데이트된 값과 onChange함수, 그리고 임의로 스테이트를 업데이트 할 수 있는 setState 함수 3개가 배열로 나오게 된다.


1-3 Typescript와 리액트 훅스

const useInput = (initialValue = null) => {
	const [value, setValue] = useState(initialValue);
	const handler = useCallback((e) => {
		setValue(e.target.value);
  	}, []);
  	return [value, handler, setValue];
}

타입스크립트에서 작성하면 에러가 발생한다. initialValue의 타입이 지정되어 있지 않기때문이다.
가장 간단한 답은 타입의 any를 작성하는 것이다. any타입을 주게 되면 어떠한 타입이든 대부분 받을 수 있다. 하지만 any를 어쩔 수 없이 쓰게 되는 경우도 있긴 하겠으나, 너무 자주 사용한다면 타입스크립트를 쓰는 의미가 없다.
타입스크립트가 아니라 애니스크립트이다 - 제로초
그래서 배운것이 제네릭이다. 그런데 제너릭이든 애니타입이든 어떤 타입이든 다 받을 수 있다는 건데 무엇이 차이점인지 한번 찾아보았다.

제네릭 코드는 특정 타입으로 동작하게 함으로써, 유연하고 재사용성이 높은 함수나 타입을 만들 수 있게 한다.
애니 타입은 모든 타입의 값들을 포함할 수 있다. 애니는 타입들의 인스턴스에 대한 구체적인 타입으로 사용될 수 있다.
제네릭과 애니 타입이 많이 비교되기도 하는데(사실 제네릭은 언어가 제공하는 특징이고 애니 타입은 타입의 일종이므로 비교 자체가 성립이 안될 수도 있음에도), 제네릭의 정의 중 "특정 타입"과 애니 타입의 정의 중 "모든 타입"을 보면 그 차이가 명백히 드러난다. 그렇다 제네릭은 타입을 한정할 때 사용되고, 애니 타입은 모든 타입을 임의로 사용하기 위해 사용된다.
출처 : https://taeminator1.tistory.com/m/73

정확히 제네릭은 타입을 말하는 것이 아니라 특정 타입으로 동작 하게 만드는 것이다.
그렇다면 제네릭을 사용해 useInput함수를 바꿔보자.

import { Dispatch, SetStateAction, useCallback, useState } from 'react';
const useInput = <T = any>(initailData: T):[T, (e: any) => void, Dispatch<SetStateAction<T>>] => {
	const [value, setValue] = useState(initialValue);
	const handler = useCallback((e) => {
		setValue(e.target.value);
  	}, []);
  	return [value, handler, setValue];
}

특정한 타입으로 값을 매개 변수를 받는다면 리턴되는 value의 타입도 같은 타입으로 지정이 되고, (e:any) => void는 handler 함수의 타이핑, 그리고 Dispatch와 SetStateAction는 리액트에서 제공하는 훅스의 타이핑이다.


2. 비동기요청, CORS와 Proxy

2-1. 비동기요청

api로 데이터 요청을 할 비동기 요청들은 Redux를 사용할때 컴포넌트와 비동기로직이 분리 되는 장점이 있었다. 하지만 1개의 컴포넌트에서만 사용 할 비동기로직의 경우 굳이 분리 할 이유가 없기 때문에 Redux로 빼게 되면 코드가 길어져서 굳이 리덕스로 빼지 않는다.
axios는 promise이기 때문에 요청 후 3가지 케이스로 결과를 출력 할 수 있다.

axios.post('url', {
	data:{} // 보낼 데이터
}, {
	// 설정
})
	.then((response) => {}) // 성공
	.catch((error) => {}) // 실패
	.finally(() => {}) // 성공, 실패여부와는 관계 없이 요청이 끝난 후 실행

그리고 중요한 것은 메서드가 무엇이든 페이지를 로드할때 http요청이 일어나게 된다.
http의 구조는 헤더와 본문으로 구성 되어있으며, 본문은 실제 요청에 대한 결과내용들이 담겨 있고, 헤더는 본문에 대한 정보가 담겨있다. 나는 네트워크탭에서 보내는 데이터와 본문만 확인 했었다. 하지만 이 강좌에서 당장에 필요는 없지만 꼭 알아두어야 한다 강조하신 헤더 구성요소들을 찾아 정리 해보았다.


General Header

전송되는 컨텐츠에 대한 정보보다는, 요청/응답이 이루어지는 날짜 및 시간등에 대한 일반적인 정보가 포함된다.

  • Date : 현재시간 (Sat, 23 Mat 2019 GMT)
  • Pragma : 캐시제어 (no-cache), HTTP/1.0에서 쓰던 것으로 HTTP/1.1에서는 Cache-Control이 쓰인다.
  • Cache-Control : 캐시 제어
  • Transfer-Encoding : body 내용 자체 압축 방식 지정본문에 데이터 길이가 나와서 브라우저가 해석해서 화면에 뿌려줄 때 이 기능을 사용한다.
    'chunked'면 본문의 내용이 동적으로 생성되어 길이를 모르기 때문에 나눠서 보낸다는 의미다.
  • Upgrade : 프로토콜 변경시 사용 ex) HTTP/2.0
  • Via : 중계(프록시)서버의 이름, 버전, 호스트명
  • Content-Encoding : 본문의 리소스 압축 방식 (transfer-encoding은 body 자체이므로 다름)
  • Content-type : 본문의 미디어 타입(MIME) ex) application/json, text/html
  • Content-Length : 본문의 길이
  • Content-language : 본문을 이해하는데 가장 적절한 언어 ex) ko
  • Expires : 자원의 만료 일자
  • Allow : 사용이 가능한 HTTP 메소드 방식 ex) GET, HEAD, POST
  • Last-Modified : 최근에 수정된 날짜
  • ETag : 캐시 업데이트 정보를 위한 임의의 식별 숫자
  • Connection : 클라이언트와 서버의 연결 방식 설정

Request/ Response Header

Request Header는 웹브라우저가 웹서버에 요청하는 것을 텍스트로 변환한 메시지들이다.

  • Request Line : 어떤 웹서버로 접속(Host 부분)해서, 어떠한 방식(HTTP/1.1)으로, 어떠한 메소드(GET)를 통해 무엇을(/doc/test/.html) 요청했는지에 대한 메시지가 담겨있다.
  • Host : 요청하려는 서버 호스트 이름과 포트번호
  • User-agent : 클라이언트 프로그램 정보 ex) Mozilla/4.0, Windows NT5.1
    이 정보를 통해서 서버는 클라이언트 프로그램(브라우저)에 맞는 최적의 데이터를 보내줄 수 있다.
  • Referer : 바로 직전에 머물렀던 웹 링크 주소(해당 요청을 할 수 있게된 페이지)
  • Accept : 클라이언트가 처리 가능한 미디어 타입 종류 나열 ex) / - 모든 타입 처리 가능, application/json - json데이터 처리 가능.
  • Accept-charset : 클라이언트가 지원가능한 문자열 인코딩 방식
  • Accept-language : 클라이언트가 지원가능한 언어 나열
  • Accept-encoding : 클라이언트가 해석가능한 압축 방식 지정 ex) gzip, deflate
    압축이 되어있다면 content-length와 content-encoding으로 압축을 해제한다.
  • Content-location : 해당 개체의 실제 위치
  • Content-disposition : 응답 메세지를 브라우저가 어떻게 처리할지 알려줌. ex) inline, attachment; filename='jeong-pro.xlsx'
  • Content-Security-Policy : 다른 외부 파일을 불러오는 경우 차단할 리소스와 불러올 리소스 명시ex) default-src 'self' -> 자기 도메인에서만 가져옴
    ex) default-src 'none' -> 외부파일은 가져올 수 없음
    ex) default-src https -> https로만 파일을 가져옴
  • If-Modified-Since : 여기에 쓰여진 시간 이후로 변경된 리소스 취득. 페이지가 수정되었으면 최신 페이지로 교체하기 위해 사용된다.
  • Authorization : 인증 토큰을 서버로 보낼 때 쓰이는 헤더
  • Origin : 서버로 Post 요청을 보낼 때 요청이 어느 주소에서 시작되었는지 나타내는 값
    이 값으로 요청을 보낸 주소와 받는 주소가 다르면 CORS 에러가 난다.
  • Cookie : 쿠기 값 key-value로 표현된다. ex) attr1=value1; attr2=value2

어떤것들은 자주 봤던 내용들도 보이고 처음보거나 생소한 내용들도 많이 보인다. 이 부분은 프로젝트를 진행하며 정리 해놓고 어떤 역할을 하는지 직접 확인해보자.

출처 : https://hazel-developer.tistory.com/m/145


2-2. CORS, Proxy

api와 데이터를 주고 받다보면 가끔 가다 Access to XMLHttpRequest at '주소' from origin ...하는 에러를 볼 수 있다.
CORS 설정문제 인데 요청하는 주소(프론트)와 요청에 대한 결과를 보내주는 주소(백엔드)가 다를 경우 정상적인 연결이 되지 않는다.

이 때 해결방법은 2가지 방법이 있다.

  • 백엔드 개발자에게 cors설정을 요청 하는 방법.
    프론트와 백엔드가 서로 다른 서버인 경우 cors설정을 해 요청을 받아 들일수 있도록 설정해 주어야한다.
// express cors 설정
app.use(
  cors({
    origin: true,  // 모든 출처 허용. '*' 를 써도 된다.
    credentials: true,  // 요청간에 쿠키를 주고 받을 수 있도록 설정
  })
); 
// 하지만 별도의 특정 도메인만 허용하게 하는 경우는
app.use(
  cors({
    // localhost:3090만 허용
    origin: 'localhost:3090',
    // 특정의 여러 도메인을 등록하고 싶은 경우
    origin: ['localhost:3090', 'localhost:4000', 'localhost:5000'],
    credentials: true,  // 요청간에 쿠키를 주고 받을 수 있도록 설정
  })
);
  • 프론트개발자가 api요청시 프록시 설정을 하는 방법.
// webpack.config.ts
...
const config: webpack.Configuration = {
  ...
  devServer: {
    historyApiFallback: true, // react router history Api
    port: 3090,
    publicPath: '/dist/',
    // 프록시 설정
    proxy: {
      // api로 시작하는 요청에는 localhost:3095로 바꿔 요청
      '/api/': {
        target: 'http://localhost:3095',
        changeOrigin: true,
      },
    },
  },
}
...

하지만 프록시로 해결하는 방법은 임시방편이다. dev 혹은 local에서만 사용 가능하다.


3. SWR

SWR은 리덕스를 대체 할 수 있는 전역 관리 라이브러리 이다.
리덕스의 경우 메서드 구분없이 요청값을 저장하였다.
하지만 SWR의 경우 get요청만 저장한다. 이럴 경우 post요청 후에 get으로 한번 더 데이터 요청을 하면 된다.

SWR의 특징

SWR의 장점은 멀티태스킹 작업시에 다시 돌아오면 다시 자동으로 api요청을 해 데이터를 최신화 시켜준다. 물론 이 작업도 커스터마이징이 가능하다.
SWR과 React-Query는 매우 비슷하므로 둘중 하나만 익혀도 다른 하나도 별 문제 없이 사용 가능하다.

SWR 사용법

const fetcher = (url: string) => 
  axios
    .get(url, {
      // 서로 다른 도메인간에 쿠키를 주고받기 허용하는 옵션
      // * 쿠키를 생성은 백엔드가 하고 요청은 프론트엔드
      withCredentials: true 
    })
      .then((response) => response.data);

const { data, error, revalidate, mutate } = useSWR(url, fetcher, options);
  • url : api 요청 할 url

  • fetcher : api요청을 어떻게 처리 할 지에 대한 함수 매개변수로 url을 받는다.

  • options : 재 요청에 대한 설정 옵션들이다. 해당 옵션들은 SWR공식 문서에서 확인가능하다.
    https://swr.vercel.app/ko/docs/options#%EC%98%B5%EC%85%98

  • data : fetcher에서 데이터 요청에 성공하면 fetcher에서 return 해주는 값을 저장한다.
    data가 존재하지 않는다면 로딩상태.

  • error : fetcher에서 에러를 보낼 경우에 에러에 대한 정보가 저장된다.

  • revalidate : SWR의 버전이 1.x로 올라오면서 isValidating으로 변수명이 변경되었다.
    fetcher를 다시 실행하게 해주는 함수이다.

  • mutate : 캐시 된 데이터를 뮤테이트 하기 위한 함수

그 이외 Q&A

state대신 let을 쓰면 안되는 이유.

컴포넌트에서 수정 가능한 변수 let을 쓰게 되면 컴포넌트가 리렌더링 시에 계속 초기화가 된다.
예시를 보자.

import { useState } from 'react';

const Component1 = () => {
	let values = '';
  	const onChange = (e) => {
    	values = e.target.value
    }
	return {
    	<>
      		<input 
      			value={values} 
  				onChange={e => onChange(e)}
  			/>
    	</>
    }
}
const Component2 = () => {
	const [ values, setValues ] = useState('');
  	const onChange = (e) => {
    	setValues(e.target.value);
    }
	return {
    	<>
      		<input 
      			value={values} 
  				onChange={e => onChange(e)}
  			/>
    	</>
    }
}

Component1 의 컴포넌트의 경우 계속해서 재실행되며 values가 초기화 된다.

profile
고급개발자되기

0개의 댓글