728x90
반응형

프로그래밍을 할 때, 함수의 인자를 객체로 묶어 전달하는 것은 종종 사용되는 패턴입니다. 이 글에서는 객체 인자를 사용하는 함수 호출에 대해 설명하고, 코드 오류를 수정한 경험을 공유하겠습니다.

문제 상황

다음과 같이 submitPost 함수를 호출할 때, 각 값들을 객체로 묶어서 전달했습니다:

dispatch(submitPost({ boardId, title, content, file }));

submitPost 함수의 인자는 객체로 전달되기 때문에, 호출되는 쪽에서도 객체의 프로퍼티명을 동일하게 맞춰줘야 합니다. 이전 코드에서는 boardId 부분이 board_id로 되어 있어, 단순히 인자 순서만 맞추면 된다고 생각했지만 작동되지 않았습니다.

원인 분석

객체 인자로 인자를 받을 때, 객체의 프로퍼티명이 정확히 일치해야 합니다. 아래 코드를 보면, boardId를 객체 인자로 받습니다:

export const submitPost = createAsyncThunk(
  "posts/submit",
  async ({ boardId, title, content, file }, { rejectWithValue }) => {
    console.log(boardId);
    try {
      const response = await axios.post("http://localhost:4000/api/posts", {
        board_id: boardId,
        user_id: 1,
        title,
        content,
      });

      if (file) {
        const formData = new FormData();
        formData.append("files", file);
        await axios.post(
          `http://localhost:4000/api/posts/${response.data.id}/upload`,
          formData,
          {
            headers: {
              "Content-Type": "multipart/form-data",
            },
          }
        );
      }

      return response.data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

핵심 포인트

  • 함수 인자를 객체로 전달할 때는, 객체의 프로퍼티명이 함수 내부에서 사용하는 이름과 정확히 일치해야 합니다.
  • 이전 코드에서 boardId 대신 board_id를 사용하여 문제가 발생했습니다.

수정 방법

호출하는 쪽과 호출되는 쪽의 객체 프로퍼티명을 일치시키면 문제를 해결할 수 있습니다. 다음은 수정된 코드입니다:

호출 코드

dispatch(submitPost({ boardId, title, content, file }));

함수 정의 코드

export const submitPost = createAsyncThunk(
  "posts/submit",
  async ({ boardId, title, content, file }, { rejectWithValue }) => {
    try {
      const response = await axios.post("http://localhost:4000/api/posts", {
        board_id: boardId, // 이 부분을 변경하지 않았습니다.
        user_id: 1,
        title,
        content,
      });

      if (file) {
        const formData = new FormData();
        formData.append("files", file);
        await axios.post(
          `http://localhost:4000/api/posts/${response.data.id}/upload`,
          formData,
          {
            headers: {
              "Content-Type": "multipart/form-data",
            },
          }
        );
      }

      return response.data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

결론

함수 인자를 객체로 전달할 때는 객체의 프로퍼티명이 정확히 일치해야 함을 유념해야 합니다. 이번 글에서는 객체 인자를 사용하는 함수 호출과 관련된 오류를 수정한 과정을 설명했습니다. 객체 인자를 사용하는 것은 코드의 가독성을 높이고, 인자 순서를 신경 쓰지 않아도 되는 장점이 있습니다. 다만, 프로퍼티명이 일치하지 않을 경우 예상치 못한 오류가 발생할 수 있으므로 주의해야 합니다.

반응형
728x90
반응형

Redux는 JavaScript 애플리케이션을 위한 예측 가능한 상태 컨테이너입니다. 주로 React와 함께 사용되지만, Vue, Angular와 같은 다른 프레임워크나 라이브러리와도 사용할 수 있습니다. Redux는 애플리케이션의 상태 관리를 단순화하고, 상태 업데이트를 일관되고 예측 가능한 방식으로 처리합니다.

Redux 개념

상태 (State)

애플리케이션의 상태는 하나의 큰 JavaScript 객체로, 앱에서 사용되는 모든 데이터를 포함합니다.

액션 (Action)

상태를 변화시키는 이벤트입니다. 액션은 type 속성을 가진 객체로, 어떤 상태 변화를 일으킬지 설명합니다. 필요에 따라 추가 데이터를 payload로 전달할 수 있습니다.

리듀서 (Reducer)

액션에 의해 상태가 어떻게 변경되어야 하는지 정의하는 순수 함수입니다. 리듀서는 이전 상태와 액션을 인자로 받아 새 상태를 반환합니다.

스토어 (Store)

애플리케이션의 상태와 리듀서를 포함하는 객체입니다. 스토어는 앱의 상태를 저장하며, getState(), dispatch(action), subscribe(listener) 같은 메서드를 통해 상태를 관리합니다.

데이터 흐름

Redux의 데이터 흐름은 엄격하게 단방향입니다:

  1. 액션 발송: UI에서 사용자의 상호작용 또는 다른 이벤트에 응답하여 액션을 발송(dispatch)합니다.
  2. 리듀서 실행: 스토어는 현재 상태와 발송된 액션을 리듀서에 전달합니다. 리듀서는 액션 타입에 따라 새 상태를 계산합니다.
  3. 상태 업데이트: 리듀서가 반환한 새 상태는 스토어에 저장됩니다. 이 과정을 통해 앱의 상태가 업데이트됩니다.
  4. UI 업데이트: 상태가 변경되면, 스토어를 구독하는 UI 컴포넌트가 새 상태에 맞게 갱신됩니다.

Redux 사용 이유

예측 가능한 상태 관리

Redux는 모든 상태 변화를 중앙에서 관리하여, 애플리케이션의 동작을 더 예측 가능하게 만듭니다.

디버깅 용이

Redux DevTools와 같은 도구를 사용하여 상태 변화를 시간에 따라 추적하고, 액션에 따른 상태 변화를 확인할 수 있습니다.

컴포넌트 간 상태 공유

전역 상태를 사용하여, 여러 컴포넌트 간에 상태를 쉽게 공유하고 업데이트할 수 있습니다.

유지 보수성

액션과 리듀서를 통한 명확한 구조는 대규모 애플리케이션의 유지 보수성을 향상시킵니다.

결론

Redux는 그 자체로는 작은 라이브러리지만, 애플리케이션의 상태 관리를 위한 강력한 아키텍처를 제공합니다. 크고 복잡한 애플리케이션, 특히 여러 상태를 공유하고 다루어야 하는 경우 Redux를 사용하는 것이 좋습니다.


이 문서는 Redux의 핵심 개념과 데이터 흐름, 그리고 Redux를 사용하는 이유를 정리한 것입니다. Redux는 상태 관리를 단순화하고, 예측 가능하게 만들어주는 도구로, 특히 복잡한 애플리케이션에서 유용합니다.

반응형
728x90
반응형

오늘 타입스크립트에서 함수를 작성하던 중 너무 힘빠지는 오류(?)를 발견하였다.

문제의 소스코드는 아래와 같다.

async getPostsAndPaginator(boardId: number, page: number, search: string) {
    const perPage = 10;
    const offset = (page - 1) * perPage;
    const formattedSearch = `%${search}%`;

    const posts = await this.postRepository
      .createQueryBuilder("posts")
      .select()
      .where("posts.board_id = :boardId AND posts.title LIKE :formattedSearch", { boardId, formattedSearch })
      .orderBy("posts.created_at", "DESC")
      .offset(offset)
      .limit(perPage)
      .getMany();

    const totalCount = await this.postRepository
      .createQueryBuilder("posts")
      .where("posts.board_id = :boardId AND posts.title LIKE :formattedSearch", { boardId, formattedSearch })
      .orderBy("posts.created_at", "DESC")
      .getCount();

    const paginatorObj = paginator({
      totalCount: totalCount,
      page,
      perPage,
    });

    return [posts, paginatorObj];

이렇게 코드를 작성하게 되면 vscode에서

Return type of public method from exported class has or is using name 'PaginatorResult' from external module "/Users/bottlesik/nodejs-workspace/my-board-api/src/utils/paginators" but cannot be named.ts(4053)

(method) PostsService.getPostsAndPaginator(boardId: number, page: number, search: string): Promise<(Posts[] | PaginatorResult)[]>

위와 같은 오류 메시지를 발생시킨다.

하지만 나는 도대체 뭐가 문제인지 모르기에 위 소스코드에서 주석처리된 부분을 살리고 그 위 쿼리빌더 내용을 날리면

해당 메소드가 아래와 같이 정의 된다.

(method) PostsService.getPostsAndPaginator(boardId: number, page: number, search: string): Promise<any[]>

여기서 둘의 차이를 비교해보면 : Promise<(Posts[] | PaginatorResult)[]> vs : Promise<any[]> 이다

도대체가 왜 이렇게 리턴타입이 바뀌는지 도통 알 수 가 없어서 일단 위 소스코드에서 명시적으로 Promise<any[]> 를 리턴타입으로 지정해주어 해결했다. 그러나 뭐가 문제인지는 잘 모르겠다....

자바를 쓸 때는 기본적으로 리턴타입을 명시하니 이런 상황을 맞이하는 경우가 없었는데....

음 괜히 타입스크립트가 밉다.

반응형