이전에 TypeScript로 프로젝트를 진행한 경험이 있다. 이때, 완벽하게 이해하지 못하고 넘어갔던 코드나 개념을 다시 짚어보려고 한다.
import { RouteComponentProps } from "react-router-dom";
const LogInView: React.FunctionComponent<RouteComponentProps> = (props) => {
const goSignUpView = () => {
props.history.push(`/signup`);
};
}
React에서 컴포넌트 만드는 방법
클래스 기반 - 확장(extends)해서 사용
- React.Componenet와 React.PureComponent는 shouldComponentUpdate 라이프 사이클 메소드를 다루는 방식을 제외하고는 같다.
React.Componenet
- React.Component를 확장(extends) 해서 컴포넌트를 만들면, shouldComponentUpdate 메소드를 선언하지 않으면 컴포넌트는 props, state 값이 변경될 때마다 항상 리렌더링 된다.
React.PureComponent
- shouldComponentUpdate 라이프 사이클 메소드가 이미 적용된 React.Component 클래스라고 보면 된다.
- React.PureComponent를 확장(extends) 해서 컴포넌트를 만들면, shouldComponentUpdate 메소드를 선언하지 않아도 PureComponent 내부에서 props와 state를 shallow level 안에서 비교하고, 변경된 값이 있을 때에만 리렌더링 된다.
함수 기반
Functional Component
- 함수 컴포넌트는 클래스 기반의 컴포넌트와 달리 state, 라이프 사이클 메소드(componentDidMount, shouleComponentUpdate 등)와 ref 콜백을 사용할 수 없다.
(context는 사용 가능 / hooks API가 추가된 이후에는 함수 컴포넌트에서도 state 관리 가능)
- 함수 컴포넌트는 state가 없고, 라이프 사이클도 신경 쓰지 않기 때문에 클래스 기반의 컴포넌트 보다 퍼포먼스가 좋다고 예상할 수 있지만 실제로 함수 컴포넌트도 클래스 기반 컴포넌트로 래핑(wrapping) 되기 때문에, 큰 차이는 없다.
- 오히려 React.PureComponent에 shoulComponentUpdate를 통해 리렌더링을 최소화하기 때문에 React.Component와 함수 컴포넌트보다 더 빠를 수 있다.
🤔 항상 React.PureComponent를 사용하는 것이 좋을까?
- 아니다. PureComponent는 shallow level로만 데이터를 비교하기 때문에, nested object 등의 변경된 데이터를 감지 하지 못한다.
- 그렇기 때문에 React.Component의 shouldComponentUpdate를 직접 다뤄야 한다.
- 또한, 모든 컴포넌트에 PureComponent를 사용하는 것이 오히려 컴포넌트를 더 느리게 할 수도 있다.
📌 클래스 컴포넌트(state, lifecyle, ref)가 필요한 상황이 아니면 항상 컴포넌트는 함수로 만드는 것이 좋다.
Shallow compare란?
- shallow compare는 equility를 체크하는 것을 말한다.
- 숫자나 문자열 같은 값들을 비교한다.
- object를 비교할 때는 객체의 attribute(속성)을 비교하지 않고, 객체의 reference(참조)를 비교한다. 그렇기 때문에 같은 값이 들어있는 object라도 항상 다른 값으로 체크한다.
TypeScript에서 React Router 사용
- 타입스크립트에서 리액트 라우터를 사용하려면 react-router-dom뿐만 아니라 타입 정보가 있는 @types/react-router-dom 패키지도 필요하다.
- 위의 예시 코드에서
<RouteComponentProps>
처럼 RouteComponentProps를 제네릭으로 사용한다.
이 때, Route를 통해 component에는 3개의 객체가 넘어온다.
- history
- 브라우저의 window.history와 비슷하다.
- 주소를 임의로 변경하거나 되돌아갈 수 있게 한다.
- 주소를 변경하더라도 SPA의 특성에 맞게 페이지 전체를 리로드 하지 않고 페이지 일부만 리로드 한다.
- location
- 브라우저의 window.location과 비슷하다.
- 현재 페이지에 대한 정보를 가지고 있고, URL을 쪼개서 가지고 있다.
- URL의 query 정보도 가지고 있다.
- match
- Route의 path에 정의한 것과 매치된 정보를 가지고 있다.
제네릭
- 여러 타입에 대해 동작하는 함수를 정의하되, 해당 함수를 정의할 때는 알 수 없고 사용할 때에만 알 수 있는 타입 정보를 사용하려 할 때 제네릭을 사용한다.
타입 변수
- 요소를 사용하는 시점에서만 알 수 있는 타입을 담아두기 위해서 타입 변수가 필요하다.
- 타입 변수를 부등호(<>)로 변수명을 감싸 정의하고, 정의한 타입 변수를 요소의 타입 정의에 사용할 수 있다.
제네릭 함수
function getFirstElem<T>(arr: T[]): T {
}
function 함수명<타입 변수>(인자 타입): 반환 타입 {
}
const languages: string[] = ['TypeScript', 'JavaScript'];
const language = getFirstElem<string>(languages);
-
위의 타입 정의는 임의의 타입 T에 대해, getFirstElem은 T 타입 값의 배열 arr를 인자로 받아 T 타입 값을 반환하는 함수를 말한다.
-
제네릭 함수를 호출할 때는 정의에서 타입 변수가 있던 자리에 타입 인자를 넣어준다.
유니온 타입
function square(value: number, returnString: boolean = false): string | number {
const squared = value * value;
if (returnString) {
return squared.toString();
}
return squared;
- 위의 코드를 보면 함수 square은 숫자 타입과 불리언 타입의 인자를 받아 그 값에 따라 숫자 또는 문자열 타입의 값을 반환한다. 이 경우 반환 타입이 인자의 타입이 아닌 값에 의존한다.
- 이 때 string 또는 number 타입 두 가지 경우의 수를 나타낸다.
인터섹션 타입
인터섹션 타입을 이용해 여러 경우에 모두 해당 하는 타입을 표현할 수 있다.
type Infeasible = string & number;
A & B 타입의 값은 A 타입과 B 타입에 할당 가능해야 한다. A와 B 모두 객체 타입이면 A & B 타입의 객체는 A와 B 타입 각각에 정의된 속성 모두 가져야 한다.