12/05 구조분해할당&커스텀 훅,제네릭타입

김하은·2022년 12월 5일
0

useQuery나 useMutation 등을 했을 시에 사용했던 const {}, const [] 등에대해 알아보았다.

해당부분을 구조분해할당 이라고 부른다.

MDN문서: 구조분해할당구문은 배열이나 객체의 속성을 해체해 그 값을 개별변수에 담을수 있게하는 JS표현식

구조분해할당=비구조화할당이라고도 한다.
그렇다면 구조분해할당은 왜 사용하는것일까?

여기 child라는 객체가 있다.

const child = {
name:"짱구",
age:7,
school:"떡잎초등학교"
}

해당 객체의 값들을 전부 분해하여 변수에 할당할 일이 생겼다고 하자.
그렇다면 각각의 변수에 객체의 키값을 통해 접근하게된다.

const name = child.name
const age = child.age
const school = child.school

그런데 이렇게 작성하면 너무 길고 귀찮고 길어진다면 보기도 힘들다.

따라서 선언과 할당을 한번에 처리할 수 있는 방법을 사용하는데 이 방법이 바로 구조분해할당이다.

위의 것을 구조분해할당으로 적어주면

const {name,age,school} = child

이런식으로 적어준다.
간단하게 한줄로 표현이된다.

그런데 이때 사용되는 중괄호의 의미는 무엇일까?

객체에서 뽑았다는 의미가 된다.
만약 배열에서 뽑았다면 대괄호로 표현된다.

기존의 const {data} = useQuery()로 알아보자.

useQuery라는 함수의 객체에서 data라는것을 뽑아온것같다. 자세히보면 위의 child라는 객체에서 구조분해할당하여 중괄호안에 적어준것이 꼭 키인것 같다. 객체는 이렇게 키명으로 구조분해할당을 하고, 그 자리에 각 키에 해당하는 값이 들어와 순서가 상관이 없다.

실제로 useQuery함수를 만들어 구조분해할당을 진행해보자

function useQuery(aaa){
    // aaa(예제, FETCH_BOARDS)통해 Backend에 API요청
    return{
        data:{fetchBoards:{writer:"철수"}},
        loading:false,
        refetch:()=>{
            console.log("리패치가 실행됩니다")
        }
    }    
}
undefined

const {data,refetch} = useQuery("FETCH_BOARDS")
undefined

data.fetchBoards.writer
'철수'

refetch()
 리패치가 실행됩니다
undefined

const qqq = useQuery("FETCH_BOARDS")
undefined

qqq.data.fetchBoards.writer
'철수'

qqq.refetch()
 리패치가 실행됩니다
undefined

qqq.loading
false

콘솔창에서 실습해보았다.

useQuery에는 세가지가 객체로 리턴되는데, data, loading, 그리고 refetch라는 함수가 리턴된다.

const {data,refetch} = useQuery("FETCH_BOARDS")


==> FETCH_BOARDS라는 useQuery함수에서 data와 refetch 함수를 구조분해할당해 꺼내온다는 의미

이부분이 바로 useQuery라는 객체를 구조분해할당 한것이다.

그런데,
객체이니 data.fetchBoards.writer 이렇게 값을 꺼낼수 있었던것처럼 이 객체를 통채로 변수에 담아주어 사용도 가능하다.

const qqq = useQuery("FETCH_BOARDS")

qqq.data.fetchBoards.writer
'철수'

배열의 구조분해할당.

const classmates = [ "철수", "유리", "짱구" ]

변수에 할당한다면
const child1 = classmates[0]
const child2 = classmates[1]
const child3 = classmates[2]

해당부분을 구조분해할당방식으로 적어주면

const [child1 ,  child2 , child3 ] = classmates

이 경우에는 대괄호로 적어진 것으로 보아 리턴값이 배열형태라는것을 추론할 수 있다.

그리고, 배열에는 키값이라는것이 존재하지 않기에 임의의 이름을 사용가능하나, 해당부분의 순서가 중요하다. 각각 순서대로 들어가기때문에 만약 마지막것 하나만 받고싶은 경우에는 앞에 빈 콤마들이라도 찍어주어야한다.

function useState(aaa){// 매개변수에 인자가 들어옴. 매개변수가 초기값이 됨
    const myState = aaa // aaa를 사용하여 state의 초기값 설정
    const mySetState = (bbb) =>{ 
        console.log(`${myState}  에서 ${bbb} 로 state를 변경하겠습니다`)
        //1. bbb로 state변경(myState변경하기)
       console.log(`변경된 ${bbb}를 사용해서 컴포넌트를 리렌더링 하겠습니다`) //2. 해당컴포넌트를 리랜더링 시키기!!!!(클래스 컴포넌트에서 render함수가 실행되는것이 setState에도 있음.)

    }
    return[myState,mySetState]
}

const [qqq , setQqq] = useState(10)//초기값을 넣음(인자)
setQqq(20)
10  에서 20 로 state를 변경하겠습니다
변경된 20를 사용해서 컴포넌트를 리렌더링 하겠습니다
undefined

function useState(aaa){// 매개변수에 인자가 들어옴. 매개변수가 초기값이 됨
    const myState = aaa // aaa를 사용하여 state의 초기값 설정
    const mySetState = (bbb) =>{ 
        console.log(`${myState}  에서 ${bbb} 로 state를 변경하겠습니다`)
        //1. bbb로 state변경(myState변경하기)
       console.log(`변경된 ${bbb}를 사용해서 컴포넌트를 리렌더링 하겠습니다`) //2. 해당컴포넌트를 리랜더링 시키기!!!!(클래스 컴포넌트에서 render함수가 실행되는것이 setState에도 있음.)

    }
    return[myState,mySetState]
}


undefined

const [count,setCount] = useState(10)
undefined

count
10
setCount(20)
10  에서 20 로 state를 변경하겠습니다
변경된 20를 사용해서 컴포넌트를 리렌더링 하겠습니다
undefined

const qqq = useState("철수")
undefined

qqq[0]
'철수'

qqq[1]("훈이")
철수  에서 훈이 로 state를 변경하겠습니다
변경된 훈이를 사용해서 컴포넌트를 리렌더링 하겠습니다
undefined

리턴값이 배열로 나오는 것을 확인할 수 있다.

const [count,setCount] = useState(10)
로 10 이라는 인자가 aaa로 들어가 스테이트의 초기값이 되고, 그 다음 bbb자리에는 변경해준 스테이트값
setCount(20) 즉, 20 이 들어간다.

해당 state들을 배열로 묶은것을 하나의 변수에 넣고 인덱스 값으로 접근해도 된다.

const result =  useState("철수")
 ==> 초기값은 철수 즉, result의 0번째 인덱스에는  철수가 들어간다.
 해당스테이트를 변경시

 result[1]("유리")
 이런식으로 result의 첫번째 인덱스가 setState자리로 변경된값이 들어가기에 이렇게 적게되면 철수가 유리로 변경이된다.

그렇다면 구조분해할당은 어쩔때 사용할까?
따로 담아줘야할 값이 두개 이상일경우에 사용한다.


REST파라미터

const child = {
name:"짱구",
age:7,
school:"떡잎초등학교",
hobby:"액션가면보기",
money:500
}

이러한 객체가 있다.
해당객체에서 money와 hobby를 지우려고한다.
보통 delete를 사용하기도 하지만, 원본이 건들여지게되어 코드가 복잡해져 어디서 또 사용하고 있을지도 모르는 원본을 건드는 것 보다 rest파라미터라는 것을 사용하여 지워보도록 하자.

const {hobby,money, ...rest} = child

이런식으로 적어주면 ...rest앞의 것들은 빼고 나머지가 나오게된다. ...rest부분은 관례적으로 사용하는 이름이기에 다른 아무 이름이나 넣어도 상관은 없으나, 관례는 지키는 것으로!!

이렇게 만들고 rest를 찍게되면 앞에서빼준다고 적어준것을 제외하여 출력된다.


객체는 구조분해할당시 키명으로 하기에 이름이(키이름) 중요하고, 배열은 구조분해할당시 키가 없기에 아무이름으로 받아오게되어 순서가 중요하다.


커스텀 훅 만들기

간단히 말해 그냥 함수를 만드는것이다. 결국 use~() 등이 다 함수기에 use ~로 시작하는 함수를 만들어보는 것이다.

기존에 withAuth라는 HOC,HOF를 만들었던것을 useAuth라는 함수에 그 내용물만 복사해 넣고 해당 함수를 검증을 시작할 앞부분에 함수 실행으로 넣어준다.

useAuth() 이렇게말이다.

왜 이게 가능한가.
위에서부터 차례로 실행되기에 위쪽에 넣어준다면 컴포넌트를 읽으면서 해당 커스텀훅을 만나면 그것이 실행되고나서 그 아래가 실행되기 때문이다

다만 클래스 컴포넌트에서는 훅이란것이 존재하지 않기에 withAuth라는것을 사용한것.

그런데 여기서 , 어차피 함수라 함수명은 아무거나 사용가능하다. 다만, 해당함수 안에 use로 시작하는 애들이 있는경우, 원래 훅들은 좀 다르게 작동하기에 주의 하라는 용도? 로 함수이름에 use를 붙여준 것이다.

다시 정의하자면 use를 사용하는 훅을 커스텀 훅이라고 한다.


제네릭 타입!

제네릭 타입이란 이제껏 useForm 이나 useQuery, useMutation시에 꺽쇠로 타입을 지정해 준 부분을 말하는데, 일반적으로 이 제네릭 타입을 사용하는 경우는 별로 없다.
그러니까 라이브러리 사용자 입장에서는 말이다.

그러나, 라이브러리 제공자 입장에서는 좀 다르다.
사용자가 어떤 타입을 입력할 지 모르는 상태에서 자유롭게 입력할 수 있도록 하기위해 이 제네릭 타입이라는 것을 사용한다.

any타입을 사용하면 되지?

any타입의 경우는 아무 타입이다 다 들어가나 그 타입이 무엇인지 알 수가 없다. 반면, 제네릭 타입의 경우에는 처음에는 아무타입의 값이나 넣어도 그 처음의 값의 타입이 들어가게되어 타입추론이되기에 제네릭을 사용하게된다.

다음은 각 타입으로 실습을 진행해본것이다.

// 1.문자, 숫자 , 불린 (primitive)타입

import { useState } from "react";

//                                           >                   : 여기는 리턴타입
const getPrimetive = (arg1: string, arg2: number, arg3: boolean): [boolean, number, string] => {
  return [arg3, arg2, arg1];
};
const result = getPrimetive("철수", 123, true);

// 2. any 타입 => 그냥자바스크립트랑 같음

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);

// 3. unknown 타입: 아무거나 안됨. 사용할때 타입가정해주고 사용해야함

const getUnknown = (arg1: unknown, arg2: unknown, arg3: unknown): [unknown, unknown, unknown] => {
  if (typeof arg1 === "number") console.log(arg1 + 1000); // 쓰려면 뭔지 정해놓고 사용해야함// 따라서 any보다는 안전
  return [arg3, arg2, arg1];
};
const result = getUnknown("철수", 123, true);

// 4. getGeneric 타입 - 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 result = getGeneric<number, number, boolean>("철수", 123, true); // 타입명시

const [count, setCount] = useState(0); // 타입추론
const [state, setState] = useState<number>("철수"); 
 // 타입명시


// 4. getGeneric 타입 - 2단계
// 타입을 마음대로 만듬.// 어차피 변수기에 이름 상관없음
// 꺽쇠로<> 안에서 어떤타입을 쓸건지 정의해주어야함
// 뭘 넣을지는 상관없으나 일단 한번 뭔가가 들어가게되면 그 타입이 유지됨
function getGeneric2<T1, T2, T3>(arg1: T1, arg2: T2, arg3: T3): [T3, T2, T1] {
  return [arg3, arg2, arg1];
}
const result = getGeneric2("철수", 123, true); // 타입추론됨

// 4. getGeneric 타입 - 3단계
// 타입을 마음대로 만듬.// 어차피 변수기에 이름 상관없음
// 꺽쇠로<> 안에서 어떤타입을 쓸건지 정의해주어야함
// 뭘 넣을지는 상관없으나 일단 한번 뭔가가 들어가게되면 그 타입이 유지됨
function getGeneric3<T, U, V>(arg1: T, arg2: U, arg3: V): [V, U, T] {
  return [arg3, arg2, arg1];
}
const result = getGeneric3("철수", 123, true); // 타입추론됨

//  4. getGeneric 타입 -4 // 화살표 함수에서 사용하기
const getGeneric4 = <T, U, V>(arg1: T, arg2: U, arg3: V): [V, U, T] => {
  return [arg3, arg2, arg1];
};
const result = getGeneric4("철수", 123, true); // 타입추론됨

제네릭 타입의 경우 처음 작성하는 값들의 타입으로 타입이 추론되기에 타입명시가 필요없이 꺽쇠에 개수에만 맞게 아무거나 적어주어도 상관이 없다.

0개의 댓글