May 05, 2020
์ด๋ฒ์ ๋ง๋ค์ด๋ณธ ์๋ง๋ฒ ํ๋ก์ ํธ๋ ๋ก๋ ์ดํ๋ฆฌ์ผ์ด์ ์ผ๋ก, OOP study ๋ฏธ์ ์ค ํ๋์ด๋ค. OOP ์ ์ ๋ํ ์คํธ์ ์ค์ฌ์ ๋์ด์ ์ฝ๋๋ฅผ ์ง๋ณด์๊ณ , ์ฐ์ด๋ ํจ์๋ค์ ์ต๋ํ ๋ชจ๋ํ์์ผ์ ์ฝ๋๋ฅผ ์ง๋ณด์๋ค.
๊ตฌ์ ๊ธ์ก์ผ๋ก ์์. (์ซ์์ ๋ ฅ)
๋ก๋ 1๋ฑ์ ๋น์ฒจ๋์๋ค..! ใ ใ
๋ก๋ ํ๋ก๊ทธ๋จ์ ๊ฒฝ์ฐ, ์ ๋ ฅํผ submit ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ํ, ์กฐ๊ฑด์ ๋ง๋ ๋ค์ ์ ๋ ฅํผ์ด ๋์์ผ ํ๊ธฐ ๋๋ฌธ์ react hooks๋ฅผ ์ด์ฉํด์ ๊ฐ์ด true๊ฐ ๋์์ ์, ๋ค์ ์ ๋ ฅํผ์ ์์ฑํด์ฃผ๋๋ก ํด์ฃผ์๋ค.
const [gotALottoCount, setGotALottoCount] = useState(false)
const onSubmitPurchaseAmount = (e: any) => {
e.preventDefault()
setGotALottoCount(false)
//...
setGotALottoCount(true)
}
return
{
gotALottoCount && (
<form onSubmit={onSubmitManualLottoCount}>
<label htmlFor="manualLottoCountInput">
์๋์ผ๋ก ๊ตฌ๋งคํ ๋ก๋ ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.{' '}
</label>
<input
id="manualLottoCountInput"
type="text"
value={manualLottoCount}
onChange={onChangeManualLottoCount}
></input>
<button type="submit">์
๋ ฅ</button>
</form>
)
}
์๋ฅผ ๋ค์ด, onSubmitPurchaseAmount
๊ฐ ๊ตฌ์
๊ธ์ก ์
๋ ฅํผ์ ๋ํ submit ์ด๋ฒคํธํธ๋ค๋ฌ ํจ์์ธ๋ฐ, ํด๋น ํจ์๊ฐ ๋๋๊ณ setGotALottoCount(true)
๋ฅผ ํตํด gotALottoCount๋ฅผ true๋ก ๋ฐ๊ฟ ์ฃผ์ด์, ๋ค์ form ์๋ฆฌ๋จผํธ๊ฐ ์์ฑ๋๋๋ก ํด์ฃผ์๋ค. ๊ทธ๋ฆฌ๊ณ submit ์ด๋ฒคํธํธ๋ค๋ฌ ํจ์ ์ด๋ฐ์ setGotALottoCount(false)
๋ผ๋ ์ด๊ธฐ๊ฐ ๋ฆฌ์
์ฝ๋๋ ๋ฃ์ด์ฃผ์๋ค.
react hooks ๋ฅผ ํตํด, ์ ํจ์ฑ ๊ฒ์ฌ์ ๋ฐ๋ฅธ ์๋ฌ๋ฉ์์ง ์ถ๋ ฅ๋ ํด์ฃผ์๋ค. ์๋ฌ๋ฉ์์ง์ ๋ํ ๋ค์ด๋ฐ์ ํ๋ค๋ณด๋ ๋ณ์๋ช ์ด 40์๊ฐ ๋์ด๊ฐ๋ ๊ฒฝ์ฐ๊ฐ ํ๋คํ๋ค. ใ ใ ๋ณ์๋ช ์ ๋ํ ๊ณ ๋ฏผ๋ ๋ค์ ํด๋ด์ผ๊ฒ ๋ค..
์๋ฌ๋ฉ์์ง์ ๋ํ ๋ณ์๋ช ํด๊ฒฐ! ํด๊ฒฐํ ๋ฐฉ๋ฒ์ผ๋ก ์ด๋
const [purchaseAmountError, setPurchaseAmountError] = useState('')
return
{
purchaseAmountError === 'PURCHASE_AMOUNT_IS_BLANK_ERROR' && (
<div style={{ color: 'red' }}>๊ตฌ์
๊ธ์ก์ ์
๋ ฅํ์ง ์์์ต๋๋ค.</div>
)
}
{
purchaseAmountError === 'PURCHASE_AMOUNT_IS_NOT_NUMBER_ERROR' && (
<div style={{ color: 'red' }}>๊ตฌ์
๊ธ์ก์ ์ซ์๋ก ์
๋ ฅํด์ฃผ์ธ์.</div>
)
}
{
purchaseAmountError ===
'PURCHASE_AMOUNT_IS_LESS_THAN_MINIMUM_AMOUNT_ERROR' && (
<div style={{ color: 'red' }}>
๋ก๋ ์ต์ ๊ตฌ์
๊ฐ๊ฒฉ์ {LOTTO_PRICE}์์
๋๋ค.
</div>
)
}
Validator(์ ํจ์ฑ๊ฒ์ฌ)๋ ๋ชจ๋๋ก ๋ฐ๋ก ๊ด๋ฆฌํด์ฃผ์๊ณ ์๋ฌ๋ฐ์์ ์๋ฌ๋ฉ์์ง ๋ฌธ์์ด์ ๋ฆฌํดํ๊ณ , ์๋ฌ๊ฐ ์์ผ๋ฉด ์ ๋ ฅ๊ฐ์ ๊ทธ๋๋ก ๋ฆฌํดํ๋๋ก ํด์ฃผ์๋ค.
// App.tsx
import { validatePurchaseAmountInput } from './modules/formValidator'
const onSubmitPurchaseAmount = (e: any) => {
e.preventDefault()
setPurchaseAmountError('')
setGotALottoCount(false)
const validatedPurchaseAmount = validatePurchaseAmountInput(
purchaseAmount,
LOTTO_PRICE
)
if (validatedPurchaseAmount === 'PURCHASE_AMOUNT_IS_BLANK_ERROR') {
return setPurchaseAmountError('PURCHASE_AMOUNT_IS_BLANK_ERROR')
}
if (validatedPurchaseAmount === 'PURCHASE_AMOUNT_IS_NOT_NUMBER_ERROR') {
return setPurchaseAmountError('PURCHASE_AMOUNT_IS_NOT_NUMBER_ERROR')
}
if (
validatedPurchaseAmount ===
'PURCHASE_AMOUNT_IS_LESS_THAN_MINIMUM_AMOUNT_ERROR'
) {
return setPurchaseAmountError(
'PURCHASE_AMOUNT_IS_LESS_THAN_MINIMUM_AMOUNT_ERROR'
)
}
const _purchaseAmount = Number(validatedPurchaseAmount)
const lottoCount = Math.floor(_purchaseAmount / LOTTO_PRICE)
setLottoCount(lottoCount)
setGotALottoCount(true)
}
์ ํจ์ฑ๊ฒ์ฌ ๋ชจ๋ formValidator.ts
ํ์ผ ์์์ ํจ์ํ๋ํ๋ export ํด์ฃผ์๋ค.
// formValidator.ts
export const validatePurchaseAmountInput = (
purchaseAmount: string,
lottoPrice: number
) => {
let _purchaseAmount = purchaseAmount.trim()
if (_purchaseAmount.length === 0) {
return 'PURCHASE_AMOUNT_IS_BLANK_ERROR'
}
const purchaseAmountHasString =
_purchaseAmount && _purchaseAmount.match(/\D/g)
if (purchaseAmountHasString !== null && purchaseAmountHasString.length >= 0) {
return 'PURCHASE_AMOUNT_IS_NOT_NUMBER_ERROR'
}
if (_purchaseAmount.length < String(lottoPrice).length) {
return 'PURCHASE_AMOUNT_IS_LESS_THAN_MINIMUM_AMOUNT_ERROR'
}
return _purchaseAmount
}
๋ก๋ ํ์ฅ์ ์๋ฏธํ๋ Lotto ๊ฐ์ฒด๋ฅผ class ๋ฌธ๋ฒ์ ์ฌ์ฉํด์ ๋ง๋ค์ด์ฃผ์๋ค.
export class Lotto {
numbers: number[] = []
constructor(numbers: number[]) {
this.numbers = numbers
}
}
์๋ ์ฝ๋๋ ์๋๋ก๋ ๋ฒํธ๋ฅผ Lotto ๊ฐ์ฒด ๋ฐฐ์ด ์์ ๋ฃ๋ ์์์ธ๋ฐ, ์ ๋ ฅ๋ฐ์ ๋ฌธ์์ด์ ๋ถ๋ฆฌํ๊ณ ์ซ์๋ก ๋ณํํ ํ sorting๊น์ง ํด์ Lotto๊ฐ์ฒด์ ๋ด์์ฃผ์๋ค. ๊ทธ๋ฆฌ๊ณ Lotto ๊ฐ์ฒด ๋ฐฐ์ด์ push ํด์ฃผ์๋ค.
์ด ์ญ์, ์ ๋๋ก ์ํํ๊ธฐ ์ํด์๋ Validator(์ ํจ์ฑ ๊ฒ์ฌ)๋ฅผ ํตํด ๋ชจ๋ ์๋ฌ๋ฅผ ์ก์์ฃผ๋ ๊ฒ์ด ์ ๊ฒฐ์กฐ๊ฑด์ด์๋ค.
const _myLottos: Lotto[] = []
for (let i = 0; i < Number(manualLottoCount); i++) {
const _manualLotto = manualLottos[i].split(',')
const manualLottoNumbers = _manualLotto.map(lottoNumber =>
Number(lottoNumber)
)
const sortedManualLottoNumbers = manualLottoNumbers.sort((a, b) => a - b)
_myLottos.push(new Lotto(sortedManualLottoNumbers))
}
WinningLotto ๊ฐ์ฒด๋ ์ฐ์น๋ก๋์ ๋ํ ์ ๋ณด์ ์ ์ ๋ก๋์ ๋น๊ตํ๋ ๋ฉ์๋๋ฅผ ๊ฐ๋ ๊ฐ์ฒด๋ก, ๋ง์ฐฌ๊ฐ์ง๋ก class ๋ฌธ๋ฒ์ ์ด์ฉํด์ ๋ง๋ค์ด์ฃผ์๋ค.
import { Lotto } from './Lotto'
export class WinningLotto {
lotto: Lotto
bonusNo: number = 0
constructor(lotto: Lotto, bonusNo: number) {
this.lotto = lotto
this.bonusNo = bonusNo
}
match(userLotto: Lotto) {
let count = 0
let bonusCount = 0
this.lotto.numbers.map(number => {
userLotto.numbers.indexOf(number) >= 0 && count++
})
userLotto.numbers.indexOf(this.bonusNo) >= 0 && bonusCount++
if (count === 6) return 'FIRST'
if (count === 5 && bonusCount) return 'SECOND'
if (count + bonusCount === 5) return 'THIRD'
if (count + bonusCount === 4) return 'FOURTH'
if (count + bonusCount === 3) return 'FIFTH'
return 'MISS'
}
}
match
๋ฉ์๋๋ ๋ง์ ๋ก๋ ๊ฐ์์ ๋ฐ๋ฅธ ๋ฑ์๋ฅผ ๋ฆฌํดํ๋ฏ๋ก ์ด๋ฐ์์ผ๋ก ์ฌ์ฉํด์ฃผ์๋ค.
const RANKS = {
FIRST: 0,
SECOND: 0,
THIRD: 0,
FOURTH: 0,
FIFTH: 0,
MISS: 0,
}
for (let i = 0; i < myLottos.length; i++) {
RANKS[winningLotto.match(myLottos[i])]++
}
๋ก๋์ ๋ํ ํจ์๋ชจ๋์ ๋ชจ์๋๋ lottoFunctions.ts ๋ฅผ ๋ง๋ค์ด์ฃผ์๋ค. ์ด ํ์ผ ๋ด์์๋ ๋๋ค ๋ก๋๋๋ฒ๋ฅผ ์์ฑํ๋ ํจ์์, ๊ทธ์ ๋ง๋ ๋ก๋ ๊ฐ์ฒด๋ฅผ ์์ฑํด์ ๋ก๋ ๊ฐ์ฒด๋ฐฐ์ด์ ๋ฆฌํดํ๋ ํจ์ ๋๊ฐ๋ฅผ ๋ง๋ค์ด์ฃผ์๋ค. ๊ทธ๋ฆฌ๊ณ ๋ก๋ ๊ท์น์ด ๋ฌ๋ผ์ง ์ ์์ผ๋, ์์๋ํ ์ธ๋ถ์์ ๊ด๋ฆฌํ ์ ์๋๋ก LOTTO_NUMBERS
(๋ชจ๋ ๋ก๋๋ฒํธ๊ฐ ๋ค์ด์๋ ๋ฐฐ์ด : [1,2,3โฆ45]), LOTTO_COUNT
(๋ก๋๋ฅผ ์ ํํ ์ ์๋ ํ์ : 6) ์ parameter๋ก ๋ฃ๊ฒ ํด์ฃผ์๋ค.
// lottoFunctions.ts
import { Lotto } from './Lotto'
export const makeAutomaticLotto = (
lottoCount: number,
LOTTO_NUMBERS: number[],
LOTTO_COUNT: number
) => {
const lottos: Lotto[] = []
for (let i = 0; i < lottoCount; i++) {
lottos.push(
new Lotto(
setRandomNumbers(LOTTO_NUMBERS, LOTTO_COUNT).sort(
(a: number, b: number) => a - b
)
)
)
}
return lottos
}
export const setRandomNumbers = (
LOTTO_NUMBERS: number[],
LOTTO_COUNT: number
) => {
let takenLottoNumbers = [...LOTTO_NUMBERS]
const resultNumbers: number[] = []
for (let j = 0; j < LOTTO_COUNT; j++) {
const randomNumber = Math.floor(
Math.random() * (LOTTO_NUMBERS[LOTTO_NUMBERS.length - 1] - j)
)
const chosen = takenLottoNumbers.splice(randomNumber, 1)[0]
resultNumbers.push(Number(chosen))
}
return resultNumbers
}
์ ๋ํ ์คํธ๋ given, when, then 3๊ฐ์ง ๋จ๊ณ๋ก ๋ฐ์ํ ์ ์๋ ๋ชจ๋ ๊ฒฝ์ฐ๋ฅผ (์ต๋ํ ใ ) ํ ์คํธ ํด์ฃผ์๋ค. ์๋๋ WinningLotto์ match ๋ฉ์๋์ ๋ํ ํ ์คํธ์ ์์ด๋ค.
// WinningLotto.test.ts
import { WinningLotto } from './WinningLotto'
import { Lotto } from './Lotto'
describe('WinningLotto๊ฐ์ฒด match๋ฉ์๋', () => {
// given
const FirstLotto = new Lotto([1, 2, 3, 4, 5, 6])
const winningLotto = new WinningLotto(FirstLotto, 7)
it('WinningLotto๊ฐ์ฒด์ 6๊ฐ์ ์ซ์ ๋ชจ๋ ๋ง์ ๊ฒฝ์ฐ, 1๋ฑ์ ๋ฆฌํดํ๋์ง ํ์ธ', () => {
// when
const myLotto = new Lotto([1, 2, 3, 4, 5, 6])
const result = winningLotto.match(myLotto)
// then
expect(result).toStrictEqual('FIRST')
})
it('WinningLotto๊ฐ์ฒด์ 5๊ฐ์ ์ซ์, 1๊ฐ์ ๋ณด๋์ค๋ณผ์ด ๋ง์ ๊ฒฝ์ฐ, 2๋ฑ์ ๋ฆฌํดํ๋์ง ํ์ธ', () => {
// when
const myLotto = new Lotto([1, 2, 3, 4, 5, 7])
const result = winningLotto.match(myLotto)
// then
expect(result).toStrictEqual('SECOND')
})
it('WinningLotto๊ฐ์ฒด์ 5๊ฐ์ ์ซ์๋ง ๋ง์ ๊ฒฝ์ฐ, 3๋ฑ์ ๋ฆฌํดํ๋์ง ํ์ธ', () => {
// when
const myLotto = new Lotto([1, 2, 3, 4, 5, 8])
const result = winningLotto.match(myLotto)
// then
expect(result).toStrictEqual('THIRD')
})
it('WinningLotto๊ฐ์ฒด์ 4๊ฐ์ ์ซ์๋ง ๋ง์ ๊ฒฝ์ฐ, 4๋ฑ์ ๋ฆฌํดํ๋์ง ํ์ธ', () => {
// when
const myLotto = new Lotto([1, 2, 3, 4, 8, 9])
const result = winningLotto.match(myLotto)
// then
expect(result).toStrictEqual('FOURTH')
})
it('WinningLotto๊ฐ์ฒด์ 3๊ฐ์ ์ซ์๋ง ๋ง์ ๊ฒฝ์ฐ, 5๋ฑ์ ๋ฆฌํดํ๋์ง ํ์ธ', () => {
// when
const myLotto = new Lotto([1, 2, 3, 8, 9, 10])
const result = winningLotto.match(myLotto)
// then
expect(result).toStrictEqual('FIFTH')
})
it('WinningLotto๊ฐ์ฒด์ ๋ง๋ ์ซ์๊ฐ 3๊ฐ๋ ์์ ๊ฒฝ์ฐ MISS๋ฅผ ๋ฆฌํดํ๋์ง ํ์ธ', () => {
// when
const myLotto = new Lotto([8, 9, 10, 11, 12, 13])
const result = winningLotto.match(myLotto)
// then
expect(result).toStrictEqual('MISS')
})
})
ํ ์คํธ๋ฅผ ํ๋ฉด์ ๋ฌด์กฐ๊ฑด ์๋ฌ๊ฐ ๋์ง ์๊ฒ ์ง? ์๊ฐํ์ง๋ง ์ค์๋ ๊ณณ๊ณณ์ ์จ๊ฒจ์ ธ ์์๋ค. ใ ใ ;; ์ด๋ฒ์ ํ ์คํธ์ ํ์์ฑ์ ํ์คํ ๋๋ผ๊ฒ ๋์๋ ๊ฒ ๊ฐ๋ค.
์๋ฌ ์ถ๋ ฅ์ ๋ํ ๋ณ์๋ช ์ด ๊ธฐ๋ณธ 30์์์ 40์๊น์ง ๋์ด๊ฐ๋ ๊ฒฝ์ฐ๊ฐ ์์๋ค. ์ต๋ํ ๊ธฐ๋ฅ์ ์์๋ณผ ์ ์๊ฒ ์ฐ๋ คํ์ง๋ง ๋๋ฌด ๊ธธ์ด๋ ํ๋์ ํ์ ํ๊ธฐ๊ฐ ์ฝ์ง ์์๋ค. ์ด๋ด ๊ฒฝ์ฐ๋ ์ด๋ป๊ฒ ํด์ผ ์ข์ ์ง ์ข ๋ ๊ณ ๋ฏผ ํด๋ด์ผํ ๊ฒ ๊ฐ๋ค.
์ข์ ๋ฐฉ๋ฒ์ด ์๊ฐ๋์ ๋ณ์๋ช ๊ธธ์ด๋ฅผ ์ค์ฌ๋ณด์๋ค.
๊ธฐ์กด์ ์ฝ๋๋ ์๋ฌ๋ฉ์์ง์ ๋ด์ฉ์ ๊ด๋ จ๋ ๋ณ์๋ฅผ ํ๋ํ๋ ๋ฐ๋ก ๋ฌ์, boolean ๊ฐ์ด ๋ฐ๋๋ฉด ํด๋น ์๋ฌ๋ฉ์์ง ๋ด์ฉ์ ์ถ๋ ฅํ๋๋ก ํ์๋ค.
๊ธฐ์กด์ ์ฝ๋ badโฆ
// App.tsx
import { validatePurchaseAmountInput } from './modules/formValidator'
const [purchaseAmountIsBlankError, setPurchaseAmountIsBlankError] = useState(
false
)
const [
purchaseAmountIsNotNumberError,
setPurchaseAmountIsNotNumberError,
] = useState(false)
const [
purchaseAmountIsLessThanMinimumAmountError,
setPurchaseAmountIsLessThanMinimumAmountError,
] = useState(false)
const onSubmitPurchaseAmount = (e: any) => {
e.preventDefault()
setPurchaseAmountIsBlankError(true)
setPurchaseAmountIsNotNumberError(true)
setPurchaseAmountIsLessThanMinimumAmountError(true)
const validatedPurchaseAmount = validatePurchaseAmountInput(
purchaseAmount,
LOTTO_PRICE
)
if (validatedPurchaseAmount === 'PURCHASE_AMOUNT_IS_BLANK_ERROR') {
return setPurchaseAmountIsBlankError(true)
}
if (validatedPurchaseAmount === 'PURCHASE_AMOUNT_IS_NOT_NUMBER_ERROR') {
return setPurchaseAmountIsNotNumberError(true)
}
if (
validatedPurchaseAmount ===
'PURCHASE_AMOUNT_IS_LESS_THAN_MINIMUM_AMOUNT_ERROR'
) {
return setPurchaseAmountIsLessThanMinimumAmountError(true)
}
}
์๋ฌ ๋ณ์๋ฅผ ์๋ฌ๋ด์ฉ์ ๋ฐ๋ผ ์ง์ด์ฃผ๋๊ฒ ์๋๋ผ purchaseAmountError
๋ก๋ง ๋๊ณ ๋ฐ์ํ ์์ธ์ ๋ฐ๋ผ ๊ฐ๋ง ๋ฐ๊ฟ์ฃผ์๋๋ ์ธ๋ฐ ์์ด ๊ธด ๋ณ์๋ช
๋ ์์๊ธฐ๊ณ , ๋ณ์ ํ๋๋ก ํตํฉํด์ ๊ด๋ฆฌํ ์ ์์ด์ ์ข์๋ค.
์์ ํ ์ฝ๋ good!! ๐
// App.tsx
import { validatePurchaseAmountInput } from './modules/formValidator'
const [purchaseAmountError, setPurchaseAmountError] = useState('')
const onSubmitPurchaseAmount = (e: any) => {
e.preventDefault()
setPurchaseAmountError('')
const validatedPurchaseAmount = validatePurchaseAmountInput(
purchaseAmount,
LOTTO_PRICE
)
if (validatedPurchaseAmount === 'PURCHASE_AMOUNT_IS_BLANK_ERROR') {
return setPurchaseAmountError('PURCHASE_AMOUNT_IS_BLANK_ERROR')
}
if (validatedPurchaseAmount === 'PURCHASE_AMOUNT_IS_NOT_NUMBER_ERROR') {
return setPurchaseAmountError('PURCHASE_AMOUNT_IS_NOT_NUMBER_ERROR')
}
if (
validatedPurchaseAmount ===
'PURCHASE_AMOUNT_IS_LESS_THAN_MINIMUM_AMOUNT_ERROR'
) {
return setPurchaseAmountError(
'PURCHASE_AMOUNT_IS_LESS_THAN_MINIMUM_AMOUNT_ERROR'
)
}
}
์ด๋ฒ lotto ์ดํ๋ฆฌ์ผ์ด์
์ ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ๋ถ๋ฆฌํ์ง ์๊ณ App.tsx
์์ ๋ชจ๋ ๋ก์ง์ ์คํํ๋ค. ์ํ๊ด๋ฆฌ๊ฐ ์ฝ๋ค๋ ์ ๋๋ฌธ์ด์๋๋ฐ, ๋์ค์ ํ๋ฒ form ํ๊ทธ๋ค์ ๋ถ๋ฆฌํด๋ณด๋ ๊ฒ๋ ์ข์ ์ฐ์ต์ด ๋ ๊ฒ ๊ฐ๋ค.