본 글은 리엑트 공식 문서를 참고했습니다.
원문보기
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 배열을 넣어주면 되는데, **이 배열 안에 넣은 내용이 바뀌면, 우리가 등록한 함수를 호출해서 값을 연산해주고, 만약에 바뀌지 않았다면 이전에 연산한 값을 재사용하게 된다.