useCallback은 useMemo와 상당히 비슷하다. (이전 포스팅 참고) 주로 렌더링 성능을 최적화해야 하는 상황에서 사용하다. useCallback을 통해 이벤트 핸들러 함수를 필요할 때만 생성할 수 있다.
const getAverage =numbers =>{
console.log('평균 값 계산 중 ---');
if(numbers.length===0){
return 0
}
const sum= numbers.reduce((a,b)=> a+b)
return sum/numbers.length
}
const Average = ()=>{
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange =(e)=>{
setNumber(e.target.value)
}
const onInsert = (e)=>{
const newList = list.concat(parseInt(number))
setList(newList)
setNumber('')
}
const avg = useMemo(()=> getAverage(list), [list])
return(
<div>
<input value ={number} onChange ={onChange}/>
<button onClick={onInsert}> 등록 </button>
<ul>
{list.map((value, index) =>
<li key={index}>{value}</li>
)}
</ul>
<div>
<b>평균 값 : </b>{avg}
</div>
</div>
)
}
Average 컴포넌트 내부에는 이벤트 핸들러로 onChange와 onInsert가 선언되어 있다. 이벤트 핸들러는 컴포넌트가 리렌더링 될 때마다 새로 생성되는데, 컴포넌트의 렌더링이 자주 발생하거나 렌더링해야 할 컴포넌트의 개수가 많을 경우 이 부분을 최적화 해주는 것이 좋다.
👉 useCallback을 이용한 최적화
...
const onChange =useCallback((e)=>{
setNumber(e.target.value)
},[])
const onInsert = useCallback((e)=>{
const newList = list.concat(parseInt(number))
setList(newList)
setNumber('')
},[number, list])
...
useCallback의 첫번째 파라미터에는 생성하고 싶은 함수를 넣고, 두번째 파라미터에는 배열을 넣는다. onChang()처럼 컴포넌트가 처음 렌더링될 때만 함수를 생성하려면 [ ]
빈 배열을 넣는다. onInsert()와 같이 어떤 값이 변경될 때만 생성하도록 하려면 [number, list]
처럼 배열에 변경을 체크할 값을 넣어주면된다. number와 list의 값이 바뀌거나 새로운 항목이 추가될 때 마다 함수가 생성된다.
함수 내부에서 상태 값에 의존해야 할 때는 그 값을 반드시 두 번째 파라미터 안에 포함시켜 주어야한다. onChange()의 경우 기존의 값을 조회하지 않고 설정하는 기능만 하기 때문에 빈 배열을 넣어줬지만, onInsert()는 기존의 number와 list를 조회해서 newList를 생성하기 때문에 꼭 두 번째 파라미터 배열안에 넣어줘야한다.
숫자, 문자열, 객체처럼 일반 값을 재사용하려면 useMemo를 사용하고, 함수를 재사용하려면 useCallback을 사용한다.
👉 같은 기능을 담은 코드 비교 useCallback vs useMemo
useCallback(()=>{
console.log('hello pumpkin')
},[])
useMemo(()=>{
const hello=()=>{
console.log('hello pumpkin')
}
return hello
},[])
useRef는 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해 준다. Average 컴포넌트에서 '등록' 버튼을 눌렀을 때 포커스가 input으로 넘어가도록 코드를 작성해보자.
const getAverage =numbers =>{
...
}
const Average = ()=>{
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
//useRef 추가한 부분
const inputElement = useRef(null)
const onChange =useCallback((e)=>{
setNumber(e.target.value)
},[])
const onInsert = useCallback((e)=>{
const newList = list.concat(parseInt(number))
setList(newList)
setNumber('')
//실제 엘리먼트에 focus전달
inputElement.current.focus()
},[number, list])
const avg = useMemo(()=> getAverage(list), [list])
return(
<div>
//input ref 확인
<input value ={number} onChange ={onChange} ref={inputElement}/>
<button onClick={onInsert}> 등록 </button>
<ul>
{list.map((value, index) =>
<li key={index}>{value}</li>
)}
</ul>
<div>
<b>평균 값 : </b>{avg}
</div>
</div>
)
}
useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킨다.
로컬 변수란 렌더링과 상관없이 바뀔 수 있는 값을 의미한다.
✍ 클래스형 컴포넌트
class Component extends Component{
id = 1;
setId =(n)=>{
this.id = n
}
printId=()=>{
console.log(this.id)
}
}
✍ 함수형 컴포넌트 + useRef
const RefSample =()=>{
const id = useRef(1);
const setId =(n)=>{
id.current = n;
}
const printId =()=>{
console.log(id.current)
}
}
💥 주의_ ref 안의 값이 바뀌어도 컴포넌트가 렌더링되지 않기 때문에 렌더링과 관련되지 않은 값을 관리할 때만 사용해주어야 한다.