import { useState } from 'react';
import Counter from './components/Counter/Counter.jsx';
import Header from './components/Header.jsx';
import { log } from './log.js';
function App() {
log('<App /> rendered');
const [enteredNumber, setEnteredNumber] = useState(0);
const [chosenCount, setChosenCount] = useState(0);
function handleChange(event) {
setEnteredNumber(+event.target.value);
}
function handleSetClick() {
setChosenCount(enteredNumber);
setEnteredNumber(0);
}
return (
<>
<Header />
<main>
<section id="configure-counter">
<h2>Set Counter</h2>
<input type="number" onChange={handleChange} value={enteredNumber} />
<button onClick={handleSetClick}>Set</button>
</section>
<Counter initialCount={chosenCount} />
</main>
</>
);
}
export default App;
여기서 input에 문자 하나를 입력할 때마다 여러 컴포넌트 함수들이 다시 실행된다. 문자를 입력할 때마다 상태가 업데이트되기 때문에 컴포넌트 함수가 재실행된다.
이 문제를 고치기 위해서는 memo()를 사용할 수 있다.
컴포넌트 함수의 속성을 살펴보고 컴포넌트 함수가 정상적으로 다시 실행될 때, 예를 들어 앱 컴포넌트 함수가 실행되면 memo가 이전 속성 값과 새로 받을 속성 값을 살펴본다. 만약 컴포넌트 함수가 실행됐는데 속성 값들이 완전히 동일하다면 이 컴포넌트 함수 실행을 memo가 저지한다.
사용법은 아래와 같다.
import { useState, memo } from 'react';
import IconButton from '../UI/IconButton.jsx';
import MinusIcon from '../UI/Icons/MinusIcon.jsx';
import PlusIcon from '../UI/Icons/PlusIcon.jsx';
import CounterOutput from './CounterOutput.jsx';
import { log } from '../../log.js';
function isPrime(number) {
log(
'Calculating if is prime number',
2,
'other'
);
if (number <= 1) {
return false;
}
const limit = Math.sqrt(number);
for (let i = 2; i <= limit; i++) {
if (number % i === 0) {
return false;
}
}
return true;
}
// memo import 해서 컴포넌트 감싸주기
const Counter = memo(function Counter({ initialCount }) {
log('<Counter /> rendered', 1);
const initialCountIsPrime = isPrime(initialCount);
const [counter, setCounter] = useState(initialCount);
function handleDecrement() {
setCounter((prevCounter) => prevCounter - 1);
}
function handleIncrement() {
setCounter((prevCounter) => prevCounter + 1);
}
return (
<section className="counter">
<p className="counter-info">
The initial counter value was <strong>{initialCount}</strong>. It{' '}
<strong>is {initialCountIsPrime ? 'a' : 'not a'}</strong> prime number.
</p>
<p>
<IconButton icon={MinusIcon} onClick={handleDecrement}>
Decrement
</IconButton>
<CounterOutput value={counter} />
<IconButton icon={PlusIcon} onClick={handleIncrement}>
Increment
</IconButton>
</p>
</section>
);
})
export default Counter;
여기서 initialCount가 바뀌거나 내부적 상태가 바뀌면 memo는 이를 저지하지 않으며, 컴포넌트 함수가 작동한다.
만약 memo로 모든 컴포넌트를 감싼다면 리액트는 컴포넌트 함수를 실행하기 전 항상 속성들을 확인해야 한다. 그리고 속성 값을 확인하는 건 그만큼 성능에 부담을 주게 된다. 그래서 최대한 상위 컴포넌트 트리에 있는 컴포넌트 또는 재렌더링을 방지할 수 있는 컴포넌트에 사용하는 것이 좋다.
useCallback 훅은 함수의 재생성을 방지하기 위해 사용된다.
import { useState, memo, useCallback } from 'react';
import IconButton from '../UI/IconButton.jsx';
import MinusIcon from '../UI/Icons/MinusIcon.jsx';
import PlusIcon from '../UI/Icons/PlusIcon.jsx';
import CounterOutput from './CounterOutput.jsx';
import { log } from '../../log.js';
function isPrime(number) {
log(
'Calculating if is prime number',
2,
'other'
);
if (number <= 1) {
return false;
}
const limit = Math.sqrt(number);
for (let i = 2; i <= limit; i++) {
if (number % i === 0) {
return false;
}
}
return true;
}
const Counter = memo(function Counter({ initialCount }) {
log('<Counter /> rendered', 1);
const initialCountIsPrime = isPrime(initialCount);
const [counter, setCounter] = useState(initialCount);
const handleDecrement = useCallback(function handleDecrement() {
setCounter((prevCounter) => prevCounter - 1);
}, []);
const handleIncrement = useCallback(function handleIncrement() {
setCounter((prevCounter) => prevCounter + 1);
}, []);
return (
<section className="counter">
<p className="counter-info">
The initial counter value was <strong>{initialCount}</strong>. It{' '}
<strong>is {initialCountIsPrime ? 'a' : 'not a'}</strong> prime number.
</p>
<p>
<IconButton icon={MinusIcon} onClick={handleDecrement}>
Decrement
</IconButton>
<CounterOutput value={counter} />
<IconButton icon={PlusIcon} onClick={handleIncrement}>
Increment
</IconButton>
</p>
</section>
);
})
export default Counter;
이렇게 하면 'Increment'버튼 또는 'Decrement' 버튼을 클리갷도 IconButton과 중첩 icon 컴포넌트들이 재실행되지 않으며 이제 counter output만 출력된다.
memo와 차이점: memo는 컴포넌트 함수를 감싸는데 사용했지만 useMemo는 컴포넌트 함수 안에 있는 일반 함수들을 감싸고 그들의 실행을 방지한다.
// 기존 코드
const initialCountIsPrime = isPrime(initialCount);
// useMemo 사용
const initialCountIsPrime = useMemo(() => isPrime(initialCount), [initialCount]);
isPrime의 불필요한 실행을 막기 위해 useMemo로 감싼 모습이다. useMemo는 첫 번째 인자로 콜백함수, 두 번째 인자로 의존성 배열을 받는다. 콜백 함수는 의존성 배열 안에 있는 값들이 바뀔 경우에만 재실행된다. 만약 빈 의존성 배열이 있다면 이것은 절대 재실행되지 않는다(바뀔 수 있는 의존성이 없기 때문).