6. useCallback
useMemo와 같이 메모이제이션 기법 중 하나이며 컴포넌트의 성능을 최적화하기 위한 도구 중 하나입니다.
useCallback은 함수의 반환 값을 메모이제이션 하는 것(useMemo)이 아니라 함수 자체를 캐싱해주는 Hook입니다.
함수형 컴포넌트는 함수이며 이러한 함수형 컴포넌트를 렌더링 한다는 것은 컴포넌트 함수를 호출하는 것입니다. 그렇기 때문에 내부 변수들은 렌더링이 될때마다 초기화됩니다. 이 과정에서 useCallback으로 원하는 함수를 묶어서 렌더링이 다시 되어도 초기화 되는 것을 막을 수 있습니다. 여기서 주의할 점은 useCallback으로 함수를 메모이제이션 할려면 해당 함수를 작성할 때 함수 표현식으로 작성해야 한다는 것 입니다. 만약에 함수 선언식으로 함수를 작성한다면 호이스팅되기 때문에 메모이제이션 할 수 없습니다.
useCallback의 구조
useCallback(() => {
// 메모이제이션 해 줄 콜백함수
},[의존성 배열])
// 사용 예
const functionName = useCallback((num) => {
return num + ;1
}, [state])
useMemo와 동일하게 의존성 배열 내부에 있는 값이 변경되지 않는 이상 다시 초기화 되지 않습니다.
예제를 사용해보며 이해해 봅시다.
import {useState} from 'react';
export default function App () {
const [val, setVal] = useState(100)
const [isDark, setIsDark] = useState(false)
const boxstyle = () => {
return (
backgroundColor: 'pink',
width: `${val}px`,
height: `${val}px`,
)
}
return (
<div style={{
background : isDark ? "black" : "white"
}}>
<input
type = 'number'
value = {val}
onChange = { (e) => {setVal(e.target.value)} }
/>
<button onClick={setIsDark(!isDark)}>
{isDark ? 다크 모드 : 화이트 모드}
</button>
<Box createBoxStyle={boxstyle} />
</div>
)
}
import {useEffect, useState} = form 'react'
export default const Box({ createBoxStyle }) {
const [style, setStyle] = useState({});
useEffect(()=>{
console.log("박스 크기");
setStyle(createBoxStyle());
},[createBoxStyle]);
return <div style={style} />
}
위 코드 같은 경우 state가 바뀌면서 재 렌더링 되는 경우 boxstyle 함수가 초기화 되기 때문에 isDark가 변경되어도 "박스 크기"가 콘솔에 뜨게 됩니다. 박스 크기와 직접적인 연관이 없는 isDark가 변경딜 때는 Box 컴포넌트에 있는 useEffect를 실행시키지 않는 것이 효율적이기 때문에 useCallback을 이용해 boxstyle 함수를 묶어주는 것이 좋습니다.
묶어준 코드는 다음과 같습니다.
import {useState, useCallback} from 'react';
export default function App () {
const [val, setVal] = useState(100)
const [isDark, setIsDark] = useState(false)
const boxstyle = useCallback(() => {
return (
backgroundColor: 'pink',
width: `${val}px`,
height: `${val}px`,
)
},[val])
return (
<div style={{
background : isDark ? "black" : "white"
}}>
<input
type = 'number'
value = {val}
onChange = { (e) => {setVal(e.target.value)} }
/>
<button onClick={setIsDark(!isDark)}>
{isDark ? 다크 모드 : 화이트 모드}
</button>
<Box createBoxStyle={boxstyle} />
</div>
)
}
이렇게 작성하면 박스 크기를 지정하는 val state가 변경될때만 boxstyle 함수가 초기화 되기 때문에 불필요하게 Box 컴포넌트의 useEffect를 호출하는 상황을 해결할 수 있습니다.
사실 위의 useCallbak 내부의 콜백 함수는 값만 반환해 주기 때문에 useMemo를 사용해도 문제 없습니다.
7. useReducer
리액트에서 state 관리를 위한 또 다른 Hook입니다. 보통 여러개의 하위값을 가지고 있는 복잡한 state를 다루는 경우 useReducer를 사용하면 코드를 매우 깔끔하게 짤 수 있습니다.
이러한 useReducer는 Reducer, Dispatch, Action으로 이루어져 있습니다.
간단하게 설명하자면 state를 변경해 주는 작업을 하는 것이 Reducer이고, 값의 변경을 요구하는 행위가 Dispatch, 요구하는 내용이 Action입니다.
Dispatch( Action ) ====> Reducer( State, Action ) =====> state에 업데이트
예제를 보면서 이해해 봅시다.
(예제 1 : 은행)
import {useState, useReducer} from 'react';
//reducer : state의 값을 변경해줌
//dispatch : state를 업데이트 하기 위한 요구
//actioin : 요구의 내용, 보통 객체의 형태로 보냅니다.
const reducer = (state, action) = > {
switch (action.type) {
case 'deposit':
return state + action.payload;
case 'withdraw':
return state + action.payload;
default:
return state;
}
};
const ACTION_TYPE = {
deposit : 'deposit',
withdraw : 'withdraw',
}
export default fucntion App () {
const [val, setVal] = useState(0)
const [money, dispatch] = useReducer(reducer, 0) //(리듀서, 초기값)
return (
<div>
<h2>은행</h2>
<p>잔고 : {money}원</p>
<input
type = 'number'
value = {val}
onChange = {(e) => { setVal(parseInt(e.target.value)) ]}
step = 1000
/>
<button onClick={() => {
dispatch({
type: ACTION_TYPE.deposit,
payload : number,
})
}}>출금</button>
<button onClick={
dispatch({
type: ACTION_TYPE.withdraw,
payload : number,
})
}>예금</button>
</div>
)
}
위의 코드 처럼 state를 업데이트 해줄 reducer를 설정해 주고, dispatch로 원하는 작업을 설정할 수 있습니다. 사실 위와 같은 상황에서 money state는 useState를 사용해도 될 정도로 간단한 작업을 수행합니다. 다음 예제를 통해 복잡한 작업에서 빛을 발하는 useReducer를 확인해 봅시다.
(예제 2 : 출석부)
import {useState, useReducer} from 'react';
const ACTYPE = {
add : 'add',
delete : 'delete',
hereCheck : 'hereCheck',
}
const initial = {
count: 0,
students : [],
}
const reducer = (state, action) => {
switch(action.type) {
case ACTYPE.add:
return {
count: state.count + 1,
students: [...state.students, action,addInfo],
};
case ACTYPE.delete:
return {
count:state.count - 1
students: state.students.filter((infoOne) => infoOne.id !== action.deleteId),
};
case ACTYPE.hereCheck:
return {
count : state.count,
students : state.students.map((student)=>{
if(action.hereId === student.id) {
{
...student,
isHere: !student.isHere
}
};
return student;
})
}
default:
return state;
}
};
export default function App () {
const [name, setName] = useState("");
const [nameBook, dispatch] = useReducer(reducer, initial);
const handleAdd = () => {
dispatch({
type: ACTYPE.add,
addInfo: {
id: Date.now(),
name : name,
isHere : false
},
})
setName("")
}
const handleDelete = (id) => {
dispatch({
type : ACTYPE.delete,
deleteId : id,
})
}
const handleHere = (id) => {
dispatch({
type: ACTYPE.hereCheck,
hereId : id,
})
}
return (
<div>
<h2> 출석부 출석부~ </h2>
<p> 총 학생 수 : {nameBook.count} </p>
<input
type = 'text'
value = {name}
onChange = { (e) => { setName(e.target.value) } }
/>
<button onClick={handleAdd}> 추가 </button>
{
nameBook &&
<ul>
{
nameBook.students.map((student) => {
return (
<li key={student.id}>
<span
onClick = {() => handleHere(student.id)}
style = {
student.isHere
? {color:gray, textDecoration: line-through}
: {}
}
}>
{student.name}
</span>
<button onClick={() => handleDelete(student.id)}>삭제</button>
</li>
)
})
}
</ul>
}
</div>
)
}
위 코드를 보면 state에 대한 복잡한 작업을 reducer에서 수행해 주는 것을 볼 수 있습니다. 앞으로 위와 같이 하위 값들이 있는 state에 대한 복잡한 작업을 해야하는 경우 useReducer를 사용하여 가독성 높은 코드를 작성해 보면 좋을 것 같습니다.
'React Hooks 다시보기..!!' 카테고리의 다른 글
[React] React Hooks 다시보기..!! (useContext, useMemo) (0) | 2024.06.02 |
---|---|
[React] React Hooks 다시보기..!! (useState, useEffect, useRef) (2) | 2024.06.02 |