External Store로 관리하는 무한 좋아요 구현하기

유소정·2024년 5월 13일
0
post-thumbnail

🙋 이 문서를 보고 나면

  • React의 외부에서 External Store 형태로 데이터를 관리할 수 있다.
  • External Store로 관리하는 무한 좋아요를 구현할 수 있다.

📝 External Store 형태로 데이터를 관리하는 이유는?

저는 React의 외부에서 External Store 형태로 데이터를 관리했습니다.

External Store로 관리한 이유는 '관심사의 분리' 때문입니다.
React 입장에서 데이터가 어떻게 관리되는지 알 필요가 없습니다.

📝 External Store 형태로 데이터를 관리하는 도구, TSyringe를 소개합니다

React의 useState 없이 상태를 관리해야 합니다.
React는 '상태가 바뀌면' 상태가 업데이트 되었습니다.
이젠 React의 도움을 못 받으니 어떻게 하면 좋을까요?

저는 TSyringe를 이용했습니다. 이는 의존성을 주입해주는 역할을 합니다. External Store를 관리하는데 활용할 수 있습니다.
React 컴포넌트 입장에서는 전역처럼 여겨집니다. (전역변수를 쓰는 것처럼 느껴지는데, 전역변수는 아닙니다)
그래서 Prop Drilling 문제를 해결할 수 있는 방법 중 하나입니다.

참고로 TSyringe말고 다른 방법도 있습니다. 예를 들어, Context가 있습니다. 하지만 Context는 전체를 바꾸기 때문에 비효율적이라서 선택하지 않았습니다.

📝 TSyringe로 좋아요 관리하기

1.설치

1-1 tsyringe & reflect-metadata 설치하기

reflect-metadata는 polyfill입니다.
polyfill은 더 이상 지원하지 않는 곳에서도 지원을 가능하게 하는 역할을 의미합니다.

그래서 reflect-metadata는 더 이상 TypeScript에서 데코레이터와 메타데이터를 지원하지 않는 곳에서도 지원이 가능하게 하는 역할을 합니다.

npm i tsyringe reflect-metadata

reflect-metadata는 모든 프로그램이 시작하는 위치에 써줍니다. 저는 main.tsx에 썼습니다.

import 'reflect-metadata';

1-2 테스트에 reflect-metadata 추가하기

테스트 코드를 작성하셨다면, reflect-metadata를 테스트 쪽에도 추가해야 합니다.
테스트는 시작이라고 할 만한 위치가 없어서, 저는 jest.config.js에 작성된 setuptests.ts 파일의 상단에 작성했습니다.

1-3 데코레이터(@) 허용하기

데코레이터를 허용하기 위해서 tsconfig.json 파일에서 아래 두개의 주석을 해제합니다.

"experimentalDecorators": true,
"emitDecoratorMetadata": true,

1-4 usestore-ts 설치하기

usestore-ts는 필수가 아닙니다. usestore-ts 없이도 코드를 작성할 수 있습니다.
하지만 아래 예제는 usestore-ts를 사용했습니다.

usestore-ts는 시드웨일 라이브러리입니다.

React가 상태를 변경하면 알아서 업데이트 하는 것처럼, 우리도 상태가 바뀌면 연결된 컴포넌트를 forceUpdate해야 합니다. 이를 publish(상태 변경 알림)이라고 하겠습니다.

usestore-ts를 사용한 이유는 데이터를 publish가 편해서입니다.

이전에는 따로 BaseStore를 만들어서 해당 Store에 데이터를 publish하는 부분을 넣고, 모든 Store가 BaseStore를 상속받게 했습니다. 그리고 데이터의 값이 변경되면 BaseStore의 publish를 실행했습니다.

하지만 usestore-ts의 Action 데코레이터를 쓰면 publish를 따로 해줄 필요가 없습니다.
내부에서 자동으로 해줍니다.

npm install usestore-ts

2. 작성

2-1. Store 생성

Store를 만드는데 마치 전역인 것처럼 동작합니다.

singleton은 전역에서 하나라는 의미입니다.

tsyringe는 Ioc Container를 제공합니다. 그래서 Ioc Container가 객체 생성을 알아서 해줍니다.
Store가 다른 것에 의존성이 있다면 그것까지 알아서 조립해줍니다.

// src/store/LikeStore.ts

import { singleton } from 'tsyringe';

import { Action, Store } from 'usestore-ts';

import { apiService } from '../services/ApiService';

@singleton()
@Store()

export class LikeStore {
  id = '';
  liked = 0;
  error = false;

  @Action()
  increase(id : string) {
    this.id = id;
    this.liked += 1;
  }

  @Action()
  setError() {
    this.error = true;
  }
}

2-2. Store 연걸다리

이렇게 커스텀 Hook으로 분리하는 이유는
React가 UI를 담당하고, 순수한 TypeScript는 비지니스 로직을 담당하기 위해서 입니다.
이를 통해 '관심사의 분리'를 명확히 할 수 있습니다.

// src/hooks/useLikeStore.ts

import { container } from 'tsyringe';

import { useStore } from 'usestore-ts';

import { LikeStore } from '../store/LikeStore';

export default function useLikeStore() {
  const store = container.resolve(LikeStore);
  return useStore(store);
}

2-3. Store 사용

// src/components/MapDetail/DetailedMapPage.tsx

import useLikeStore from '../../hooks/useLikeStore';

export default function DetailedMapPage() {
  const [, store] = useLikeStore();

  const handleBoothLike = (value: string) => {
    store.increase(value);
  };
  
  return (
          <button
	      	type="button"
    	    onClick={() => handleBoothLike(id)}
          >
	      	무한 좋아요
        </button>
  );
};

백엔드에 데이터 업데이트 하기

// src/services/ApiService.ts

import axios from 'axios';

const API_BASE_URL = process.env.REACT_APP_URL;

export default class ApiService {
  private instance = axios.create({
    baseURL: API_BASE_URL,
  });

  async fetchLike({ id, liked }:{
     id: string;
     liked: number;
    }): Promise<{ liked: number; }> {
    const response = await this.instance.put(`booth/liked/${id}`, { likeCount: liked });
    return response.data;
  }
}

export const apiService = new ApiService();

📘 정리

TSyringe를 통해서 데이터를 전역처럼 관리할 수 있었고,
usestore-ts를 통해서 상태 변경 알림을 쉽게 처리할 수 있었습니다.

그리고 이렇게 데이터를 React의 외부에서 관리하는 이유는
관심사의 분리를 위해서였습니다.

데이터를 React의 외부에서 관리해보며, 선언형 UI가 얼마나 편했는지 알 수 있게 됐습니다.

profile
기술을 위한 기술이 되지 않도록!

0개의 댓글