25일차 custom-hooks

osdsoonhyun·2023년 3월 14일
0

코드캠프

목록 보기
17/22
  1. const {}, const [] 원리 이해하기! -> Destructuring
  2. 객체에서 데이터 삭제하기! -> Rest-Parameter
  3. HOC는 원래 클래스용 -> Custom-Hooks

구조분해 할당(Destructuring)이란?

  • 비구조화 할당을 쉽게 생각하면 여러 번의 선언과 할당 과정을 한번에 처리하는 것

  • 즉, 비구조화할당이란 변수를 한번에 모두 선언하고 할당 할 수 있도록 도와주는 것이다.
    위의 세줄을 비구조화 할당을 이용하면 아래의 한 줄로 줄어들게 된다.
  • {}가 사용된 이유는 child의 data가 객체이기 때문, 만약 구조분해할당 시 데이터가 배열이라면 []가 사용된다.
  • 이전에 사용되던 const { data } = useQuery(FETCH_BOARDS); 이것은, uesQuery의 실행 결과 리턴 타입이 객체였단 것을 알 수 있다.

객체의 구조분해할당

//객체의 비구조화 할당
const {name, age, school} = child
  • 객체의 구조분해 할당은 선언부에 {중괄호}를 사용하며, 객체의 key값을 변수명으로 사용한다.
    또한 할당부에는 객체의 이름을 넣어주시면 된다.
  • 객체는 key 값을 가지고 구조분해할당을 하지만 배열의 경우 key가 없기 때문에 임의로 값을 입력해도 된다.
  • 객체는 key가 있기 때문에 순서가 필요 없고 원하는 값의 key만 입력해서 받을 수 있으나, 배열의 경우 순서대로 입력해야 하고 원하는 값만 얻을 순 없다.

useQuery의 객체 비구조화할당 원리

const { data, loading } = useQuery(FETCH_BOARDS)
  • useQuery의 실행후 return값이 객체 이므로 객체의 구조분해 할당이다.
  • key를 기준으로 뽑기 때문에 원하는 값만 추출하여 사용하였다.
  • 구조분해할당을 하지 않더라도 useQuery의 결과로 객체가 리턴된다.

useState, useMutation의 배열 비구조화할당 원리

const [state, setState] = useState('');
  • useState의 state의 초기값 설정
  • useState의 setState
    - state 변경하기
    • 해당 컴포넌트를 리렌더링 시키기
  • state와 setState 리턴하기
  • 배열의 구조분해할당이므로 key값이 없기에 배열의 자리가 중요하고 대신 이름을 자유롭게 변경할 수 있다.

정리
구조분해할당에서 객체와 배열의 차이점, 객체의 구조분해할당은 선언부의 변수명이 객체의 key값이 할당되고, 배열은 변수명은 상관없지만 순서가 중요하다.

REST 파라미터

  • delete를 사용하여 원본을 건드리면 좋지 않다.
  • 구조분해할당을 사용하면 원본을 건드리지 않고 객체를 만들 수 있다.
  • rest 파라미터를 사용하여 money, hobby를 삭제하지 않고 name, age, school의 값을 얻을 수 있다.

정리
객체에서 특정 값을 지우고 싶을때, delete를 사용하게 되면 원본의 값도 지우게 되어 좋지 않다.
따라서, 원본을 건드리지지 않기 위해 rest파라미터를 사용한다

REST 파라미터 예시

  • 불필요한 데이터인 typename이 들어오고 있다.
  • 지우고 싶은 데이터인 typename을 구조분해할당을 이용하여 새로운 객체값을 얻을 수 있다.
  • const { _typename, ...newBasket } = basket을 이용

custom-hooks이란?

  • Hook이란 함수형 컴포넌트에서 사용할 수 있는 state 관련 기능들의 모음이라 할 수 있다.
  • custom-hook은 이런 hook들을 이용해서 내가 필요한 함수를 만드는 것으로 앞에 use를 붙인 함수이다.

custom-hooks로 useAuth 분리

  • withAuth를 export하기 전에 실행했던 것과 마찬가지로 만들 useAuth 또한 함수이기 때문에 이번에는 useAuth 함수를 앞에 실행시켜줌으로써 CustomHooksUseAuthPage가 실행되기 전에 실행된 것처럼 실행한다.

그런데 Hook이 함수라면, custom hook역시 함수일텐데 일반함수와는 어떤 차이점이 있으며 비슷하면 그냥 함수를 쓰면 되지 굳이 왜 사용하는 것 일까?

  • 사실 별 차이는 없지만, 내부에서 useState, useEffect와 같은 hook을 사용하게 되면 custom hook이라고 한다.
  • hook이 사용된 custom-hook 내부는 실행되는 패턴이 다르기 때문에 주의를 주기 위한 방법으로 앞에 use를 붙인다.
  • 물론 안 붙인다고 에러가 발생하는 것은 아니지만 미리 사전에 알려주기 위함이다.
    - ‼️ custom hook을 사용하게되면 함수 네이밍에 use를 사용해야 한다. ‼️

다양한 custom hooks 만들기 useMoveToPage

  • 결제페이지에 들어간다고 예를들면, 결제페이지는 로그인한 사람만 이용가능하다. 만약 비로그인 상태에서 결제페이지에 들어가면 '로그인 후 이용주세요.' 가 보이는데 그렇다면 로그인을 하고 나서 어떤 페이지로 가게 될까?
  • 두 가지가 있다. 첫 번째는 메인 페이지로 가거나, 두 번째는 접속한 마지막 페이지로 돌아가는 것이다.
  • 메인 페이지 로그인 이후 router.push('/')로 돌아가면 되어 쉬우나, 로그인 이전 마지막 페이지로 돌아가기 위해서는 globalState를 만들어놓고 로그인페이지 빼고 페이지 이동할 때마다 마지막 페이지를 저장해놓는다.
  • 그러면 로그인 이후 globalState를 이용하여 페이지 이동하면 된다.
  • globalState를 하나 만들어 놓고 페이지 이동할때마다 마지막 페이지를 기록해놓는다.

custom-hooks를 활용한 리팩토링

  • useMoveToPage를 이용하여 코드의 양을 줄일 수 있다.

  • useSearch를 custom-hook으로 만들어 게시물 검색, 상품 검색, 마이 페이지 검색 등에서 사용하여 코드양을 줄일 수 있다.
  • 리팩토링시 custom-hook을 활용하여 container 부분을 최소화한다. 서비스 규모가 크지 않다면, container를 굳이 필요없을 정도로 최소화하고 custom-hook을 다른 곳에서도 재사용하고 유지보수를 좋게하도록 한다.

타입스크립트 타입

primitive 타입

// 1. 문자/ 숫자/ 불린 (primitive) 타입
const getPrimitive = (arg1: string,arg2: number,arg3: boolean): [boolean,number,string] => {
  return [arg3, arg2, arg1];
}

const result = getPrimitive('철수', 123, true);
  • 타입 체크 뿐만 아니라 return 타입까지 지정해줌으로써 결과값에 대한 타입도 예측 가능하다.

any 타입

// 2. any 타입 => javascript와 같음
const getAny = (arg1: any,arg2: any,arg3: any): [any,any,any] => {
  console.log(arg1 + 1000); // any는 아무거나 다 됨
  return [arg3, arg2, arg1];
}

const result = getAny('철수', 123, true);
  • ’알 수 없다, 모른다’ 라는 뜻으로, 개발자에게 주의를 주는 용도의 타입입니다. 타입이 지정되지 않았으므로 연산에 오류가 발생할 수 있음을 경고합니다.

  • ’어떠한 것이든지, 누구든지’ 라는 뜻으로, 어떠한 타입이 입력되더라도 전부 허용하는 타입입니다. 요소에 any 타입을 부여할 경우 사실상 타입스크립트가 아닌 자바스크립트를 사용하는 것이나 마찬가지가 됩니다

unknown 타입

// 3. unknown 타입 
const getUnknown = (arg1: unknown ,arg2: unknown ,arg3: unknown ): [unknown ,unknown ,unknown ] => {
  if(typeof arg1 === 'number') console.log(arg1 + 1000); // unknown은 사용시, 타입을 가정하여 사용해야 함
  return [arg3, arg2, arg1];
}
  • unknown은 any와 마찬가지로 아무거나지만, 모르는 상태이다.
  • any는 자바스크립트와 같지만 unknown은 타입을 정해놓고 사용해야 한다.
  • 타입의 제한을 두어 any 보다 안전하다.

타입스크립트 generic 타입

// 4. generic 타입  - 1
function getGeneric< MyType1, MyType2, MyType3 >(arg1: MyType1  ,arg2: MyType2  ,arg3: MyType3  ):  [ MyType3, MyType2, MyType1 ] {
  return [arg3, arg2, arg1];
}

const result = getGeneric('철수', 123, true); // 타입 추론
const result2 = getGeneric<string,number,boolean>('철수',123,true); //타입 명시
  • 사용자가 직접 타입을 만들어 부여- 입력 값에 따라 내부 타입이 정의한다.
  • 단일타입이 아닌 다양한 타입에서 작동하게 작성 가능
  • 처음에 뭘 넣든 상관은 없으나 일단 들어가면 타입이 정해진다.
  • 타입이 정해지므로 return 타입에 대한 결과까지 예측할 수 있다.
  • 들어갔을 때 해당 타입을 추론한 결과이다.
    -> const result = getGeneric('철수',123,true)
  • 타입 명시를 하려면 타입을 따로 작성하면 된다.
    -> const result = getGeneric<string,number,boolean>('철수',123,true)
// 5. generic 타입  - 2 변수명이므로 간소화
function getGeneric2< T1, T2, T3 >(arg1: T1  ,arg2: T2  ,arg3: T3  ):  [ T3, T2, T1 ] {
  return [arg3, arg2, arg1];
}
const result = getGeneric2('철수', 123, true); 

// 6. generic 타입  - 3 더 간소화
function getGeneric3< T , U, V >(arg1: T   ,arg2: U  ,arg3: V  ):  [ V, U, T  ] {
  return [arg3, arg2, arg1];
}
const result = getGeneric3('철수', 123, true); 
  • generic 타입명은 변수명과 같으므로 마음대로 정해도된다.

generic 타입은 왜 사용하는 것인가?
generic은 일반적으로 라이브러리를 가져다 사용하기 때문에 자주 사용하지는 않는다. useQuery, useMutation처럼 내가 만든 기능을 다른 사람에게 제공하는 경우 해당 기능에 들어오는 값의 타입을 예상할 수 없습니다. 이럴 때 Generic을 사용하면 들어오는 값의 타입에 따라 반환되는 값이나 컴포넌트의 타입이 결정되도록 할 수 있습니다. 즉,사용자 입장에서 타입 추론을 위해 generic을 사용한다.

화살표 함수로 generic 사용하기

// 6. generic 타입  - 4 화살표함수
const getGeneric4 = <T ,U , V>(arg1: T ,arg2: U ,arg3: V ): [V,U,T] => {
  return [arg3, arg2, arg1];
}

const result = getGeneric4('철수', 123, true); 

기초 generic 타입 적용

import { useRouter } from "next/router";
import { useEffect, ComponentType } from "react";

export const withAuth = (Component: ComponentType) => (props:any) => {
  const router = useRouter();

  useEffect(()=>{
    if(!localStorage.getItem('accessToken')){
      alert('로그인 후 이용가능합니다!');
      void router.push('/23-03-login-check');
    }
      
  },[]);

  return <Component {...props}/>
}
  • Component는 리액트에서 제공하는 ComponentType을 적용하고 props에는 무엇이 들어올지 모르므로 generic이 필요하다. P는 객체 타입이므로 앞에 extends {} 를 붙여준다.
  • <P extends {}>(props: P)

정리
withAuth가 라이브러리와 유사한데, withAuth 사용시 다른 곳에서 import 하여 사용하는데, 어디서 어떻게 props를 무엇을 넣어 사용할지 모르는 상황이라 props 타입을 any로 할 수 밖에 없는데, any로 하게 되면 타입 추론이 불가하기 때문에 generic을 통하여 타입 명시해준다.
주로 라이브러리, 함수를 제공하는 입장에서 generic을 사용한다.

0개의 댓글