NEXTSTEP ํ์ด๋ ๋ฏธ์
์ธ ํ์ด๋จผ์ธ ์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ, Payssion์
๋๋ค.
Payment๐ณ + Mission๐ฏ = Payssion๐ฅ
NEXTSTEP <TDD, ํด๋ฆฐ ์ฝ๋ with React> 2๊ธฐ์ ์ฐธ์ฌํด ์งํํ ๋ฏธ์ ํ ํ๋ก์ ํธ์ ๋๋ค. ๋ฏธ์ ์ ์ต์ข ๋ชฉํ๋ ํ์ด๋จผ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ NPM์ ๋ฐฐํฌํ๋ ๊ฒ์ด์์ผ๋ฉฐ, ๊ทธ ๊ณผ์ ์์ ์ฌ์ฉ์์๊ฒ ํธ๋ฆฌํ UI/UX ์ ๊ณต, ํจ๊ป ๊ฐ๋ฐํ๋ ๋๋ฃ๋ฅผ ์ํ ํด๋ฆฐํ ์ฝ๋ ์์ฑ, ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์๋ฅผ ์ํ DX ์ ๊ณต์ ๊ณ ๋ คํ์ต๋๋ค. ํด๋น ํ๋ก์ ํธ๋ ๋ฐฐํฌ์ ์ฑ๊ณตํด ํจ๊ป ๋ฏธ์ ์ ์ฐธ์ฌํ ๋๋ฃ ๋ถ๋ค์ด ์ฌ์ฉ์ค์ด๋ฉฐ, ์ค์ ์ฌ์ฉ์ ๋ํ ํผ๋๋ฐฑ์ ๋ฐํ์ผ๋ก ๊พธ์คํ ๊ฐ์ ํ๊ณ ์์ต๋๋ค.
-
์ค์นํ๊ธฐ
// npm npm i payssion // yarn yarn add payssion
-
์ค๋นํ๊ธฐ (PayssionProvider)
import { PayssionProvider } from 'payssion' function App() { return ( <BrowserRouter> <PayssionProvider> <Routes> {routes.map((route) => ( <Route key={route.path} path={route.path} element={route.element} /> ))} </Routes> </PayssionProvider> </BrowserRouter> ) }
-
๊ฒฐ์ ์์ํ๊ธฐ (initiatePayment)
-
amount, onSuccessAction๋ ํ์์ ๋๋ค.
-
initiatePayment ํ์
type InitiatePaymentParams = { amount: number onSuccessAction: () => void }
-
์์ ์ฝ๋
import { usePayssion } from 'payssion' const PaymentComponent = () => { const [amount, setAmount] = useState(0) const onSuccessAction = () => { // ์ฑ๊ณต ์ ๋์ํ ์ฝ๋ } const { initiatePayment } = usePayssion() return ( <> <SomeComponent /> <PaymentButton onClick={() => initiatePayment({ amount, onSuccessAction })} /> </> ) }
-
๊ฒฐ์ ๋ชจ๋ ์ด๊ธฐ
import { Payssion, isOpen } from 'payssion' const SomeComponent = () => { const { isOpen } = usePayssion() return ( <> <Header /> <Description /> {isOpen && <Payssion />} </> ) }
2023-05-06.16.37.11.mov
์ปดํฌ๋ํธ ์ฌ์ฌ์ฉ์ฑ์ ๋์ด๊ธฐ ์ํ ๊ณ ๋ฏผ
๊ธฐ์กด ๊ฐ๋ฐ ๋ฐฉ์์ ํ์ด์ง๋ฅผ ๊ธฐ์ค์ผ๋ก Top-Down ๋ฐฉ์์ผ๋ก ์์ ์ ํ๋ค๋ฉด, ์ด ํ๋ก์ ํธ์์๋ ๊ฐ๋ฐ์ ์์ํ๊ธฐ ์ ์ ๋ฐ๋ณต ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ฅผ ํ์ ํ๊ณ ์์ ์ปดํฌ๋ํธ๋ถํฐ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ฒ ์์ ์ฌ๋ฆฌ๋ฉด์ Bottom-Up ๋ฐฉ์์ผ๋ก ๊ฐ๋ฐ์ ์งํํ์ต๋๋ค. Thinking in react๋ฌธ์๋ฅผ ๋ฐํ์ผ๋ก 1๋จ๊ณ: UI๋ฅผ ์ปดํฌ๋ํธ ๊ณ์ธต์ผ๋ก ๋๋๊ธฐ, 2๋จ๊ณ: React๋ก ์ ์ ์ธ ๋ฒ์ ๋ง๋ค๊ธฐ, 3๋จ๊ณ: UI state์ ๋ํ ์ต์ํ์ (ํ์ง๋ง ์์ ํ) ํํ ์ฐพ์๋ด๊ธฐ, 4๋จ๊ณ: State๊ฐ ์ด๋์ ์์ด์ผ ํ ์ง ์ฐพ๊ธฐ ์ด๋ ๊ฒ 4๊ฐ์ง ๋จ๊ณ๋ก ์ปดํฌ๋ํธ๋ฅผ ์ค๊ณํ๋๋ก ํ์ต๋๋ค.
ํนํ ํ์ด๋จผ์ธ ์ฑ ํน์ฑ์ ํผ ์ ์ด๋ฅผ ํด์ผํ๋ ์ํฉ์ด ๋ง์๋๋ฐ, CDD(Component Driven Development)์ ๊ธฐ๋ฐํด UI๋ฅผ ๊ตฌ์ฑํ๋ฉด์ Input ์ปดํฌ๋ํธ๋ฅผ ๋ชจ๋ ํผ์์ ์ฌ์ฌ์ฉํ ์ ์์์ต๋๋ค. CDD ๊ธฐ๋ฐ ์ ๊ทผ๋ฒ์ ์ด๊ธฐ์ ์๊ฐ์ด ์์๋๋ ๊ฒ์ฒ๋ผ ๋๊ปด์ก์ง๋ง ์๊ฐ์ด ์ง๋ ์๋ก ์ปดํฌ๋ํธ์ ์ฌ์ฌ์ฉ์ฑ์ ๋ฐํ์ผ๋ก ํ์ฅ์ฑ์ด ๋์์ง๋ค๊ณ ๋๊ผ์ต๋๋ค. ํน์ ์ปดํฌ๋ํธ์์๋ ํ์ ์ปดํฌ๋ํธ๋ฅผ ์ข ์ํ์ง ์์ผ๋ฉด์ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ๋ ํฉ์ฑ ์ปดํฌ๋ํธ๋ฅผ ๋์ ํ ์๋ ์์์ต๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ ์ปดํฌ๋ํธ๋ ์ฌ์ฌ์ฉ์ด ๊ฐ๋ฅํ ๋ ๋ฆฝ์ ์ธ ๋ชจ๋๋ก ๋์ํ์ผ๋ฉฐ ์ถํ์ ํ ์คํธ๋ฅผ ๋์ ํ ๋์๋ ์ด์ ์ด ์์ ๊ฒ์ด๋ผ ํ๋จํ์ต๋๋ค.
// InputTitle
const InputTitle = ({ children }: PropsWithChildren) => {
return <StyledInputTitme>{children}</StyledInputTitme>
}
// Input.tsx
const Input = forwardRef(({ type = 'text', css, ...props }: InputProps, ref: ForwardedRef<HTMLInputElement>) => {
return <InputBasic ref={ref} type={type} css={{ ...css }} {...props} />
})
// InputBox.tsx
const InputBox = ({ children, css }: PropsWithChildren<InputBoxProps>) => {
return <StyledInputBox css={{ ...css }}>{children}</StyledInputBox>
}
// CardExpiredDate.tsx
const CardExpiredDate = ({ expiredDateRef }: CardExpiredDateProps) => {
const { handleInputChange } = useCardExpiredDate()
return (
<InputContainer>
<InputTitle>๋ง๋ฃ์ผ</InputTitle>
<InputBox css={{ width: '50%' }}>
<Input ref={expiredDateRef.first} placeholder="MM" data-name="MM" onInput={handleInputChange} maxLength={2} />
<Input
ref={expiredDateRef.second}
placeholder="YY"
data-name="YY"
onInput={handleInputChange}
maxLength={2}
/>
</InputBox>
</InputContainer>
)
}
// CardAdd.tsx (CardForm ์ญํ )
const CardAdd = () => {
const {
//...
} = useCardAdd()
return (
<PayssionApp>
//...
<CardForm>
<CardForm.CardNumbers />
<CardForm.CardExpiredDate />
<CardForm.CardOwner />
<CardForm.CardSecurityCode />
<CardForm.CardPassword />
</CardForm>
//...
</PayssionApp>
)
}
์ํ๊ฐ ๋ง์์ง๋ค๋ ๊ฒ์ ๊ด๋ฆฌ ํฌ์ธํธ๊ฐ ๋์ด๋๋ ๊ฒ์ด๋ผ๋ ์๊ฐ์ ๋ฐํ์ผ๋ก ๊ธฐ๋ฅ์ด ๋์ํ๋ '์ต์ํ์ ์ํ'๋ก ๋ง๋ค๊ณ ์ ํ์ต๋๋ค. ์๋ฅผ๋ค์ด ์๋ ๊ฐ์ ํค๋ณด๋์ ๊ฒฝ์ฐ ๋ชจ๋ฌ ์ข ๋ฃ๋ฅผ ์ํ ref ์ํ๋ฅผ ์ ์ธํ๊ณ , ์ด๋ค ์ํ๋ ๊ฐ์ง๊ณ ์์ง ์์ต๋๋ค. ๋์ props๋ก ์ ๋ฌ๋ฐ๋ onKeyPress, ๋๋ฉ์ธ ํจ์์ธ getRandomVirtualDigits ๋ฑ์ ๊ฐ์ ์ด์ฉํด ์ํ๋ ์ต์ํํ๋ฉด์ ๊ธฐ๋ฅ์ด ๋์ํ๋๋ก ์์ฑํ์ต๋๋ค. ์ด๋ฅผ ํตํด ์ปดํฌ๋ํธ์ ์ฌ์ฌ์ฉ์ฑ ์ฆ๊ฐ๋ผ๋ ์ถ๊ฐ ์ฅ์ ์ ์ป์ ์ ์์์ต๋๋ค.
const VirtualKeyboard = ({ onKeyPress }: VirtualKeyboardProps) => {
const { modalRef } = useVirtualKeyboard()
return (
<div ref={modalRef}>
<BottomSheetContainer>
<DigitButtonContainer>
{getRandomVirtualDigits().map((digit) => (
<DigitButton key={digit} onClick={() => onKeyPress(String(digit))}>
{digit}
</DigitButton>
))}
</DigitButtonContainer>
</BottomSheetContainer>
</div>
)
}
์ปดํฌ๋ํธ ๊ธฐ๋ฐ์ ๊ฐ๋ฐ ๋ฐฉ์์ ์ ์ฉํ๋ฉฐ, ์คํ ๋ฆฌ๋ถ์ ์ฌ์ฉํ์ฌ ์ปดํฌ๋ํธ์ ๋ ๋ฆฝ์ฑ์ ํ๋ณดํ๊ณ , ์๊ฐ์ ์ผ๋ก ํ ์คํธํ๊ณ ๋ฌธ์ํํ ์ ์์์ต๋๋ค. ์ด๋ฅผ ํตํด, ๊ฐ ์ปดํฌ๋ํธ๊ฐ ๊ฐ๋ณ์ ์ผ๋ก ๊ธฐ๋ํ๋๋ก ๋์ํ๋์ง ํ์ธํ ์ ์์์ผ๋ฉฐ, ๋๋ฒ๊น ๊ณผ์ ๋ ๋จ์ํ๋์์ต๋๋ค. ๋ํ ๊ฐ ์ปดํฌ๋ํธ์ ๋ค์ํ ์ํ๋ฅผ ์์ฝ๊ฒ ์๋ฎฌ๋ ์ด์ ํ ์ ์์ด, UI์ ๊ฐ ์์๊ฐ ์ํธ์์ฉํ๋ ๋ฐฉ์์ ๋ช ํํ๊ฒ ์ดํดํ ์ ์์์ต๋๋ค.
๊ฐ๋ณ์ ์ธ ์ปดํฌ๋ํธ ๋ฟ๋ง ์๋๋ผ ์ปดํฌ๋ํธ๋ฅผ ์กฐํฉํ ํ์ด์ง ์น์ ์ ์ ๊ณตํ์ฌ, ํ์ด์ง ๋จ์์์ ์ด๋ป๊ฒ UI๊ฐ ๋์ํ๋์ง ํ์ธํ ์ ์๋๋ก ํ์ต๋๋ค. ์ด๋ฅผ ํฌ๋ก๋งํฑ์ ์ฌ์ฉํด์ ๋ฐฐํฌํจ์ผ๋ก์จ ์ฌ์ฉ์๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ ์ ๋ฏธ๋ฆฌ ๋์ ๋ฐฉ์์ ์ฝ๊ฒ ํ์ธํ ์ ์์์ต๋๋ค.
Form ์ปดํฌ๋ํธ๋ ์ฌ๋ฌ๊ณณ์์ ์ฌ์ฌ์ฉ๋ ์ ์์ผ๋ฉฐ, ์ธ์ ๋ ์๊ตฌ ์ฌํญ์ ๋ฐ๋ผ ๋ณํํ ์ ์๋ ์ปดํฌ๋ํธ์ ๋๋ค. ํ์ง๋ง Prop์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ์ฃผ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ฒ ๋๋ค๋ฉด, ์๊ตฌ ์ฌํญ์ด ๋ณํํ ๋๋ง๋ค ์ปดํฌ๋ํธ ๋ด๋ถ ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ๊ณ , Prop์ ๋๊ฒจ์ฃผ๋ ๋ฐ์ดํฐ๋ ์ถ๊ฐ๋๋ ๋ฑ์ ๋ถํธํจ์ด ์์ ์ ์์ต๋๋ค. ๊ทธ๋์ ํฉ์ฑ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํด Form ๋ด๋ถ์ ๋ค์ด๊ฐ๋ ์ฝ๋๋ฅผ ์ปดํฌ๋ํธ ์ฌ์ฉ ๋ถ๋ถ์์ ๊ด๋ฆฌํ ์ ์๋๋ก ํ์ต๋๋ค. ์ด๋ฅผ ํตํด Form์ ์๊ตฌ ์ฌํญ์ด ๋ณ๊ฒฝ๋๋๋ผ๋, ์ปดํฌ๋ํธ์ ๋ด๋ถ ๊ตฌํ์ด ์๋๋ผ ์ฌ์ฉ ๋ถ๋ถ์์ ์์ ์ ํด์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
// ์ฌ์ฉ ๋ถ๋ถ
<CardDetailsForm>
<CardDetailsForm.PageTitle buttonElement={<BackButton />} title="์นด๋ ๋ชฉ๋ก์ผ๋ก ๋์๊ฐ๊ธฐ" />
<CardDetailsForm.BigCard
onClickDeleteButton={onClickDeleteButton}
cardNumbers={cardNumbers}
cardName={cardName}
cardOwner={cardOwner}
cardExpiredDate={cardExpiredDate}
cardType={cardType}
/>
<CardDetailsForm.CardAliasInput inputRef={nicknameRef} defaultValue={cardNickname} />
<CardDetailsForm.NavigationButton
additionalClassNames="mt-50"
onBeforeNavigate={handlePreNavigation}
to="CardList"
text="๋ค์"
/>
</CardDetailsForm>
// ๋ด๋ถ ๊ตฌํ
const CardDetailsForm = ({ children }: PropsWithChildren) => {
const bigCard = getCardDetailsFormSubElement(children, BigCard)
const pageTitle = getCardDetailsFormSubElement(children, PageTitle)
const NavigationButton = getCardDetailsFormSubElement(children, NavigationTextButton)
const cardAliasInput = getCardDetailsFormSubElement(children, CardAliasInput)
return (
<CustomPayssionApp>
<Title>{pageTitle}</Title>
{bigCard}
<InputContainer
css={{
flexCenter: 'center',
width: '100%',
}}
>
{cardAliasInput}
</InputContainer>
{NavigationButton}
</CustomPayssionApp>
)
}
CardDetailsForm.PageTitle = PageTitle
CardDetailsForm.BigCard = BigCard
CardDetailsForm.NavigationButton = NavigationTextButton
CardDetailsForm.CardAliasInput = CardAliasInput
ํ์ด์ง ๊ฐ์ ๊ณต์ ๋์ด์ผ ํ๋ ๋ช ๊ฐ์ง ์ํ๋ค์ด ์กด์ฌํ์ต๋๋ค. ํด๋น ํ๋ก์ ํธ์์๋ ๋ชจ๋ฌ, ์นด๋, ์นด๋ ๋ฆฌ์คํธ ์ํ๊ฐ ์ฌ๋ฌ ํ์ด์ง์์ ๊ณต์ ๋์ด์ผ ํ๋ ์ํฉ์ด์์ต๋๋ค. ๊ทธ๋์ ์ ์ญ ์ํ ๊ด๋ฆฌ ๋๊ตฌ๊ฐ ํ์ํ์ต๋๋ค. ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ผ๋ ์ ํ์ง๋ ์กด์ฌํ์ง๋ง, Context API + useReducer๋ฅผ ํ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค. ๊ทธ ์ด์ ๋ ๊ด๋ฆฌ๋์ด์ผ ํ๋ ์ํ์ ์์ ๋ณต์ก๋๊ฐ ๋์ง ์์๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ฌด๋ถ๋ณํ๊ฒ ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ฒ๋ ๊ฒฝ์ฐ ์ถํ์ ๋ง์ด๊ทธ๋ ์ด์ ์ด ํ์ํ ์ํฉ์ด๋ ์ ์ง๋ณด์๋ฅผ ํ๋ ์ํฉ์์ ๋์ฑ ์ด๋ ค์์ด ์์ ๊ฒ์ด๋ผ๊ณ ํ๋จํ๊ณ ๋ฆฌ์กํธ๋ง์ ์ฌ์ฉํด์ ํด๊ฒฐํ๋ ๊ฒ์ผ๋ก ๊ฒฐ์ ํ์ต๋๋ค.
Context API๋ฅผ ์ด์ฉํด ์ ์ญ์์ ๊ด๋ฆฌํ๋ ์นด๋ ์ํ์ ๋ํด ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ ์ต์ ํ๋ฅผ ํ์ต๋๋ค. ์นด๋ ์ํ๋ฅผ ์ด์ฉํ๋ ์นด๋ ํ๋ฆฌ๋ทฐ ์ปดํฌ๋ํธ์ ์นด๋ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ํผ ์ปดํฌ๋ํธ๊ฐ ๋ถ๋ฆฌ๋์ด ์์์ต๋๋ค. ๊ทธ๋์ ์นด๋์ ์ํ๋ฅผ ๋ณ๊ฒฝํ ๋๋ง๋ค ํผ ์ปดํฌ๋ํธ์์๋ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๋ ์ํฉ์ด์์ต๋๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ํ ์ปจํ ์คํธ์ ๋ณ๊ฒฝ ์ปจํ ์คํธ๋ก ๋ถ๋ฆฌํด Provider๋ฅผ ํตํด ๊ฐ๊ฐ ์ ๊ณตํ๋ ๋ฐฉ์์ผ๋ก ์ํคํ ์ฒ๋ฅผ ์ฌ๊ตฌ์ฑํ์ต๋๋ค. ์ด๋ ๊ฒ ๋ถ๋ฆฌํ์ ๊ฒฝ์ฐ, ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ ํ๋ฆฌ๋ทฐ ์ปดํฌ๋ํธ๋ง ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๊ณ , ํผ ์ปดํฌ๋ํธ๋ Reducer ํจ์๋ฅผ ์ฐธ์กฐํ๊ธฐ ๋๋ฌธ์ ์ปจํ ์คํธ์ Value๊ฐ ๋ณ๊ฒฝ๋์ง ์์๋ค๊ณ ํ๋จํด ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ์ง ์์ต๋๋ค. ์ด๋ฅผ ํตํด, ์ปดํฌ๋ํธ ๊ฐ์ ์ํ ๊ณต์ ์ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค. ์ด ๊ณผ์ ์์ ์ํ ๊ด๋ฆฌ ์ ๋ต์ ๊ฐ์ ์ ํตํด ์ปดํฌ๋ํธ ๊ฐ์ ์์กด์ฑ์ ์ค์ด๊ณ , ํจ์จ์ ์ธ ์ปดํฌ๋ํธ ๊ตฌ์กฐ๋ฅผ ์ค๊ณํ๋ ๊ฒ์ ๋ ๊น์ ์ดํด๋ฅผ ์ป์ ์ ์์์ต๋๋ค.
์ฝ๋์ ๊ด์ฌ์ฌ๋ฅผ ๋ถ๋ฆฌํ๊ณ ์ฌ์ฌ์ฉ์ฑ์ ๋์ด๊ธฐ ์ํด ์ปค์คํ ํ ์ ์ ๊ทน์ ์ผ๋ก ํ์ฉํ์ต๋๋ค. ํนํ ํผ, ๋ชจ๋ฌ, API ํธ์ถ ๋ฑ ๋ฐ๋ณต์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋ก์ง์ ๋ํด ์ปค์คํ ํ ์ ์ ์ฉํ์ต๋๋ค. ์ด๋ฅผ ํตํด ์ปดํฌ๋ํธ๊ฐ UI๋ผ๋ ๋จ์ผ ์ฑ ์์ ์ง์คํ ์ ์๋๋ก ํ์ผ๋ฉฐ, ๋น์ฆ๋์ค ๋ก์ง์ ์จ๊น์ผ๋ก์จ ์ฝ๋์ ๊ฐ๋ ์ฑ์ด ์ฆ๊ฐํ์ต๋๋ค. ๋ํ hook ํด๋๋ฅผ ์ปดํฌ๋ํธ์ Co-Location ๋๋๋ก ์์น์์ผฐ์ต๋๋ค. ์ด๋ฅผ ํตํด ์ ์ง๋ณด์์ ํ์ํ ์ฝ๋๋ฅผ ์ฝ๊ฒ ์ฐพ์ ์ ์๋๋ก ํ์ต๋๋ค.
const CardAdd = () => {
const {
numbersRef,
passwordRef,
expiredDateRef,
ownerRef,
securityCodeRef,
openValidToast,
setOpenValidToast,
onBeforeNavigate,
isValidCardInfo,
} = useCardAdd()
return (
//...
)
}
CardAdd
โฃ components
โ โฃ CardForm
โ โ โฃ components
โ โ โฃ hooks --> CardForm ๋ด๋ถ ์ปดํฌ๋ํธ์ ๋ํ ์ปค์คํ
ํ
โ โ โฃ CardForm.tsx
โ โ index.ts
โฃ hooks --> CardAdd ์ปดํฌ๋ํธ์ ๋ํ ์ปค์คํ
ํ
โฃ CardAdd.tsx
์นด๋ ์ถ๊ฐ ํ์ด์ง์์ ์ ์ฒด ์ ์ด ์ปดํฌ๋ํธ -> ๋ถ๋ถ ๋น์ ์ด ์ปดํฌ๋ํธ -> ์ ์ฒด ๋น์ ์ด ์ปดํฌ๋ํธ๋ก ๋ฆฌํฉํ ๋งํ์ต๋๋ค. ์ด๊ธฐ์๋ ๊ฐ์ฅ ํธํ๊ฒ ์ ํ๋ ์ ์ด ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ผ๋, ์ฝ๋๋ฅผ ๋ถ์ํ๋ฉด์ ๋ณด์์ฝ๋์ ์นด๋ ๋น๋ฐ๋ฒํธ ์ ๋ ฅ์ ๋ํด์๋ ์นด๋ ์ปดํฌ๋ํธ์ ์ฆ๊ฐ์ ์ผ๋ก ๋ฐ์์ด ๋์ง ์์๋ ๋๋ ๊ฒ์ ํ์ ํ์ต๋๋ค. ๊ทธ๋์ ํด๋น ์ ๋ ฅ์ ๋ํด์๋ ๋น์ ์ด ์ปดํฌ๋ํธ๋ก ๋์ํ๋๋ก ์์ ํ์ต๋๋ค.
์ดํ ์นด๋์ ์ํ๊ฐ ๋ณด์ฌ์ง๋ ์นด๋ ์ปดํฌ๋ํธ์ ์นด๋์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ํผ ์ปดํฌ๋ํธ๊ฐ ๋ถ๋ฆฌ๋์ด์๊ณ , ํผ ์ปดํฌ๋ํธ์์ ์ฆ๊ฐ์ ์ธ ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ํ์ํ์ง ์๋ค๋ ๊ฒ์ ํ์ ํ์ต๋๋ค. Context API์์ ์กฐํฉ์ผ๋ก ์ ์ฒด ์ ๋ ฅ์ ๋ํด์ ๋น์ ์ด ์ปดํฌ๋ํธ๋ฅผ ํ์ฉํ ์ ์๋๋ก ์์ ํ์ต๋๋ค. ์ด๋ฅผ ํตํด ์ ์ด ์ปดํฌ๋ํธ์ ๋น์ ์ด ์ปดํฌ๋ํธ์ ๋ํด์ ๊น๊ฒ ์ดํดํ ์ ์์์ผ๋ฉฐ ์ถํ ํผ ์ ์ด์ ๋ฐฉํฅ์ฑ์ ์ก๋ ๋ฐ์ ๊ธฐ์ค์ ์ ๊ฐ์ง ์ ์๊ฒ ๋์์ต๋๋ค. ํด๋น ๊ฒฝํ์ ๋ธ๋ก๊ทธ๋ก ์ ๋ฆฌํด์ ๊ณต์ ํ์ต๋๋ค. ์์ธํ
- ํผ ์ ์ด๊ฐ ํ์ํ Input์ ์นด๋ ๋ฒํธ, ๋ง๋ฃ์ผ, ์นด๋ ์์ ์, ๋ณด์์ฝ๋, ๋น๋ฐ๋ฒํธ ๋ค์ฏ๊ฐ์ง์ ๋๋ค.
- ์ฒ์์๋ ์ต์ํ๋ ์ ์ด ์ปดํฌ๋ํธ๋ก ๋ชจ๋ ํผ ์ ์ด๋ฅผ ์๋ฃํ์ต๋๋ค.
- ๋ฆฌํฉํ ๋ง์ ์งํํ๋ฉฐ ๋ณด์ ์ฝ๋, ๋น๋ฐ๋ฒํธ ์ํ๊ฐ์ ๋ค์ ๋ฒํผ์ ๋๋ฅผ ๋์๋ง ํ์ํ๋ค๋ ๊ฒ์ ์ธ์งํ์ต๋๋ค.
- ๋ณด์ ์ฝ๋, ๋น๋ฐ๋ฒํธ์ ๋ํด์๋ ๋น์ ์ด ์ปดํฌ๋ํธ๋ก ๊ด๋ฆฌํ์ต๋๋ค.
-
์นด๋ ๋ฒํธ, ๋ง๋ฃ์ผ, ์นด๋ ์์ ์ ์ ๋ณด ๋ํ ์ํ๊ฐ์ด ํผ ๋ด๋ถ์์ ํ์ํ ๊ฒ์ด ์๋๋ผ ์ธ๋ถ ์ปดํฌ๋ํธ(์นด๋)์์ ํ์ํ๋๋ ๊ฒ์ ์ธ์งํ์ต๋๋ค.
-
๋ชจ๋ ํผ ์ ์ด๋ฅผ ๋น์ ์ด๋ก ์๋ํ๊ธฐ ์ํด Context API์ ๊ฒฐํฉํด ๊ตฌํํ์ต๋๋ค.
-
์๋ ์ฝ๋๋ CardExpiredDate์์ Context API์ ๋น์ ์ด๋ฅผ ์ฌ์ฉํ ์์์ ๋๋ค.
- CardExpiredDate ์ปดํฌ๋ํธ์์ ์ฌ์ฉ๋๋ handleInputChange๋ ๊ฒฐ๊ตญ useCardInfo์์ ์ ๊ณตํ๋ handleExpiredDate ํจ์์ด๋ฉฐ, ์ด ํจ์๋ cardDispatch๋ฅผ ์ํํฉ๋๋ค.
- ์ด๋ฅผ ํตํด ์ ์ญ Context์ ๋ง๋ฃ์ผ ์ํ๊ฐ์ ์ ์ฅํ๋ฉฐ, Dispatch ์ปจํ ์คํธ๋ง ์๋นํ๊ธฐ ๋๋ฌธ์ ๋ฆฌ๋ ๋๋ง์ ๋ฐ์ํ์ง ์์ต๋๋ค.
// CardExpiredDate.tsx const CardExpiredDate = ({ expiredDateRef }: CardExpiredDateProps) => { const { handleInputChange } = useCardExpiredDate() return ( <InputContainer> <InputTitle>๋ง๋ฃ์ผ</InputTitle> <InputBox css={{ width: '50%' }}> <Input ref={expiredDateRef.first} placeholder="MM" data-name="MM" onInput={handleInputChange} maxLength={2} /> <Input ref={expiredDateRef.second} placeholder="YY" data-name="YY" onInput={handleInputChange} maxLength={2} /> </InputBox> </InputContainer> ) } // useCardExpiredDate.ts const useCardExpiredDate = () => { const { handleExpiredDate } = useCardInfo() const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => { const { dataset: { name }, value, } = event.target switch (name) { case 'YY': if (value.length === 2 && !isYearValid(Number(value))) { alert('์ ํจ๊ธฐ๊ฐ์ด ๋ง๋ฃ๋์์ต๋๋ค.') event.target.value = '' return } handleExpiredDate({ value, yymm: name }) break case 'MM': if (Number(value) > 12 || Number(value) < 0) { alert('์์ 1์ด์ 12์ดํ์ฌ์ผ ํฉ๋๋ค.') event.target.value = '' return } handleExpiredDate({ value, yymm: name }) break } } return { handleInputChange } } // useCardInfo.ts const useCardInfo = () => { const cardDispatch = useContext(CardDispatchContext) const handleExpiredDate = ({ value, yymm }: HandleExpiredDateProps) => { if (!isNumber(value)) return switch (yymm) { case 'YY': cardDispatch({ type: 'SET_EXPIRED_YEAR', payload: value, }) break case 'MM': cardDispatch({ type: 'SET_EXPIRED_MONTH', payload: value, }) break } } // ... return { handleExpiredDate, // ... } }
NPM ๋ฐฐํฌ๋ฅผ ์ํ ๋น๋ ํ์ผ ์์ฑ ๊ณผ์ ์์ Rollup์ ํ์ฉํ์ฌ ESM, CJS ๋ชจ๋ ํ๊ฒฝ์ ๊ณ ๋ คํ์ต๋๋ค. ๋ํ TypeScript๋ฅผ ์ง์ํ์ต๋๋ค. Create React App(CRA)๋ฅผ ์ฌ์ฉํ๋ฉฐ ์ ๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด Craco๋ฅผ ํตํด ์ค๋ฒ๋ผ์ด๋ํ๋ ๋ฑ์ ์ค์ ์ ์ถ๊ฐํ๊ธฐ ๋๋ฌธ์, ์ด๊ธฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ฉ ๋น๋ ํ์ผ ์์ฑ ๊ณผ์ ์ ์ด๋ ค์์ด ์์์ต๋๋ค. ํ์ง๋ง Rollup์ ์ฌ์ฉํด ์ ๋ ๊ฒฝ๋ก๋ฅผ Craco์ ์ค์ ๊ณผ ๋ง์ถฐ์ฃผ๊ณ , ์ฌ๋ฌ ๋ชจ๋ ํ๊ฒฝ์ ๋์ํ๋ ๋น๋ ํ์ผ์ ๋ง๋ค์ด ํด๊ฒฐํ์ต๋๋ค. ์ด๋ฅผ ํตํด ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ์์ ํ๊ฒฝ์ ๋ฐ๋ผ ์๋ง์ ๋ชจ๋ ํ๊ฒฝ์ ์ ๊ณตํ ์ ์์์ผ๋ฉฐ, ์ ์ ํ์ ์ ๊ณต์ ํตํด ํ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ๋์์ต๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐฐํฌ๋ฅผ ์ํ ๋ค์ํ ์ค์ ์ ๋ํด ๋ฐฐ์ฐ๊ณ ์ด๋ฅผ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๊ฒฝํ์ ํ ์ ์์์ต๋๋ค.
CSS ํด๋์ค ์ด๋ฆ ์ค๋ณต ๋ฌธ์ ๋ก ์ธํด ์ฌ์ฉ์๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ์ UI๊ฐ ๊นจ์ง๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ฒ์์๋ CSS ๋ชจ๋์ ์ฌ์ฉํด์ ํด๊ฒฐํ๊ณ ์ ํ์ผ๋ ๋ชจ๋ ํ๊ฒฝ ์ค์ , ๊ฐ ์ปดํฌ๋ํธ ์ฝ๋ ์์ ๋ฑ์ด ํ์ํ๋ค๋ ๊ฒ์ ํ์ ํด์ CSS-In-JS๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋์ ๋น์ฉ์ด ํฌ๊ฒ ๋ค๋ฅด์ง ์๋ค๊ณ ํ๋จํ์ต๋๋ค. ๊ทธ๋์ Near Zero Runtime ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Stitches๋ฅผ ์ด์ฉํด ํด์๋ ํด๋์ค ์ด๋ฆ์ ์ฌ์ฉํจ์ผ๋ก์จ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์์ต๋๋ค.
- PayssionProvider, Payssion, usePayssion ์ธ ๊ฐ์ง๋ก ๋ถ๋ฆฌํด์ ๊ฐ๊ฐ์ ์ ์ ์๊ฒ ์ ๋ฌํ๋๋ก ํ์ต๋๋ค.
- PayssionProvider๋ ํ์ด๋จผ์ธ ์ฑ์ ์ฌ์ฉํ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ๋ Provider ํจ์์ ๋๋ค.
- Payssion์ ์ค์ ์ฌ์ฉ ์ปดํฌ๋ํธ์ ๋๋ค.
- usePayssion์ PayssionProvider์์ ์ ๊ณตํ๋ ๊ฐ ์ค์ ์ ์ ๊ฐ ์ค์ ํ์ํ ๊ฐ๋ง ์ ๊ณตํ๋ ์ปค์คํ ํ ์ ๋๋ค. initiatePayment, isOpen, closePayment ์ธ ๊ฐ์ง์ ๊ฐ์ ์ ๊ณตํฉ๋๋ค.