-
Notifications
You must be signed in to change notification settings - Fork 8
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] 약속 생성 흐름에 퍼널 패턴 적용 #356
Conversation
Test Results9 tests 9 ✅ 11s ⏱️ Results for commit 5c5be28. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해리 퍼널패턴이 너무 멋지네요~
일단 구현만 한 상태라고 하니 직접적으로 확인해볼 수 없었지만 기대가 되네요~!
리뷰 사항 반영 및 답변 부탁드려요를레히~
interface ValidationRules { | ||
pattern?: RegExp; | ||
errorMessage?: string; | ||
} | ||
|
||
const useInput = (rules?: ValidationRules) => { | ||
const useInput = (rules?: ValidationRules): UseInputResult => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Q]
ReturnType 타입을 사용하면 될 것 같다는 생각이 들었는데 타입을 별도로 정의하신 이유가 있을까요?
https://www.typescriptlang.org/ko/docs/handbook/utility-types.html#returntypetype
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오, 유틸리티타입 적극적으로 활용해 보는 것 좋네요. 다만, 커스텀 훅을 정의할 때 해당 커스텀 훅의 반환값 타입을 명시하면 함수가 함수의 반환값 타입을 참조하는 자기 참조 문제가 있어, 커스텀 훅을 정의한 후 아래에서 ReturnType을 활용해 보도록 할게요~
export type UseInputResult = ReturnType<typeof useInput>;
아래는 참고하시면 좋을 것 같아, 에러 메시지를 함께 첨부합니다
'useInput' implicitly has type 'any' because it does not have a type annotation
and is referenced directly or indirectly in its own initializer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그리고, ReturnType을 활용하는 만큼 타입 이름도 Return으로 수정해서 사용하는 유틸리티타입이랑 이름을 맞춰보도록 할게요
import { | ||
addHoursToCurrentTime, | ||
generateEndTimeOptions, | ||
generateStartTimeOptions, | ||
isTimeSelectable, | ||
} from './useTimeRangeDropdown.utils'; | ||
|
||
export default function useTimeRangeDropdown() { | ||
export default function useTimeRangeDropdown(): UseTimeRangeDropdownResult { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Q]
이 부분도 useInput 질문과 같습니다. ReturnType 대신 사용한 이유가 궁금하네요!
); | ||
}; | ||
|
||
const { startTime, endTime, handleStartTimeChange, handleEndTimeChange } = useTimeRangeDropdown(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P3]
해당 값을 구조분해할당하고 return값을 명시해주는 것 보다
const { startTime, endTime, handleStartTimeChange, handleEndTimeChange } = useTimeRangeDropdown(); | |
const meetingTimeInput = useTimeRangeDropdown(); |
으로 작성하는 것이 더 간편하다는 생각이 드는데, 구조분해할당으로 구현하신 이유가 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아 그러네요, 함수형 컴포넌트를 정의할 때 받는 props를 구조분해할당하는 것에 익숙해지다 보니, 습관이 된 것 같네요. 해당 커스텀 훅을 사용하는 쪽에서 구조분해할당을 하면 되니, 구현부에서는 굳이 할 필요 없겠네요!
handleEndTimeChange={handleEndTimeChange} | ||
/> | ||
</Field> | ||
<BottomFixedButton onClick={() => console.log('hi')} disabled={isCreateMeetingFormInValid}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1]
'hi harry' 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚨🚨🚨🚨🚨🚨🚨🚨🚨
🚨Erase console.log()
🚨
🚨🚨🚨🚨🚨🚨🚨🚨🚨
import { useEffect, useState } from 'react'; | ||
|
||
const useButtonOnKeyboard = () => { | ||
const [resizedButtonHeight, setResizedButtonHeight] = useState(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P3]
매직넘버는 상수화하면 좋을 것 같아요 해리포터군~
const Funnel = useMemo( | ||
() => | ||
Object.assign( | ||
function RoteFunnel(props: RouteFunnelProps<Steps>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Q]
Roteunnel -> RouteFunnel을 의미하신 것일까요?
[location.state, initialStep, steps], | ||
); | ||
|
||
return [setStep, Funnel] as const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Q]
나중에 goPrevStep을 활용할 때는 return을 어떻게 구성하실 예정이신가요? 배열 형태이다보니 순서가 정해져있을 것 같아 궁금해 리뷰 남겨요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
우선은 서비스에 뒤로가기 버튼이 없기 때문에, useState 훅 처럼 배열 형태로 반환하도록 구현한 후 돌아가는지 빠르게 확인하고 PR을 날렸습니다. 피드백을 빠르게 받는 것이 좋다고 판단했어요. 그래서 뒤로가기 버튼이 생긴다면 객체 형태로 반환할 것 같네요~
|
||
const { startTime, endTime, handleStartTimeChange, handleEndTimeChange } = useTimeRangeDropdown(); | ||
|
||
const isCreateMeetingFormInValid = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2]
invalid 자체가 하나의 단어이기 때문에 아래와 같이 사용하는 것은 어떨까요?
isHostInfoInValid도 마찬가지입니다!
const isCreateMeetingFormInValid = | |
const isCreateMeetingFormInvalid = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
굿~
<MeetingName | ||
meetingNameInput={meetingNameInput} | ||
isMeetingNameInvalid={isMeetingNameInvalid} | ||
onNextStep={() => setStep('약속주최자정보')} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2]
"약속이름", "약속주최자정보"와 같은 step도 상수화 하면 좋을 것 같아요 🙌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
정상수 형님이 좋아하실 것 같아요~
frontend/src/styles/global.ts
Outdated
@@ -100,11 +100,12 @@ const globalStyles = css` | |||
} | |||
|
|||
body { | |||
overflow: hidden; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이것도 모바일 버튼... 문제 해결을 위해서...ㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해리! 퍼널 패턴 작업이 쉽지 않았을 텐데 정말 잘 해주셨네요!!
빠르게 작업해 주셨는데... 리뷰가 너무 늦어서 죄송합니다.
🍀🍀화이팅🍀🍀
const Funnel = useMemo( | ||
() => | ||
Object.assign( | ||
function RoteFunnel(props: RouteFunnelProps<Steps>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2]
함수명에서 오타가 있는 것 같아요~
function RoteFunnel(props: RouteFunnelProps<Steps>) { | |
function RouteFunnel(props: RouteFunnelProps<Steps>) { |
hostNickNameInput.value.length < 1 || | ||
hostPasswordInput.value.length < 1 || | ||
hostNickNameInput.errorMessage !== null || | ||
hostPasswordInput.errorMessage !== null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2]
isMeetingNameInvalid
, isHostInfoInValid
, isCreateMeetingFormInValid
에서 유효성 검사를 반복적으로 수행하고 있는 부분이 있네요!
공통되는 부분을 함수로 분리해 보는 건 어떨까요?
const isInputInvalid = (input) => input.value.length < 1 || input.errorMessage !== null;
const isMeetingNameInvalid = isInputInvalid(meetingNameInput);
const isHostNickNameInvalid = isInputInvalid(hostNickNameInput);
const isHostPasswordInvalid = isInputInvalid(hostPasswordInput);
const isHostInfoInvalid = isHostNickNameInvalid || isHostPasswordInvalid;
const areDatesUnselected = selectedDates.length < 1; | ||
const hasDate = (date: string) => selectedDates.includes(date); | ||
|
||
const handleDateClick = (date: string) => { | ||
setSelectedDates((prevDates) => | ||
hasDate(date) ? prevDates.filter((d) => d !== date) : [...prevDates, date], | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P3]
handleDateClick
함수에서 날짜가 선택되었는지 확인 후 배열에서 추가/삭제하는 로직이 있는데, 이 부분은 Set
자료구조를 활용해봐도 좋을 것 같아요~
const [selectedDates, setSelectedDates] = useState<Set<string>>(new Set());
const handleDateClick = (date: string) => {
setSelectedDates((prevDates) => {
const newDates = new Set(prevDates);
newDates.has(date) ? newDates.delete(date) : newDates.add(date);
}
return newDates;
});
};
const areDatesUnselected = selectedDates.size < 1;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
중복되는 날짜가 발생할 일이 없을 것이라고 생각해서, Set 자료구조를 왜 사용해야할까에 대한 의문이 있었는데 시간복잡도 측면에서는 배열보다 집합이 낫겠네요! 굿.
그런데, set 자료구조를 사용하면 서버에 전달해줘야 하는 데이터의 타입과 맞지 않는 문제가 있어 데이터 타입을 변환해 줘야 하는 오버헤드가 추가로 생길 것 같은데 빙봉은 이에 대해서 어떻게 생각하시나요?
// 서버에 보낼 때는 Set을 배열로 변환
const getSelectedDatesArray = () => Array.from(selectedDates);
// 또는 간단하게 스프레드 연산자로 배열 변환
const selectedDatesArray = [...selectedDates];
위 코드처럼 다시 데이터를 변환해줘야할 것 같아요. 시간복잡도 측면에서 개선은 될 것 같으나, 다시 데이터타입 변환을 위한 오버헤드가 생긴다는 점에서는 단점이 될 수 있을 것 같은데, 의견 부탁드립니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
프리코스 때 set 자료구조 많이 사용한 것 같은데 추억이네요~ 소다나~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[C]
@hwinkr
호옴🤸♀️ 좋은 고민 주제네요!
제 생각에는 객체를 배열로 바꿔주는 연산 자체는 큰 오버헤드라고 생각하지 않습니다!
현재 코드에서는 배열에서 값을 찾고 (includes), 제거 (filter)하고, 추가([...])하는 과정이 모두 O(n)의 비용이 발생하네요! 하지만 Set을 사용하면 값을 추가/삭제/확인 연산이 O(1)로 더 빠르고, 최종적으로 배열로 변환할 때만 O(n) 비용이 발생하므로 성능적으로 이득일 것 같아요!
만약, 데이터 변환 오버헤드가 중요한 문제라고 느껴지면 지금 구조를 유지하는 방향으로 가도 될 것 같습니다 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저 같은 경우에는 요렇게 Map으로 구현했으니 참고해보셔도 좋을 것 같아요~!
https://github.com/woowacourse-teams/2024-momo/blob/develop/frontend/src/utils/groupDates.ts
handleEndTimeChange={handleEndTimeChange} | ||
/> | ||
</Field> | ||
<BottomFixedButton onClick={() => console.log('hi')} disabled={isCreateMeetingFormInValid}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚨🚨🚨🚨🚨🚨🚨🚨🚨
🚨Erase console.log()
🚨
🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
||
useEffect(() => { | ||
const handleButtonHeightResize = () => { | ||
if (!visualViewport?.height) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Q]
오호 visualViewport
가 어떤 역할을 하나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MDN-visualViewport
에서도 확인할 수 있듯, 사용자의 실제 화면 영역입니다. 모바일의 경우 입력을 하기 위해서는 모바일 가상 키보드가 올라와야 하는데 이 때, 사용자가 실제로 볼 수 있는 화면의 영역의 크기가 달라지기 때문에 해당 뷰포트에 resize 이벤트 핸들러를 달아서 버튼이 모바일 키보드 위로 올라올 수 있도록 했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해리 포터~ 고생했어요🍀
남겨주신 질문에 대한 답변 남겼습니다! 확인 부탁드려요
const areDatesUnselected = selectedDates.length < 1; | ||
const hasDate = (date: string) => selectedDates.includes(date); | ||
|
||
const handleDateClick = (date: string) => { | ||
setSelectedDates((prevDates) => | ||
hasDate(date) ? prevDates.filter((d) => d !== date) : [...prevDates, date], | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[C]
@hwinkr
호옴🤸♀️ 좋은 고민 주제네요!
제 생각에는 객체를 배열로 바꿔주는 연산 자체는 큰 오버헤드라고 생각하지 않습니다!
현재 코드에서는 배열에서 값을 찾고 (includes), 제거 (filter)하고, 추가([...])하는 과정이 모두 O(n)의 비용이 발생하네요! 하지만 Set을 사용하면 값을 추가/삭제/확인 연산이 O(1)로 더 빠르고, 최종적으로 배열로 변환할 때만 O(n) 비용이 발생하므로 성능적으로 이득일 것 같아요!
만약, 데이터 변환 오버헤드가 중요한 문제라고 느껴지면 지금 구조를 유지하는 방향으로 가도 될 것 같습니다 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해리 남겨준 리뷰 잘 반영해주셔서 감사합니다 :)
하지만 내부로직에 아직 InValid라고 표기된 부분이 있네요!
확인해보고 반영해주시고 다시 말씀해주시면 Approve 드리겠습니다요~
@@ -28,7 +28,6 @@ export default function BottomFixedButton({ | |||
> | |||
{children} | |||
</Button> | |||
<div id="test"></div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
멋쨍이 개발자 🍀
@@ -37,4 +35,6 @@ const useInput = (rules?: ValidationRules): UseInputResult => { | |||
}; | |||
}; | |||
|
|||
export type UseInputReturn = ReturnType<typeof useInput>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해리가 반영해주셔서 너무 기쁘네요 🥹
|
||
const hostNickNameInput = useInput({ | ||
pattern: INPUT_FIELD_PATTERN.nickname, | ||
errorMessage: FIELD_DESCRIPTIONS.nickname, | ||
}); | ||
|
||
const hostPasswordInput = useInput({ | ||
pattern: INPUT_FIELD_PATTERN.password, | ||
errorMessage: FIELD_DESCRIPTIONS.password, | ||
}); | ||
const isHostInfoInValid = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1]
이 부분이 아직 반영이 안된 것 같네요~ 추가 반영 부탁드려용~!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아오 ㅋㅋ 일기써야겠다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
참 잘했어요 💯
- readonly 타입의 문자열 스텝들을 StepType으로 정의 - FunnelProps, StepProps의 타입을 StepType에 포함되는 Steps 타입을 사용해서 정의
- Funnel 컴포넌트의 여러 자식 컴포넌트 중 child 컴포넌트가 스텝에 속하는 컴포넌트인지 확인 - 현재 스텝에 맞는 targetStep 컴포넌트를 반환
- react-router-dom 라이브러리가 제공하는 useLocation, useNavigate 훅을 활용해서 라우팅 기능 구현 - 새로고침해도 현재 스텝은 유지 - Step 컴포넌트는 Funnel의 여러 자식 컴포넌트들로 현재 스텝에 맞는 컴포넌트만 렌더링
- useInput 커스텀 훅의 반환값을 props로 받는 컴포넌트 정의에서 활용할 수 있도록 useInput 커스텀 훅의 반환값 타입을 정의
- useTimeRangeDropdown 커스텀 훅의 반환값을 props로 받는 컴포넌트 정의에서 활용할 수 있도록 useTimeRangeDropdown 커스텀 훅의 반환값 타입을 정의
3074682
to
5c5be28
Compare
* chore: 퍼널 패턴에서 사용할 타입 정의 - readonly 타입의 문자열 스텝들을 StepType으로 정의 - FunnelProps, StepProps의 타입을 StepType에 포함되는 Steps 타입을 사용해서 정의 * feat: 현재 렌더링 해야 할 자식 컴포넌트를 결정하는 FunnelMain 컴포넌트 구현 - Funnel 컴포넌트의 여러 자식 컴포넌트 중 child 컴포넌트가 스텝에 속하는 컴포넌트인지 확인 - 현재 스텝에 맞는 targetStep 컴포넌트를 반환 * feat: 복잡한 UI 흐름을 관리하는 useFunnel 커스텀 훅 구현 - react-router-dom 라이브러리가 제공하는 useLocation, useNavigate 훅을 활용해서 라우팅 기능 구현 - 새로고침해도 현재 스텝은 유지 - Step 컴포넌트는 Funnel의 여러 자식 컴포넌트들로 현재 스텝에 맞는 컴포넌트만 렌더링 * chore: useInput 커스텀 훅의 반환값 타입 정의 - useInput 커스텀 훅의 반환값을 props로 받는 컴포넌트 정의에서 활용할 수 있도록 useInput 커스텀 훅의 반환값 타입을 정의 * chore: useTimeRangeDropdown 커스텀 훅의 반환값 타입 정의 - useTimeRangeDropdown 커스텀 훅의 반환값을 props로 받는 컴포넌트 정의에서 활용할 수 있도록 useTimeRangeDropdown 커스텀 훅의 반환값 타입을 정의 * feat: 약속을 생성할 때 필요한 모든 지역 상태를 관리하는 useCreateMeeting 커스텀 훅 구현 * feat: 약속 이름을 입력받는 컴포넌트 구현 * feat: 약속 주최자 정보(닉네임, 비밀번호)를 입력받는 컴포넌트 구현 * feat: 약속 날짜, 시간 범위를 입력받는 컴포넌트 구현 * feat: 뷰포트 하단에 고정되는 바텀 버튼 컴포넌트 구현 * feat: 모바일 키보드 위로 바텀에 고정된 버튼이 올라올 수 있도록 하는 커스텀 훅 구현 * feat: 약속 생성 UI 흐름을 3단계로 구분하는 약속 생성 페이지 구현 * design: 모바일 화면에서 스크롤이 되는 문제를 해결하기 위해서 height 속성값 수정 * chore: 사용하지 않는 html 태그 제거 * chore: 바텀 고정 버튼 초기 높이 상수화 * chore: RouteFunnel 컴포넌트 오타 수정 * chore: useInput 커스텀 훅의 반환 타입을 ReturnType 유틸리티 타입을 활용하는 것으로 수정 * chore: useTimeRangeDropdown 커스텀 훅의 반환 타입을 ReturnType 유틸리티 타입을 활용하는 것으로 수정 * refactor: 약속 생성 api 함수 추가, 입력 필드 유효성 검증 함수 추가 분리, invalid를 하나의 단어로 사용하도록 수정 * chore: 입력 최소길이 상수 추가 * chore: invalid를 하나의 단어로 사용해서 props 정의, api 요청 함수 추가 * chore: 약속 생성 스텝 네이밍 상수화 * chore: invalid로 props 네이밍 수정 * chore: invalid로 props 네이밍 수정, 약속 생성 api 핸들러 추가 * chore: 사용하지 않는 hidden css 속성 제거 * refactor: 선택된 날짜 상태 관리를 set 자료구조를 사용하는 것으로 수정 * chore: Invalid 단어 오타 수정
관련 이슈
작업 내용
약속을 생성하는 흐름에 퍼널 패턴을 적용했어요.
1. Funnel 컴포넌트
feat: 현재 렌더링 해야 할 자식 컴포넌트를 결정하는 FunnelMain 컴포넌트 구현 해당 커밋 내역에서도 확인할 수 있듯, 퍼널 패턴의 핵심 컴포넌트라고 할 수 있는 Funnel 컴포넌트를 구현했습니다.
위 컴포넌트 구조에서 확인할 수 있듯, Funnel 컴포넌트는 여러 자식 컴포넌트를 가질 수 있어요. 그리고 Funnel.Step 컴포넌트는 문자열 타입의 현재 스텝 데이터를 props로 받습니다. Funnel 컴포넌트는 여러 자식 컴포넌트 중 현재 어떤 컴포넌트를 렌더링해야 할지 결정하는 책임을 가집니다.
리액트에서 제공해 주는 Children API를 활용하여, 자식 컴포넌트들을 배열로 다룰 수 있도록 해줍니다. 이 후, 유효한 퍼널 자식 컴포넌트인지와 props로 넘어오는 step이 스텝 목록에 속하는지를 판단합니다. 스텝 목록에 속하는지 판단하는 이유는 아래와 같아요.
위와 같이 Step 컴포넌트에 props로 유효하지 않은 step을 전달할 수 있는 휴먼 에러를 방지하기 위한 방법이라고 생각해 주시면 될 것 같아요 :)
2. 라우팅
현재 스텝에서 입력해야 하는 값을 모두 입력한 후,
다음
버튼을 클릭하면 라우팅을 해줘야 했습니다. 라우팅 처리를 위해서 가장 많이 했던 고민은 브라우저 새로고침과 관련된 고민이었습니다. 저희 3명의 의견이 갈리기도 했었는데요, 사용자가 새로고침을 하면 어떤 흐름을 제공해 줘야 하는지에 대해위와 같은 의견들이 나왔고, 저희는 두 번째 방법으로 구현하기로 결정했습니다. 그 이유는, 다시 처음부터 입력하기 위해 새로고침을 클릭하는 경우는 잘 없을 것이라 생각했고 뒤로 가기를 통해서 충분히 다시 입력할 수 있는 인터페이스를 제공해줄 수 있다고 생각했기 때문이에요. 새로고침과 관련된 정책이 결정된 후, 어떻게 라우팅 처리를 해줄지 결정하기 위해 다음과 같은 추가적인 고민들을 했어요.
해당 방법을 사용하면, 뒤로가기를 클릭했을 경우 이전 스텝으로 이동하는 것이 아니라 메인 페이지로 이동하게 되는 문제가 생겨서 이 방법은 제외했습니다.
step?={현재스텝}
과 같은 url 쿼리스트링을 사용해당 방법은 바로 url을 입력해서 n번 째 스텝 입력으로 들어오는 사용자가 있을수도 있다고 생각했고, 이 방법은 브라우저 히스토리 관리도 어렵기 때문에 제외했습니다
react-router-dom
라이브러리 활용어떤 방법으로 라우팅을 할지 고민하다가
react-router-dom
라이브러리에서 제공해 주는 useLocation, useNavigate 방법을 사용하기로 했습니다. 위 두 가지 방법에서 발견할 수 있는 문제도 해결되고, 새로고침 했을 때도 현재 스텝이 유지되기 때문에 해당 방법을 사용하기로 했습니다.구현 결과
Screen.Recording.2024-09-21.at.11.23.19.mov
남은 구현 사항
브라우저 저장소와 동기화를 해서 해결할 예정입니다.
특이 사항
브라우저 저장소와 동기화하는 작업까지 하면 PR이 너무 늦어질 것 같아 이대로 PR을 보냅니다.
리뷰 요구사항 (선택)