📌 Literal Type

타입 지정시 string, number 같은 원시타입만 할당할 수 있는 것이 아니다.

개발자가 지정한 글자나 숫자 들을 타입으로 지정할 수 있다.

1
2
let john: "Texas";
let kim: 33;
  • 이제 john에는 ‘Texas’라는 글자만 할당될 수 있고 kim에는 33이라는 숫자만 할당될 수 있다.
계속 읽기

📌 type alias(타입별칭)

1
type Animal = string | number;
  • type 타입변수명 = 타입종류
  • 이런 식으로 타입 별칭으로 변수처럼 담아서 재사용할 수 있다.
  • 관습적으로 대문자 사용

🏓 function type 지정하기

1
2
3
4
5
6
7
8
9
type NumOut = (x: number, y: number) => number;

let ABC: NumOut = function (x, y) {
return x + y;
};
function removeDash(x: string): number {
let result = x.replace(/-/g, "");
return parseFloat(result);
}
계속 읽기

🐒 useEffect

카테고리 React, Hooks

📌 useEffect

1
useEffect(effectCallback);

함수 컴포넌트에서 발생 가능한 side-effect(부수효과)를 관리하기 위해 사용한다.

  • 비동기 통신 요청 및 응답
  • DOM 조작
  • 구독/취소 등

리액트가 할 수 없는 작업을 할 때, useEffect()를 사용한다.

🌈 React Hook 실행 흐름

Hook 실행 흐름

리액트 Hook의 실행흐름은 위 사진과 같다.

함수 컴포넌트에서 클래스 컴포넌트의 생명주기를 구현하기 위해 useEffect()componentDidMount(), componentDidUpdate(), componentWillUnmount() 메서드가 발생되는 주기를 대체할 수 있어야한다.

useEffect()가 이들을 100% 대체할 순 없지만 문제없을 정도로 흉내내어 사용하고 있다.

🐥 이펙트 조건 처리

1. componentDidMount() 대체 방법

1
2
3
useEffect(() => {
// componentDidMount
}, []);
  • useEffect 2번째 인자로 빈 배열을 주게되면 컴포넌트가 처음 mount 되는 때에 한번만 호출된다.

2. componentDidUpdate() 대체 방법

1
2
3
4
5
6
7
useEffect(
() => {
// componentDidMount
// componentDidUpdate
}.
[...dependencies]
);
  • useEffect 2번째 인자로 빈 배열 대신 관리할 상태가 추가되면 해당 상태가 변경될 때에만 이펙트 함수가 실행된다. 즉, mount될 때는 1번 실행되고, update 될 때 마다 실행된다.

3. componentWillUnmount() 대체 방법

이벤트 구독/취소처럼 컴포넌트가 제거될 때 실행되어야 하는 함수의 경우 다음과 같이 사용한다.

1
2
3
4
5
6
7
8
9
function Tester() {
useEffect(() => {
let clearId = setInterval(() => console.count(), 500);
// 정리 함수(cleanup)
return () => {
clearInterval(clearId);
};
}, []);
}
  • cleanUp 함수는 메모리 누수 방지를 위해 UI에서 컴포넌트를 제거하기 직전에 수행된다.
  • Effect 함수가 실행될 때마다 cleanUp 함수가 먼저 실행되어 정리한다.

이는 componentWillUnmount()처럼 동작하는 것 같지만, 리액트 팀은 클래스 컴포넌트 생명주기대로 로직을 구현했을 때, 대규모 프로젝트에서 버그를 많이 발견하였다.

그리하여 리액트 팀에서는 구독취소 후 다시 구독하는 과정을 통해 이를 구현하였다.

1
2
3
4
5
6
7
8
9
10
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // props.friend.id가 바뀔 때만 재구독합니다.

댓글 공유

💭 useState

카테고리 React, Hooks

📌 useState

1
const [stateValue, stateUpdater] = useState(initState);

함수 컴포넌트에서 상태를 관리할 때 사용하는 API이다.

  • stateUpdater는 보통 setStateValue 이런식으로 set을 붙혀서 사용한다.

🐸 지연된 초기화

initState값은 초기 렌더링 시에만 사용되는 값으로, 이후 렌더링 시에는 무시된다. 만약 초깃값을 계산하는데 많은 시간이 필요한 경우 콜백함수를 통해 지연된 초기화 처리가 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const [stateValue, stateUpdater] = useState(() => {
// 계산에 적지 않은 시간이 소요될 경우
// 약 790.62890625 ms
const initialState = fibonacci(39);

// 계산 이후: 지연된 초기화의 처리 값을 반환
return initialState;
});

// 또는 localStorage에서 값을 읽어오는 경우
const [stateValue, setUpdater] = useState(() => {
const persnalization = localStorage.getItem("persnalization");
return JSON.parse(persnalization);
});

⛳️ 객체 타입 상태 관리

updateState 함수는 setState 함수처럼 객체 상태를 관리하기 합성된 객체를 반환해야 한다.

1
2
3
4
5
6
7
8
9
const [state, updateState] = useState({
key1: false,
key2: true,
});

updateState({
...state,
key2: true,
});
  • updateState 함수는 상태 병합이 아닌 대체를 하므로 변경되지 않는 객체 값을 유지하기 위해서 위와 같이 해야한다.

댓글 공유

✈️ 리액트 Hooks

카테고리 React, Hooks

📌 Hooks

리액트 Hook은 클래스로 컴포넌트를 만들 때 발생하는 문제점을 해결하기 위해 등장하였다.

  • 리액트 Hook을 사용하면 함수 컴포넌트 중심으로 개발이 가능하다.
  • 함수 컴포넌트에서도 상태, 로직을 추출하여 다른 컴포넌트에서 재사용 할 수 있다.
계속 읽기

📌 TypeScript와 JavaScript 관계

모든 JavaScript는 TypeScript이지만, 모든 TypeScript가 JavaScript는 아니다.

1
2
3
4
5
6
7
8
9
10
interface State {
name: string;
capital: string;
}
const states: State[] = [
{ name: "yiju", capital: "경기도" },
{ name: "kim", capitol: "서울" },
]; // Error
// Type '{ name: string; capitol: string; }' is not assignable to type 'State'.
// Object literal may only specify known properties, but 'capitol' does not exist in type 'State'. Did you mean to write 'capital'?

위 예시를 설명하면, 앞서 말한 모든 타입스크립트가 자바스크립트다 라고 하는 말은 틀렸다.

계속 읽기

📌 타입스크립트란?

JavaScript는 동적 타입만을 제공하여 예측하기 어려운 타입변환으로 디버깅이 어려워지는 문제점이 있어 이를 해결하고자 TypeScript가 탄생하였다.

TypeScript는 정적 타입 시스템을 사용하여 코드가 실행되기 전에 코드에 대하여 예측해준다.

계속 읽기

📌 미션

  • Link 역할로써 span태그, img태그 확인
  • tabindex=”0”를 포함
  • img태그는 alt 속성으로 접근 가능한 이름 정의

🐒 문제

🤿 컴포넌트 props “as”를 img로 주면, img 태그로 렌더링 해주기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import styled from "styled-components";
import { goToLink } from "../../utils";

export function Link({ as = ComponentName, href, children }) {
const StyledLink = styled(`${as}`)`
... styles
`;
return (
<>
<StyledLink
tabIndex="0"
role="link"
onClick={(e) => goToLink(e, href)}
onKeyDown={(e) => goToLink(e, href)}
>
{children}
</StyledLink>
</>
);
}

Link.defaultProps = {
as: "span",
};
  • Link 컴포넌트 안에다가 styled-component를 생성하고 as props를 전달해주었다.

  • 위와 같이 할 경우, as props에 img 태그가 들어오게 된다면 컴포넌트 명명 규칙에 어긋난다고 나온다.

    이를 해결하기 위해 조건부 렌더링을 해주었다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    import styled from "styled-components";
    import { goToLink } from "../../utils";

    export function Link({ as = ComponentName, href, children }) {
    const StyledLink = styled(`${as}`)`
    ... styles
    `;
    return (
    <>
    {as === "img" ? (
    <img
    tabIndex="0"
    role="link"
    onClick={(e) => goToLink(e, href)}
    onKeyDown={(e) => goToLink(e, href)}
    src="./gitprofile.jpeg"
    alt="W3C Website"
    />
    ) : (
    <StyledLink
    tabIndex="0"
    role="link"
    onClick={(e) => goToLink(e, href)}
    onKeyDown={(e) => goToLink(e, href)}
    >
    {children}
    </StyledLink>
    )}
    </>
    );
    }

    Link.defaultProps = {
    as: "span",
    };

    🐥 img가 업로드 되지 않는 문제

    • img 업로드 문제 해결

    문제의 원인은 npm 명령어로 webpack server만 실행시켰는데, webpack config.js 파일에 static 속성값을 [build]로 주었기 때문에 정적 이미지 파일을 불러오기 위해서는 build 폴더에서 assets을 찾는다.

    그러므로 npm run build 명령어로 build 폴더를 생성한 후 그 안에다가 img를 넣어주고 경로를 설정해주어 해결하였다.

    1
    2
    3
    4
    5
    6
    <Link
    as="img"
    alt="test이미지"
    src="./assets/img/gitprofile.jpeg"
    href="https://www.w3.org/WAI/ARIA/apg/example-index/link/link.html"
    />

    🐥 aria-label 속성 있을 때, 조건부로 css 추가 (삭제)

    이 속성을 삭제한 이유는 span 태그를 사용하여 Link 컴포넌트를 구현하였을 때, role="link"로 해주었기 때문에 aria-label 속성을 굳이 해주지 않아도 되므로 삭제하였다.

🏓 회고

  • styled-component를 사용하면서 어떻게 props를 받아서 App에서 렌더링 시켜야할지 헷갈렸었는데 직접 해보니 이해가 잘되었다.
  • 수업 시간에 배웠던 조건부 렌더링을 실제로 사용하면서 익히니 더욱 이해가 잘되었다. 역시 무언가를 만들어보면서 배우는 것이 힘들지만 더 도움되는 것 같다.

댓글 공유

📌 미션 - 로또

🔍 진행 방식

  • 미션은 기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항 세 가지로 구성되어 있다.
  • 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
  • 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.

🚀 기능 요구 사항

로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.

1
2
3
4
- 로또 번호의 숫자 범위는 1~45까지이다.
- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다. - 1등: 6개 번호 일치 / 2,000,000,000원 - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 - 3등: 5개 번호 일치 / 1,500,000원 - 4등: 4개 번호 일치 / 50,000원 - 5등: 3개 번호 일치 / 5,000원
  • 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
  • 로또 1장의 가격은 1,000원이다.
  • 당첨 번호와 보너스 번호를 입력받는다.
  • 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
  • 사용자가 잘못된 값을 입력할 경우 throw문을 사용해 예외를 발생시키고, “[ERROR]”로 시작하는 에러 메시지를 출력 후 종료한다.

입출력 요구 사항

입력

  • 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다.
1
14000
  • 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다.
1
1,2,3,4,5,6
  • 보너스 번호를 입력 받는다.
1
7

출력

  • 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다.
1
2
3
4
5
6
7
8
9
8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[1, 3, 5, 14, 22, 45]
  • 당첨 내역을 출력한다.
1
2
3
4
5
3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
  • 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%)
1
총 수익률은 62.5%입니다.
  • 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 “[ERROR]”로 시작해야 한다.
1
[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.

실행 결과 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
구입금액을 입력해 주세요.
8000

8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[1, 3, 5, 14, 22, 45]

당첨 번호를 입력해 주세요.
1,2,3,4,5,6

보너스 번호를 입력해 주세요.
7

당첨 통계

3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다.

🎯 프로그래밍 요구 사항

  • Node.js 14 버전에서 실행 가능해야 한다. Node.js 14에서 정상적으로 동작하지 않을 경우 0점 처리한다.
  • 프로그램 실행의 시작점은 App.js의 play 메서드이다. 아래와 같이 프로그램을 실행시킬 수 있어야 한다.

예시

1
2
const app = new App();
app.play();
  • package.json을 변경할 수 없고 외부 라이브러리(jQuery, Lodash 등)를 사용하지 않는다. 순수 Vanilla JS로만 구현한다.
  • JavaScript 코드 컨벤션을 지키면서 프로그래밍 한다
  • 프로그램 종료 시 process.exit()를 호출하지 않는다.
  • 프로그램 구현이 완료되면 ApplicationTest의 모든 테스트가 성공해야 한다. 테스트가 실패할 경우 0점 처리한다.
  • 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다.
  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • Jest를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.

추가된 요구 사항

  • 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
    • 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
  • else를 지양한다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
    • 때로는 if/else, switch문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다.
  • 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(Console.readLine, Console.print) 로직에 대한 단위 테스트는 제외한다.
    • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
    • 단위 테스트 작성이 익숙하지 않다면 tests/LottoTest.js를 참고하여 학습한 후 테스트를 구현한다.

라이브러리

  • MissionUtils 라이브러리에서 제공하는 Random 및 Console API를 사용하여 구현해야 한다.
    • Random 값 추출은 MissionUtils 라이브러리의 Random.pickUniqueNumbersInRange()를 활용한다.
    • 사용자의 값을 입력 받고 출력하기 위해서는 MissionUtils 라이브러리에서 제공하는 Console.readLine, Console.print를 활용한다.

사용 예시

1
const numbers = MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6);

Lotto 클래스

  • 제공된 Lotto 클래스를 활용해 구현해야 한다.
  • numbers의 # prefix를 변경할 수 없다.
  • Lotto에 필드를 추가할 수 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Lotto {
#numbers;

constructor(numbers) {
this.validate(numbers);
this.#numbers = numbers;
}

validate(numbers) {
if (numbers.length !== 6) {
throw new Error();
}
}

// TODO: 추가 기능 구현
}

회고

🐥 당첨 번호 및 로또 번호 예외 처리

  • 당첨번호 에러 처리할 때 중복검사 뿐만 아니라 범위도 확인해줘야 한다. 게다가 보너스 번호는 당첨번호와 중복 검사도 추가로 해줘야 한다.

🦉 typeof NaN === ‘number’

  • “4r”이라는 문자열을 숫자로 형변환 해주면 NaN 값이 나오게된다. 에러처리를 할 때 type이 ‘number’인지를 확인해주도록 하였는데, NaN은 number 타입을 가지고 있으므로 이에 대한 처리를 추가해줘야했다.

🦆 하드코딩 vs 소프트코딩

  • score 객체에 3등 4등 5등 의 키 값은 숫자로만 주었는데, bonus 등수는 문자열로 주어서 출력시 5등 4등 3등 2등 1등 Bonus등수가 출력되어서 이를 해결하기 위해 score의 key 값을 문자열로 변경하였다.

  • lotto 배열의 요소도 [1, 2, 3, 4, 5, 6] 이런 형태의 배열인데, print 결과물이 “[1, 2, 3, 4, 5, 6]” 이렇게 나와야 하므로 이에 대한 처리를 해주었다.

  1. 백틱만 사용
1
this.Lotto.forEach((lotto) => MissionUtils.Console.print(`${lotto}`));

Error1

  1. 문자열에 [ ] 삽입
1
this.Lotto.forEach((lotto) => MissionUtils.Console.print(`[${lotto}]`));

Error2

  1. 문자열 배열처럼 처리
1
2
3
this.Lotto.forEach((lotto) =>
MissionUtils.Console.print(`[${lotto.join(", ")}]`)
);

Error3

🐒 숫자를 천 단위 콤마찍은 문자열로 변환하기

  • Number.prototype.toLocaleString() 메서드를 사용하면 천단위에 ,를 찍은 문자열로 변환한다.

✏️ 피드백 (22.11.17 추가)

비즈니스 로직과 UI로직 구분

  • 한 클래스에서 담당하지 않고 구분하여야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Lotto {
#numbers

// 로또 숫자가 포함되어 있는지 확인하는 비즈니스 로직
contains(numbers) {
...
}

// UI 로직
print() {
...
}
}

객체의 상태 접근 제한

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 내 코드
class App {
constructor() {
this.LottoCount = null;
this.Lotto = [];
this.winNum = [];
this.bonusNum = null;
this.score = { "3개": 0, "4개": 0, "5개": 0, bonus개: 0, "6개": 0 };
this.prize = {
"3개": 5000,
"4개": 50000,
"5개": 1500000,
bonus개: 30000000,
"6개": 2000000000,
};
this.totalMoney = null;
}
}
  • 위 와 같은 경우 객체의 데이터를 외부에서 접근 가능하므로 이는 디버깅과 유지보수를 어렵게하므로 접근 제한자를 설정하고 선택된 메서드만으로 데이터를 참조하거나 변경할 수 있도록 개선한다.

객체는 객체답게 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Lotto {
#numbers

constructor(numbers) {
this.#numbers = numbers
}

getNumbers() {
return this.#numbers
}
}

class LottoGame {
play() {
const lotto = new Lotto(...)

// 숫자가 포함되어 있는지 확인한다.
lotto.getNumbers().contains(number)
// 당첨 번호와 몇 개가 일치하는지 확인한다.
lotto.getNumbers().stream()...
}
}
  • 로또 클래스는 로또 숫자를 가져오는 역할만 한다.
  • 로또게임 클래스는 play() 메서드를 호출 하였을 경우 이와 관련된 로직으로 구성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Lotto {
#numbers

constructor(numbers) {
this.#numbers = numbers
}

contains(number) {
// 숫자가 포함되어 있는지 확인한다.
return ...
}

matchCount(other) {
// 당첨 번호와 몇 개가 일치하는지 확인한다.
return ...
}
}

class LottoGame {
play() {
const lotto = new Lotto(...)

lotto.contains(number)
lotto.matchCount(...)
}
}
  • getter를 사용하여 값을 꺼내 사용하는 대신 다른 객체에 메시지를 건네주자. 객체가 스스로 일하도록 하는 객체 지향 프로그래밍 기법을 사용하자.
  • 출력을 위한 값, 순수 값 프로퍼티를 가져오기 위해서라면 getter를 허용한다.

필드 수를 줄이도록 하자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 리팩터링 전
class LottoResult {
#result = new Map()
#profitRate
#totalPrize
}

// 리팩터링 후
class LottoResult {
#result = new Map()

calculateProfitRate() { ... }

calculateTotalPrize() { ... }
}
  • 위 객체의 profitRate와 totalPrize는 등수 별 당첨 내역(result)만 있어도 모두 구할 수 있는 값이다. 따라서 위 객체는 다음과 같이 하나의 필드만으로 구현할 수 있다.

🏓 소감

2주차때 클래스구조와 테스트에 대해서 조금은 익숙해져서 3주차 미션을 받았을 때 구조가 비슷하여 조금 마음이 놓였다.

  • jest expect 함수와 matcher 함수를 찾아보면서 단위 테스트 기능에 대해 공부해볼 수 있는 기회여서 좋았다.
  • 로또 클래스와 비즈니스 로직 클래스를 구분하여 테스트를 진행하고 관심사를 분리하면서 개발을 하니 유지보수가 편리하다는 것을 느낄 수 있었다.

댓글 공유

🎧 PropTypes

카테고리 React

🌈 PropTypes

리액트 컴포넌트에 전달되는 Prop(속성)의 Type(타입)을 검사하는 방법에 대해 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EmotionCard.propTypes = {
// 전달 속성 객체, 속성 이름, 컴포넌트 이름
emotion(props, propName, componentName) {
// 체크할 유형
const checkType = "string";
// 전달 속성 유형
const propType = typeof props[propName];

// 전달 속성 검사 (문자 값인지 확인)
if (propType !== checkType) {
// 문자 값이 아닌 경우 오류 발생
throw new Error(
`${componentName} 컴포넌트에 전달 된 속성 ${propName}의 데이터 유형은 ${checkType}이 요구되나, 실제 전달된 속성 유형은 ${propType}이니 확인 바랍니다.`
);
}
},
};

위와 같이 직접 custom propTypes를 통해 prop를 검사할 수 있지만 리액트 팀에서 제공하는 propTypes 패키지를 사용하면 편리하고 안정적으로 prop 검사를 할 수 있다.

패키지 사용

설치

1
npm i -D prop-types

사용 예시

1
2
3
4
5
6
7
8
9
10
import PropTypes from "prop-types";

// ...

Worker.propTypes = {
name: PropTypes.string.isRequired,
career: PropTypes.number,
onCareerUp: PropTypes.func,
isLeave: PropTypes.bool,
};
  • isRequired 는 필수입력을 요구하는 속성을 표시한다.

propTypes 검사 항목

propType_categories

PropTypes﹕objectOf vs. shape

1
2
3
4
5
6
7
8
9
10
import { objectOf, number } from "prop-types";

const geoProps = {
latitude: 37.331706,
longitude: -122.030783,
};

ReactComponent.propTypes = {
geoProps: objectOf(number),
};
  • propTypes.objectOf객체의 속성 값이 모두 동일한 타입을 설명할 경우 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
import { shape, arrayOf, string } from "prop-types";

const personProp = {
name: "야무",
job: ["강사", "디자이너", "개발자"],
};

ReactComponent.propTypes = {
personProp: shape({
name: string,
job: arrayOf(string),
}),
};
  • 내가 객체 prop 타입 설정하고 싶다하면 shape를 사용한다.
  • propTypes.shape 는 객체의 각 속성별 타입을 설명할 때 사용한다.
  • 객체의 name 속성은 string, job 속성은 배열을 사용하였다.
  • job의 배열에는 string으로만 구성된 배열이므로 arrayOf를 사용하였다.

객체의 속성이 정확히 동일하게 일치해야한다면 propTypes.exact()를 사용한다.

❗️ Null 타입 체크 주의

propTypes는 null 타입 체크를 할 수 없어 oneOf를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
import { oneOf, oneOfType, shape, string } from "prop-types";

SignInedInfo.propTypes = {
authUser: oneOfType([
oneOf([null]), // ← 이렇게 작성합니다.
shape({
displayName: string.isRequired,
photoURL: string.isRequired,
}),
]).isRequired,
};
  • PropTypes.oneOf([’grow’, ‘learn’, ‘connect’]) 이 중 하나만 파라미터로 들어올 수 있다.

🔥 defaultProps와 같이 사용하기

함수에 기본값을 주듯이 props에 기본값을 주는 것을 리액트에서는 지양한다. 그러므로 다음과 같이 기본값을 줘야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react';
import { number, bool}

const Worker = ({ name, career, onCareerUp, isLeave }) => (
// ...
);

// Worker 컴포넌트 전달 속성 기본 값 설정
Worker.defaultProps = {
career: 0,
isLeave: true,
}

Worker.propTypes = {
career:number,
isLeave: bool
}

export { Worker };
  • boolean 대신 bool이라고 사용합니다.

댓글 공유

loco9939

author.bio


author.job