componentDidUpdate
는 항상 실행되기 때문에 꼭 조건문으로 감싸주는 것이 좋다.useEffect
의 input
인자가 빈 배열이면 componentDidMount
와 동일하다. 배열에 요소가 있으면 componentDidMount
와 componentDidUpdate
를 둘 다 수행한다.const [winNumbers, setWinNumbers] = useState(getWinNumbers());
훅스에서 위와 같이 state에 함수가 들어갈 경우, setWinNumbers
를 수행할 때마다 getWinNumbers
함수도 매번 수행된다. getWinNumbers
가 수행 시간이 오래 걸리는 함수일 경우, 성능 최적화에 문제가 발생한다. 이 때 사용하는 훅스 메소드가 useMemo
이다.
const lottoNumbers = useMemo(() => getWinNumbers(), []);
const [winNumbers, setWinNumbers] = useState(lottoNumbers);
useMemo
를 사용하면 이전의 데이터가 임시 저장소에 캐싱된다. 두번째 인자의 배열 요소가 갱신될 때마다 useMemo
가 수행되므로 불필요한 렌더링을 줄일 수 있다.
useMemo
와 useRef
의 차이점useMemo
: 복잡한 함수 결과 값을 기억한다.useRef
: 일반 값을 기억한다.useMemo
와 useCallback
의 차이점useMemo
: 함수의 리턴 값을 기억한다. useCallback
: 함수 자체를 기억한다.그렇다면 모든 함수를 useCallback
으로 감싸주는 게 이득인가? 웬만한 함수는 useCallback
으로 감싸주어도 별 문제없이 실행된다. 다만 useCallback
은 가장 처음에 실행한 함수를 기억하기 때문에 반복 수행해도 값이 바뀌지 않는다. 두번째 인자의 배열 요소가 바뀔 때마다 useCallback
이 수행되고, 그 바뀐 값도 함께 저장되어야 하므로 input
인자에 바뀌는 요소를 넣어주어야 한다.
const onClickRedo = useCallback(() => {
console.log(winNumbers);
setWinNumbers(getWinNumbers());
setWinBalls([]);
setBonus(null);
setRedo(false);
timeouts.current = [];
}, [winNumbers]); //배열에 winNumbers를 넣지 않으면 값이 갱신되지 않음
useCallback
을 필수로 적용해야하는 경우도 있다. 자식 컴포넌트에 props로 함수를 넘길 때에는 꼭 useCallback
으로 그 함수를 감싸주어야 한다. 감싸주지 않을 경우 매번 새로운 함수가 수행되기 때문에 자식 컴포넌트에도 매번 새로운 함수가 전달된다. 이런 경우, 자식 컴포넌트는 매번 새로운 함수가 전달되는 것으로 인식하기 때문에 렌더링 또한 매번 일어나는 불상사가 생긴다.
훅스에서 state를 생성할 때에는 순서가 매우 중요하다. 조건문이나 함수, 반복문에도 넣지 않고 무조건 최상위에서 선언해야 한다.
간혹 componentDidMount
는 수행하지 않고 componentDidUpdate
만 수행해야 하는 때가 있는데, 보통 다음과 같은 코드 패턴을 사용한다.
const mounted = useRef(false);
useEffect(() => {
if(!mounted.current) {
mounted.current = true;
} else {
//실행 코드(componentDidUpdate)
}
}, [/* 바뀌는 값 */]);
유니온 타입은 하나의 변수에 여러가지 타입을 지정할 수 있다. 단순히 Any
로 타입을 지정하면 타입스크립트의 장점을 극대화할 수 없기에 유니온 타입을 사용하면 수월하다.
var seho: string | number | boolean // | 연산자를 이용해 계속해서 추가 가능
function logMessage(value: string | number) {
console.log(value);
}
logMessage('hello');
logMessage(100);
Type Guard
유니온 타입을 사용하면 한 함수 내에서도 타입 별로 코드 관리가 가능하다.
function logMessage(value: string | number) {
if (typeof value === 'number') {
value.toLocaleString();
}
if (typeof value === 'string') {
value.toString();
}
throw new TypeError('value must be string or number');
}
타입을 interface로 사용하는 경우에는 두 interface의 공통된 속성만 바로 사용이 가능한데, 즉 인자로 어떤 값이 들어올지 확신할 수 없기 때문에 보장된 속성만 할당을 해줄 수 있다.
interface Developer {
name: string;
skill: string;
}
interface Person {
name: stirng;
age: number;
}
function askSumeone(someone: Developer | Person) {
someone.name; // 접근 가능
someone.skill; // 에러
someone.age; // 에러
}
공통되지 않은 속성에 접근하고 싶은 경우에는 타입 가드를 사용해서 먼저 인자의 타입이 보장된 후에 접근이 가능하다.
인터섹션 타입은 & 연산자를 이용하고, 사용된 타입의 모든 속성을 만족해야 한다.
interface Developer {
name: string;
skill: string;
}
interface Person {
name: stirng;
age: number;
}
function askSomeone(someone: Developer & Person) {
someone.name; // 접근 가능
someone.skill; // 접근 가능
someone.age; // 접근 가능
}
유니온 타입과 달리 someone
이 name
, skill
, age
를 모두 가지고 있어야하기 때문에 skill
과 age
에도 접근이 가능하다.
실무에서는 보통 유니온 타입을 더 많이 사용한다. 함수에서 사용한 유니온 타입은 함수 내부에서 타입 가드를 통한 타입 추론을 한 번 더 거쳐야하는 대신에 함수를 사용할 때 넣을 수 있는 인자 값의 폭이 넓어진다. 반면에 인터섹션 타입은 함수 내부에서 각 타입의 속성에 쉽게 접근할 수 있는 대신 함수를 사용할 때 전달되는 인자 값이 모든 타입의 속성을 포함하고 있어야 한다.
/* 유니온 타입 */
function askSomeone(someone: Developer | Person) {
…
}
askSomeone({ name: '디벨로퍼', skill: '웹 개발' }); // 가능
askSomeone({ name: '캡틴', age: 100 }); // 가능
/* 인터섹션 타입 */
function askSomeone(someone: Developer & Person) {
…
}
askSomeone({ name: '디벨로퍼', skill: '웹 개발', age: 100 }); // 가능
askSomeone({ name: '캡틴', age: 100 }); // 에러
이넘은 특정 값들의 집합을 의미하는 자료형이다. 타입스크립트는 숫자형 이넘과 문자형 이넘을 지원한다.
숫자형 이넘
이넘은 별도의 값을 지정하지 않으면 숫자형 이넘으로 취급한다. 이넘에 값들을 계속 추가하면 순서에 맞게 1씩 숫자가 증가한다.
enum Shoes {
Nike,
Adidas,
}
var myShoes = Shoes.Nike;
console.log(myShoes) // 0
문자형 이넘
이넘의 각 값에 별도의 값을 지정해주면 해당 이넘에 접근할 때 지정된 값을 불러온다.
enum Shoes {
Nike = '나이키',
Adidas = '아디다스'
}
var myShoes = Shoes.Nike;
console.log(myShoes); // '나이키'
/* 자바스크립트 */
class Person {
// 클래스 로직
constructor(name, age) {
console.log('생성되었습니다.');
this.name = name;
this.age = age;
}
}
var seho = new Person('세호', 30); //생성되었습니다.
console.log(seho);
/* 타입스크립트 */
class Person {
private name: string;
public age: number;
readonly log: string;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// 리액트 예전 문법 - 클래스 기반 코드
class App extends React.Component {
…
}
// 리액트 최신 문법 - 훅 기반의 함수형 코드
function App() {
return <div>Hello world</div>
}