본 글은 리엑트 공식 문서를 참고했습니다.
원문보기
const memoizedCallback = useCallback(
() => {
dosomething(a, b);
},
[a, b],
);
메모이제이션된 콜백을 반환한다.
useCallback은 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용한다.
불필요한 렌더링을 방지하기 위해 사용된다는 점에서 useMemo와 같다.
단, useMemo는 특정 결과값을 재사용할 때 사용하는 반면, useCallback은 특정함수를 새로 만들지 않고 재사용하고 싶을때 사용한다.
const onCreate = () => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
};
const onRemove = id => {
// user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
// = user.id 가 id 인 것을 제거함
setUsers(users.filter(user => user.id !== id));
};
const onToggle = id => {
setUsers(
users.map(user =>
user.id === id ? { ...user, active: !user.active } : user
)
);
};
이 함수들은 컴포넌트가 리렌더링 될 때 마다 새로 만들어진다. 함수를 선언하는 것 자체는 사실 메모리도, CPU도 리소스를 많이 차지 하는 작업은 아니기 때문에 함수를 새로 선언한다고 해서 그 자체만으로 큰 부하가 생길일은 없지만, 한번 만든 함수를 필요할때만 새로 만들고 재사용하는 것은 여전히 중요하다.
그 이유는, 우리가 나중에 컴포넌트에서 Props가 바뀌지 않았다면 Virtual DOM에 새로 렌더링하는 것 조차 하지 않고 컴포넌트의 결과물을 재사용하는 최적화 작업이 필요하다. 이 작업을 하려면 함수를 재사용하는 것이 필수이다.
import React, { useRef, useState, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
function countActiveUsers(users) {
console.log('활성 사용자 수를 세는중...');
return users.filter(user => user.active).length;
}
function App() {
const [inputs, setInputs] = useState({
username: '',
email: ''
});
const { username, email } = inputs;
const onChange = useCallback( e => {
//온체인지 함수는 inputs가 바뀔때만 새롭게 함수가 만들어지고, 그렇지 않으면 기존 함수를 계속 사용한다.
const { name, value } = e.target;
setInputs({
...inputs,
[name]: value
});
},
[inputs]
);
const [users, setUsers] = useState([
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com',
active: true
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: false
},
{
id: 3,
username: 'liz',
email: 'liz@example.com',
active: false
}
]);
const nextId = useRef(4);
const onCreate = useCallback(() => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
}, [users, username, email]);
// dept배열에 초기값을 반드시 넣어줘야 한다. 콜백 내부에서 참조하게 되는 상태나 값은 반드시 넣어주자!
const onRemove = useCallback(
id => {
// user.id가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만든다.
// = user.id가 id 인 것을 제거한다.
setUsers(users.filter(user => user.id !== id));
},
[users]
);
const onToggle = useCallback(
id => {
setUsers(
users.map(user =>
user.id === id ? { ...user, active: !user.active } : user
)
);
},
[users]
);
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} />
<div>활성사용자 수 : {count}</div>
</>
);
}
export default App;
주의해야할 점은, 함수 안에서 사용하는 상태 혹은 props가 있따면 꼭, deps배열 안에 포함시켜야 된다는 것이다. 만약에 deps배열 안에 함수에서 사용하는 겂을 넣지 않게 된다면, 함수 내에서 해당 값들을 참조할 때 가장 최신 값을 참조하리가 보장할 수 없다.
props로 받아온 함수가 있다면, 이 또한 deps에 넣어주어야 한다.
useMemo는 특정값이 바뀌었을때만, 특정함수를 실행해서 연산을 처리하도록 처리하고
값이 바뀌지 않았더라면 리랜더링할때 만들어놓았던 값을 재사용할 수 있도록 해준다.
const memoizedValue = useMeMo() => computeExpensiveValue(a,b) [a,b]);
// 함수가 호출되면, computeExpensiveValue를 실행한다.
// deps에 들어간 값이 바뀔때에만 호출이되고, 그렇지 않으면 원래 함수를 재사용한다.
메모이제이션된 값을 반환한다.
import React, {useRef, useState } from 'react';
import userList from './UserList';
import CreateUser from './CreateUser';
function countActiveUsers(users) {
console.log('활성 사용자 수를 세는중...');
return users.filter(user => user.ative).length;
}
function App() {
const [inputs, setInputs] = useState({
username: '',
email: ''
});
const { username, email } = inputs;
const onChange = e => {
const { name, value } = e.target;
setInpust({
...inputs,
[name]: value
});
};
const [users, setUsers] = useState([
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com',
active: true
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: false
},
{
id: 3,
username: 'liz',
email: 'liz@example.com',
active: false
}
]);
const nextId = useRef(4);
const onCreate = () => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
};
const onRemove = id => {
// user.id가 파라미터로 일치하지 않은 원소만 추출해서 새로운 배열을 만든다.
// = user.id가 id 인 것을 제거한다.
setUsers(users.filter(user => user.id !== id));
};
const onToggle = id =? {
setUser(
users.map(user =>
user.id === id ? { ... user, active: !user.active } : user
)
);
};
const count = countActiveUsers(users);
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} />
<div>활성사용자 수 : {count}</div>
</>
);
}
export default App;

활성 사용자 수를 세는것은 users에 변화가 있을때만 세야하는건데, input 값이 바뀔 때에도 컴포넌트가 리렌더링 되므로 이렇게 불필요할때에도 호출해서 자원이 낭비되고 있다.
이러한 상황에는 useMemo라는 Hook 함수를 사용하면 성능을 최적화할 수 있다.
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} />
<div>활성사용자 수 : {count}</div>
</>
);
}
export default App;
useMemo의 첫번째 파라미터에는 어떻게 연산할지 정의하는 함수를 넣어주면 되고, 두번째 파라미터에는 deps 배열을 넣어주면 되는데, **이 배열 안에 넣은 내용이 바뀌면, 우리가 등록한 함수를 호출해서 값을 연산해주고, 만약에 바뀌지 않았다면 이전에 연산한 값을 재사용하게 된다.