Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] roomInfoStore의 구조변경을 수행한다. #809

Merged
merged 15 commits into from
Oct 17, 2024

Conversation

skiende74
Copy link
Contributor

@skiende74 skiende74 commented Oct 15, 2024

❗ Issue

✨ 구현한 기능

  • roomInfo 스토어 구현
  • roomInfoValidated 구현
  • 종전 존재하던 기능들 모두구현
  • 사용처(컴포넌트, post)에 반영
  • mapper함수 구현 ( structure가 비어있으면 null로 바꿔주기 등 작업을 일괄수행 )

사용예시

// RoomName.tsx 컴포넌트
const roomName = useRoomInfoValidated('roomName');

<FormField.Input
        placeholder=""
        onChange={roomName.onChange}
        name="roomName"
        id="roomName"
        value={roomName.rawValue}
      />

구현내용은 종전 얘기한 방식과 크게 다르진 않지만 작은 차이가 있습니다.

roomInfoStore: 검증,비검증 안따지고 모두를 담는 저장소.

하나하나에 접근하고싶을때, useRoomInfoValidated 훅에서 접근. (내부적으로 roomInfoStore를 사용)
검증이필요없는 자료형은, useRoomInfoNonValidated 훅에서 접근. (내부적으로 roomInfoStore를 사용)

저장된 데이터를 가져다 POST API날리고싶을때 : 저장소(roomInfoStore)에 접근해 API요청함.

roomInfoStore가 모두를 담는 저장소가 됨에 따라
현재 존재하는 roomInfoNonValidatedStore는, 이 PR이 머지 되면 제거하는 방향으로 진행하고자 합니다.


고민한 것 공유 (그냥 참고)

이번 구현에 그치지 않고 향후 조금 복잡한(?) zustand 스토어를 구현할 때에도
동일한 방식으로 구현할 만한 보편적 패턴을 찾고 싶어 고민했습니다.

종전에는 검증 로직을 가진 아주 작은 스토어를 여러개 한꺼번에 갖고 있게 구현을 시도했었는데,
만들고보니 설계자체도 괜찮고 사용도 괜찮았지만, 짜는 과정에 타입적인 어려움이 좀 있었습니다.
zustand store의 제네릭도 일일이 신경써줘야했고, 배열메소드, 객체순회 돌릴때 타입추론이 썩 만족스럽지 않았습니다.

가까스로 해결을 하고 완성은 했었지만,
패턴은 손쉽게 짤 정도로 쉽고단순하지않으면 의미가 없다고 생각해 좀 더 쉬운 패턴으로 다시 짰습니다.

( store를 동적으로 만들 때 어렵다고 느낀 이유 : zustand store의 긴 제네릭도 일일이 신경써줘야하고, 객체순회시 타입스크립트의 추론이 만족스럽지 않아 어려움이 있었습니다. )

zustand 공식문서의 slice 패턴은 타입스크립트 환경에서는 그리 간단하지않다. - Tkdodo -

라는 Tkdodo 발언의 의미를 이 때 조금 느꼈습니다.
combine stores into slices - zustand 공식문서 tkdodo 발언

그래서 좀 더 직관적이고 쉬운 코드를 짜봤는데,
RoomInfo스토어 roomInfoStore는 로직이 거의 없이 저장 역할만 하고 (number이든, number[]이든, string이든 담음)
로직들은 훅(useRoomInfoValidated)에서 구현(입출력로직, 검증 등)하게 해봤습니다.

다 짜고 난 지금 이상적 형태가 무엇일까 한번더 생각해보면
단순한 스토어를 적당히 3~4개 짜고, 훅 1개가 이들을 사용하는 형태라고 생각합니다. (현재는 스토어1개, 훅2개)

📢 논의하고 싶은 내용

🎸 기타

Copy link

Copy link

@skiende74 skiende74 changed the title [FE] roomInfoStore의 리팩토링을 수행한다. [FE] roomInfoStore의 구조변경을 수행한다. Oct 16, 2024
@ooherin
Copy link
Contributor

ooherin commented Oct 16, 2024

정말 고생하셨습니다 제이드..!! 이 PR 은 정말 제이드의 많은 능력과 노력이 담긴 대단한 PR 인 것 같아요..ㅎㅎ
아마 방 구조와 관리비 포함 항목은 제가 맡아서 해야겠군요! 진짜 수고하셨습니다😁😁

@ooherin
Copy link
Contributor

ooherin commented Oct 16, 2024

roomInfoNonValidatedStore 가 없어질 줄은 몰랐네요..! 이미 모든 AddressModal 의 로직을 해당 스토어를 사용하도록 리팩토링 했는데, 제이드가 아마 만들면서 더 유용하다고 생각해서 하신 것 같아요. 혹시 그 이유를 들어볼 수 있을까요? 예전에 훅을 만들면서, 스토어가 너무 비대해져서 나눈다고 들었던 것 같아서요!

Copy link
Contributor

@healim01 healim01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전반적으로 매우 깔끔하게 리팩토링되었네요~!!! 매우 좋습니다.
특히 action 에서 각각의 value name 으로 변경되니 가독성이 더 좋아진거 같아요!

대신 기존에 action 을 통합해서 묶었던 방식에서 변경된거 같은데 정확히 이해한게 맞을까요?

Comment on lines 32 to 36
checklist.room = roomInfoApiMapper(checklist.room);
const response = await fetcher.post({
url: BASE_URL + ENDPOINT.CHECKLISTS_V1,
body: { ...checklist, room: checklist.room },
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +6 to +9
const deposit = useRoomInfoValidated('deposit');
const rent = useRoomInfoValidated('rent');

const errorMessage = errorMessageDeposit || errorMessageRent;
const errorMessage = deposit.errorMessage || rent.errorMessage;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 매우 깔끔해졌네요~!! 최곱니다

Comment on lines 28 to 51

// export type RoomInfo0 = {
// roomName: string;
// deposit: number;
// rent: number;
// maintenanceFee: number;
// contractTerm: number;
// floorLevel: string;
// floor: number;
// station: string;
// walkingTime: number;
// realEstate: string;
// size: number;
// structure: string;
// occupancyMonth: number;
// occupancyPeriod: OccupancyPeriod;
// summary: string;
// memo: string;
// type: string;
// // createdAt?: string;
// address: string;
// buildingName: string;
// includedMaintenances: number[]; // 관리비 포함항목
// };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불필요한 주석이 함께 포함되었네요~!

@@ -25,4 +25,28 @@ export type RoomInfo = Partial<{
buildingName: string;
includedMaintenances: number[]; // 관리비 포함항목
}>;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불필요해진 타입일까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트용으로 만들고 주석쳐놓은거라 지울게요

);

export const roomInfoApiMapper = (values: RoomInfo | Nullable<RoomInfo>) => {
const result = { ...values, structure: values.structure === '' ? undefined : '' };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

방 구조의 경우만 '' 일때 undefined으로 고친 이유는 undefined 로 post 가 가야해서 그런걸까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

방구조 API타입이 null | '원룸' | '투룸' 이런느낌이라서
비어있을때 null로 보내야하기때문에 undefined해줬습니다.
(프젝내부의 undefined는 나중에 null로 바뀌기때문)

setRawValues: (rawValues: Partial<RoomInfo>) => {
set({ ...objectMap(rawValues, ([key, value]) => [key, value.rawValue]) });
},
getRawValues: () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 메서드들은 쉽게 이해가 되는 데 getRawValues, getParsedValues 의 경우 저번에 정한 컨벤션 처럼 docs 를 간략하게 달아주면 많은 도움이 될 것 같습니다:)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알겠습니다

getParsedValues: () => RoomInfo;
}

export const roomInfoStore = createStore<RoomInfoState & { actions: RoomInfoActions }>()(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

진짜 너무 많이 깔끔해졌네요...가독성도,사용성도👍👍

const actions = useStore(roomInfoStore, state => state.actions);
const value = parseRoomInfo(name, rawValue);

const set = useCallback(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useRoomInfoUnvalidate 에 들어갈 roomStructure 같은 경우는 값 변환 함수를 사용해서 만들면 될까요??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapper는 api에서 쓰는용도라 스토어내부의 액션을 사용해주시면됩니다.

단일값을 set해주고싶으면 set({rawValue:'넣을내용',errorMessage:''})
여러 값을 한꺼번에 set해주고싶으면 setRawValues를 이용하면됩니다.

get도 마찬가지로
단일값을 get해주고싶으면 get 해서 rawValue를 꺼내서쓰고
여러값을 한꺼번에 get해주고싶으면 getRawValues를 해주면됩니다.

Copy link

Copy link

@skiende74 skiende74 marked this pull request as draft October 16, 2024 07:41
Copy link

@skiende74 skiende74 marked this pull request as ready for review October 17, 2024 01:49
Copy link

@skiende74 skiende74 merged commit 69396f5 into dev-fe Oct 17, 2024
3 checks passed
@skiende74 skiende74 deleted the feat/799-remake-store branch October 17, 2024 01:59
Copy link

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

Successfully merging this pull request may close these issues.

3 participants