Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

걸음마부터 달리기

[24/11/07] 리액트 커스텀 훅과 렌더링/ Zustand 본문

카테고리 없음

[24/11/07] 리액트 커스텀 훅과 렌더링/ Zustand

성추 2024. 11. 7. 16:06

오피셜한 정의 및 지식이 아닙니다. 단순 개발을 위해 이해한 내용으로 작성했습니다.

 

훅은 기본적으로 state와 관련하여 컴포넌트의 생명주기에 맞춰 호출되는 함수이다. (하지만 내가 느끼기론 이러한 정의가 맞는지 모르겠다. useNavigate 훅 경우 컴포넌트 생명주기와 관련되어 함수가 호출된다고 하기 보다는 어떤 이벤트에 맞춰서 되는거 아닌가? 싶다.)

 

컴포넌트가 재렌더링 하는 과정 자체가 재렌더링을 위해 "함수형 컴포넌트", 즉 함수를 다시 호출하여 해당 HTML 태그를 반환시키는 것으로 이해했다. 

 

즉 렌더링하는 것 자체가 컴포넌트의 함수를 호출하는 것이다. 이것이 커스텀 훅과 연관이 있다고 생각한다. 

생명주기와 관련해서만 수행되는 함수인 훅이 아니라, 어떤 이벤트(생명주기 관련 + 클릭같은 이벤트)에 대한 핸들러로 훅을 이해한다면 커스텀 훅을 이해하기 수월했다.

 

커스텀 훅은 

 핵심 키워드는 반복되는 로직이다. 반복되는 로직은 즉 재사용을 의미한다. 커스텀 훅을 왜 사용할까를 누군가 물어본다면 "반복되는 로직을 하나로 묶어 재사용하기 위함이다" 

라고 생각된다.

 

따라서 서로 다른 컴포넌트들에 따라 동일한 훅의 집합+일반 함수 등등의 모듈을 호출할 필요가 있을때

모듈화 시키기 위한 함수처럼 커스텀 훅을 만들어서 렌더링할때 함수 컴포넌트를 호출하면서 이러한 컴포넌트 Body 안의 코드들을 수행시키면서 자동으로 수행하기 위하여 존재한다고 이해했다.

 

   특정 블로그의 정리가 좋아서 가져와봤다.

https://ccomccomhan.tistory.com/273

 

결국 기본 언어의 문법에 존재하는 함수를 써도 최초 렌더링 혹은 재렌더링때 컴포넌트 함수가 호출되면서 해당 문법의 함수가 (바닐라 JS) 호출되겠지만 단순히 이러한 함수 로직 안에 훅이 없기에 쌩 JS 함수이다. 

 

Zustand

Zustand에서 create는 상태 관리 스토어를 생성하는 핵심 함수이다. 이 함수를 사용하면 상태를 저장하고 업데이트하는 로직을 하나의 store로 캡슐화할 수 있다. create 함수는 상태 관리 객체를 반환하며, 이 객체를 통해 상태를 읽고, 업데이트하고, 다른 상태 관련 로직을 처리할 수 있다.

 

간단하게 여러 컴포넌트들이 state를 Props Drilling 없이 공통적으로 관리하기 위해서 상태관리를 해야되는데 그 중 하나이다.

 

import create from 'zustand';

const useStore = create((set) => ({
  count: 0, // 상태 변수
  increaseCount: () => set((state) => ({ count: state.count + 1 })), // 상태 변경 함수
}));
------------------------------다른 예시---------------------------------
import {User} from "../types/interface";
import {create} from "zustand/react";

interface LoginUserStore {
    loginUser: User | null;
    setLoginUser: (loginUser : User) => void;
    resetLoginUser:() => void;
}

const useLoginUserStore = create<LoginUserStore>(set => ({
    loginUser:null,
    setLoginUser: (loginUser) => set(state=> ({...state,loginUser})),
    resetLoginUser: () => set(state => ({...state, loginUser:null}))
}));

export default useLoginUserStore;

set을 통해서 기존 state를 업데이트 할 수 있다. 즉 새로운 업데이트 함수안에 Body에 set으로 정의함으로서 알맞는 state에 대한 업데이트 함수를 만든다.

 

내 프로젝트에서는 User가 로그인 되어 있는지 아닌지에 따라 컴포넌트들이 다른 state를 나타내야 한다. 

하나의 state(User 여부) 로 부터 여러 컴포넌트들이 가져다 써야해서 상태관리가 필요해보였고 Zustand 를 사용했다.

import {User} from "../types/interface";
import {create} from "zustand/react";

interface LoginUserStore {
    loginUser: User | null;
    setLoginUser: (loginUser : User) => void;
    resetLoginUser:() => void;
}

const useLoginUserStore = create<LoginUserStore>(set => ({
    loginUser:null,
    setLoginUser: (loginUser) => set(state=> ({...state,loginUser})),
    resetLoginUser: () => set(state => ({...state, loginUser:null}))
}));

export default useLoginUserStore;

create로 인해서 상태(state)들을 모은 상태객체 저장소를 만든다. create는 함수 객체를 매개변수로 받는다. set과 get은 Zustand에서 이미 정의되어 있으니 업데이트 할때는 set을 가져와서 , 상태 값 가져올때는 get으로 가져오면 되니까 각각의 함수들을 set,get을 이용하여 Store에 보관시켜놓고 필요할때마다 다른 컴포넌트들에서 뽑아 쓰면 된다.

 

  ({...state,loginUser})), 의 문법은 set에 오는 state 객체의 속성, 즉 loginUser , setLoginUser , resetLoginUser를 복사하여 2번째로 오는 객체에 속성 이름에 맞춰 초기화 시켜준다. 

즉 위의 코드에 따라서는 로그인되어 만들어진 User 객체가 있을때 setLoginUser(User객체) 를 통해 상태객체의 loginUser를 할당시키겠다는 것이다. 따라서 우리는 이 store의 상태 값이 있으면 서로 다른 컴포넌트들에서 이것을 참조하여 뿌려주면 된다.  

 

주의:

  ({...state,loginUser})),의 문법을 보며   ({...state,loginUser:null})) 은 왜 있느냐가 궁금했다.

기존 state의 속성 값들을 얕은 복사로 새로운 객체를 만든 후 loginUser에 해당하는 속성들에 매핑하여 값을 할당해준다.

매핑한 후  이런 새로운 객체를 Zustand에서 관리하는 state 집합 객체 (LoginUserStore) 로 갈아끼운다. 객체 자체가 새로운 객체로 갈아끼워지는거다. 기존의 객체 속성 값을 변경시키는게 아니라...

 

모든 상태 관련하여 이렇게 동작하는 것이다.

useState를 쓸때도 마찬가지로 setState로 인한 State 값을 바꾸면서 기존 state 변수에 새로운 객체. 값을 만들어서 끼우는것이다. 변경하는 것이 아니라.

 

 

여담) 타입스크립트의 타입 명시

타입스크립트에서는 타입추론이 있어서 항상 타입을 명시해주지 않아도 된다. 즉 위와 같은 상황에서는 interface로 이미 타입들을 명시했으니까 인터페이스 타입으로 추론하여 자동으로 맞춰준다.