Skip to content

[11팀 이민기] Chapter 1-3 React, Beyond the Basics #58

Open
lapidix wants to merge 5 commits into
hanghae-plus:mainfrom
lapidix:main
Open

[11팀 이민기] Chapter 1-3 React, Beyond the Basics #58
lapidix wants to merge 5 commits into
hanghae-plus:mainfrom
lapidix:main

Conversation

@lapidix
Copy link
Copy Markdown

@lapidix lapidix commented Apr 9, 2025

과제 체크포인트

배포 링크

https://mingi3442.github.io/front_5th_chapter1-3/

기본과제

  • shallowEquals 구현 완료
  • deepEquals 구현 완료
  • memo 구현 완료
  • deepMemo 구현 완료
  • useRef 구현 완료
  • useMemo 구현 완료
  • useDeepMemo 구현 완료
  • useCallback 구현 완료

심화 과제

  • 기본과제에서 작성한 hook을 이용하여 렌더링 최적화를 진행하였다.
  • Context 코드를 개선하여 렌더링을 최소화하였다.

과제 셀프회고

1년차 처음 입사하고 첫 프로젝트를 진행한 뒤 저는 테스트 코드와 최적화에 대해 관심을 갖게되었었습니다. 그때 useMemo와 useCallback, 그리고 React.memo를 알게 되었었는데 이후 그 프로젝트는 종료되었습니다.
그후 프론트엔드 프로젝트를 한 번 더 했으나 시간이 많지 않아 최적화는 고려하지 못했고 구현에 포커싱해서 개발했었습니다.
이번 과제를 통해 잠시 잊고 지냈던 리액트 훅들과 최적화 기법들, 그리고 학습자료에 있는 React Dev tools등을 보면서 개발 시간에만 쫓겨 놓치고 있던 것들을 다시 한번 보게 되어서 좋았습니다.
오히려 그 당시보다 더 자세하게 동작에 대해서도 파악할 수 있었고, 학습자료도 너무 잘 되어 있어서 더 공부가 잘되었습니다.

기술적 성장

이전에 얕은 개념만으로 사용했던 것과는 달리 확실히 내부 로직이 어떻게 구현되어 있을지 고민함과 학습자료 덕분에 리액트 훅들에 대해 더 잘 알게 된 것 같습니다. 사실 이전 1년차때는 useCallback과 useMemo에 대한 차이점도 정확하게 모르고 사용했던걸로 기억하고 React.memo도 리렌더링을 방지할 수 있다고만 보고 사용했던 것과 다르게 각 함수들이 어떤 목적을 갖고, 어떤 방식으로 리렌더링을 방지하는 지에 대한 디테일들을 알 수 있어서 좋았습니다.

얕은 복사와 깊은 복사에 대한 추상적인 개념은 알고 있었으나 이번 기회에 정확한 개념에 대해서 배웠고, 최근에 회사에서 사용하는 Golang에서도 어떻게 사용되는지에 대해 찾아보면서 JS, Golang에 대한 언어 이해도가 증가했다고 느꼈습니다.

코드 품질

FSD 아키텍처를 기반으로 개발을 진행했습니다. 기본과제 통과 후 각 컴포넌트를 분리했는데 직전 회사 프로젝트 때보다 더 잘 나뉜 것 같아 만족스러웠습니다.

그러나 엔티티를 타입으로만 정의한 부분은 조금 아쉬웠고, 리팩토링이 필요한 부분이라고 생각이 들었습니다.

코드를 분리하면서 항상 고민하는 것 중하나가 user와 authentication인데, 인증은 항상 유저에 의존하는 느낌이라 이것을 별도로 빼는 것에 대한 고민을 항상 하게 되는 것 같습니다.

일단은 기존의 코드를 기준으로 분리하는 것을 목표로 하여 Context안에 user, login, logout을 분리의 목적으로 별도로 구성했습니다.

학습 효과 분석

전체 개발을 기준으로 보았을 때 가장 큰 배움이 있었던 부분은 사실 얕은 복사와 깊은 복사에 대한 개념 이해 및 구현을 통해 작동하는 원리에 대해 파악하고, 이를 다른 언어에서 사용하는 방법에 대해서도 찾아본 점이라고 생각합니다. 단적으로 제가 Golang으로 개발하던 모니터링 에이전트 프로젝트가 있는데 config와 같은 경우에는 단순 복사가 필요함으로 얕은 복사로 진행했었고, 생각이 들었고 디스크 사용량 같은 경우에는 깊은 복사를 통해 데이터의 무결성을 확보하였는데, 이전에는 그냥 이렇게 해야할 것 같아서, 혹은 이렇게 남들도 해서 하던거를 왜 이렇게 복사를 하는지에 대한 기준이 생겨서 가장 큰 배움이 있었다고 생각이 듭니다.

그러나 React Dev Tools를 포함해 대표적인 최적화 훅들인 React.memo, useMemo, useCallback에 대한 각 역할과 내부 구현을 통해 어떤 동작 방식을 가지고, 어떤 상황에 써야하는지를 알게되어 프론트엔드 적으로 많이 배우게 되었습니다.

과제 피드백

이번 과제에서 가장 좋았던 부분 중 하나는 풍부한 학습자료였던 것 같습니다.
이전 과제까지는 테스트 코드를 기반으로 진행하다가 어렵다 싶을 때 자료를 보았는데
이번에는 다르게 자료를 읽고 시작을 해보았습니다.

이번주에는 유독 학습 자료가 많아서 부담은 되었지만 많은 만큼 디테일이 좋았고 이해하기 더 쉬웠습니다. 또한 과제 발제 부분에 어떤식으로 구현을 하면 된다는 가이드라인이 있어서 처음에 봤을 때는 너무 막막했는데 과제를 끝까지 해결하는데 큰 도움이 되었습니다.

리뷰 받고 싶은 내용

유저와 인증에 관해서

유저와 인증은 분리해야 하는가, 혹은 유저 내부에서 인증에 관련된 것을 관리해야하는가에 대해 항상 고민됩니다.
예를 들어 다음과 같은 경우들이 있을 것 같습니다 FSD를 기준으로

  1. entities/user entities/authentication을 분리해서 관리
  2. entities/user 내부에서 인증 메소드를 구현
  3. entities/user만 관리하고 features/authentication에서 인증 관련 코드들 관리

저는 개인적으로 유저는 엔티티로 관리하고, 인증은 기능으로서만 있으면 된다는 생각으로 3번으로 코드를 작성하는 편인데 코치님의 생각도 궁금합니다!

비즈니스 로직과 커스텀 훅에 대해서

보통 커스텀 훅에서 비즈니스 로직이 구현되어 있는 경우도 있는데, 저는 개인적으로
features/services, features/hooks 를 분리해서 비즈니스로직은 services폴더 내부에서 관리하고 커스텀 훅이 필요 한 경우에만 hooks내부에서 사용하는 것이 좋다고 생각하는데, 너무 과하게 분리하는 것일까? 라는 고민이 들었습니다.
이 방법이 괜찮은 방법인지, 혹은 더 좋은 방법이나 스탠다드한 방법이 있는지도 궁금하고, 코치님에 대한 생각이 궁금합니다!

Context API에 분리에 대해서

현재 코드에서는 provider에서 내부로직과 상태를 관리하는데,
저는 아래와 같은 코드가 더 나은 코드라고 생각했습니다

// useAuthentication.ts
import { useContext } from "react";
import { AuthenticationContext } from "../../../entities/authentication";
import { useNotification } from "../../../features/notification";
import { useCallback } from "../../../@lib";
import { User } from "../../../entities/user";

export const useAuthentication = () => {
  const context = useContext(AuthenticationContext);
  if (context === undefined) {
    throw new Error(
      "useAuthentication must be used within an AuthenticationProvider",
    );
  }
  
  const { user, setUser } = context;
  const { addNotification } = useNotification();
  
  const login = useCallback(
    (email: string) => {
      setUser({ id: 1, name: "홍길동", email });
      addNotification("성공적으로 로그인되었습니다", "success");
    },
    [addNotification, setUser],
  );

  const logout = useCallback(() => {
    setUser(null);
    addNotification("로그아웃되었습니다", "info");
  }, [addNotification, setUser]);
  
  return {
    user,
    login,
    logout,
  };
};
// authentication.provider.tsx
import { ReactNode, useState } from "react";
import { useMemo } from "../../../@lib";
import { AuthenticationContext } from "../../../entities/authentication";
import { User } from "../../../entities/user";

export const AuthenticationProvider: React.FC<{ children: ReactNode }> = ({
  children,
}) => {
  const [user, setUser] = useState<User | null>(null);

  const authenticationContextValue = useMemo(
    () => ({
      user,
      setUser,
    }),
    [user],
  );

  return (
    <AuthenticationContext.Provider value={authenticationContextValue}>
      {children}
    </AuthenticationContext.Provider>
  );
};
// authentication.types.ts
import { createContext } from "react";
import { User } from "../../user";

export interface AuthenticationContextType {
  user: User | null;
  setUser: React.Dispatch<React.SetStateAction<User | null>>;
}

export const AuthenticationContext = createContext<
  AuthenticationContextType | undefined
>(undefined);

그러나 hook에서 useNotification()을 호출하다 보니 알림이 업데이트 될 경우 Header도 같이 업데이트가 되어서 문제가 발생하였기 때문에 Provider에서 상태 관리를 했습니다.
Hook이 비즈니스 로직에 들어가니까 의도하지 않은 렌더링이 발생했으며, FSD의 의도와는 다르게 비즈니스 로직이 apps 내부에 있게 되었고, 다른 방안이 떠오르지 않아서 이렇게 진행 했습니다.
프론트엔드 컨벤션을 해치지 않고, 어떤 방법으로 개선할 수 있었을지 코치님 의견이 궁금합니다!

@SeongYoonMin
Copy link
Copy Markdown

좋은코드 보면서 공부하고갑니다! 😁 hoc부분 조금 이해가 안되었는데 주석덕분에 이해 팍되었어요!

@dosilv
Copy link
Copy Markdown

dosilv commented Apr 12, 2025

민기님 맨날 젤 늦게까지 남아계시더니.. 뭔가 여러가지 고민을 하면서 구현하신 게 느껴져서 읽는 데 재밌었어요ㅎ.ㅎ 특히 ContextProvider-useContext hook부분에서 저는 항상 value랑 setter를 전부 Provider에 몰아넣고, hook은 단순히 값을 사용하기 위한�통로쯤으로 썼었는데 민기님 코드에서 함수들이 hook에서 선언된 걸 보고 이렇게도 할 수 있었네..?! 싶더라구요. 두 방식의 차이를 고민해보고 gpt한테도 물어봤는데, hook에 함수를 작성하면 hook 호출 시마다 useCallback이 생성돼서 약간의 비용이 생길 순 있지만 미미한 정도고, 캡슐화 측면에서는 해당 방식이 더 좋을 것 같았어요. 여기선 고려사항이 아니지만 만약 hook을 쓸 수 없는 클래스형 컴포넌트가 존재한다면 Provider쪽에 넣어야겠지만요 (저희 회사엔 아직 많아서😞)
두 방식 다 장단점은 있고, 성능 차이는 크지 않을 걸로 생각되는데 민기님은 hook에 함수를 넣는 쪽이 더 낫다고 생각하신 게 FSD구조상 로직 분리 차원에서인가요?! 말씀하신 notification 관련 문제 때문에 AuthenticationContext쪽만 함수가 Provider에 있다 보니 약간 일관성이 깨지는 느낌도 들어서 차라리 한 방식으로 통일하는 게 낫지 않을까 하는 생각도 들어서요.
리뷰는 아니고 저도 공부하게 되는.. 감상평쯤을 남기게 되었는데 그만큼 고민하며 과제하시느라 너무 고생하셨어요! 이번주도 파이팅입니당~~👏🏻💪🏻🔥

@lapidix
Copy link
Copy Markdown
Author

lapidix commented Apr 12, 2025

@dosilv
제 의도를 너무 잘 말씀해주셔서 들킨 느낌입니다..!
ContextAPI를 사용할 때, hook을 통로로 사용하시는 방식을 많이 사용하시는 것 같더라구요!

말씀하신 것 처럼 구조상 로직 분리를 위해 hook에 로직이 들어가는 게 맞다고 생각했습니다..
저는 개인적으로 provider는 의미상 기능이나 로직 등이 있는 것보다는 상태만 관리하고, hook에 해당 상태에 대한 기능 구현을 하는 것이 적합하다고 생각했습니다.

AuthenticationContext만 다르게 구현한 이유는 제가 의도한 대로 코드를 작성하니 header에서도 재렌더링이 발생해서 notification hook때문에 테스트가 통과하지 않아서 일단 통과를 위해 변경했습니다.
변경하면서 일관적으로 다 바꿀까 생각했지만, 여기 이 레포지토리는 이대로 남겨두려구요,, PR에는 제가 생각한 코드를 작성했지만,
theme, notification과 authentication에 대해서 고민한 흔적으로 남겨놓았습니다.

말씀하신 것처럼 저도 코드가 일관성 있는 것이 제일 중요하다고 생각합니다!
아마 회사나 개인 프로젝트 코드였으면,, 하나의 방식으로 통일했을 것 같아요..!
사실 리팩토링도 하다가 말아서.. 지저분했을텐데 코드 보시느라 고생하셨습니다...ㅎㅎ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants