useCallback
을 사용하여 함수 재사용useMemo
와 비슷한 Hook
useMemo
는 특정 결과값을 재사용 할 때 사용하는 반면, useCallback
은 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용.useCallback
불러오기import React,{useRef, useState, useMemo, useCallback} from 'react';
App.js
에서
onCreate
,onRemove
,onToggle
-> 위 함수들은 컴포넌트가 매번 리렌더링 될 때마다 새로운 함수 만들고 있음.
-> 함수를 선언하는 것 자체는 사실 메모리도, CPU 도 리소스를 많이 차지 하는 작업은 아니기에 함수를 새로 선언한다고 해서 그 자체 만으로 큰 부하가 생길일은 없다.
But! 그럼에도 불구하고 한번 만든 함수를 필요할때만 새로 만들고 재사용하는 것은 중요하다.return ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onRemove={onRemove} onToggle={onToggle}/> <div>활성 사용자 수 : {count}</div> </> ) }
-> 그 이유는, 컴포넌트(
<CreateUser/>
,<UserList/>
)에서props
가 바뀌지 않았으면Virtual DOM
에 새로 렌더링하는 것 조차 하지 않게 만들어 줄 수 있다. (재사용할 수 있게 구현 가능)
->이런 작업을 하려면, 매번 함수가 새로 만들어지는 구조라면 최적화 하지 못함..
그래서 함수를 재사용해줘야한다.
App.js
import React,{useRef, useState, useMemo, useCallback} from 'react';
import CreateUser from './CreateUser';
import UserList from './UserList';
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 =>{
const {name, value}=e.target;
setInputs({
...inputs,
[name]:value
});
},[inputs]);
const [users, setUsers] = useState([
{
id:1,
username: 'gyomni',
email: 'hi1@gmail.com',
active:true,
},
{
id:2,
username: 'joy',
email: 'hi2@gmail.com',
active:false,
},
{
id:3,
username: 'zoe',
email: 'hi3@gmail.com',
active:false,
}
]);
const nextId = useRef(4);
const onCreate = useCallback(()=>{
const user={
id:nextId.current,
username,
email,
};
setUsers([...users, user]);
setInputs({
username:'',
email:''
});
nextId.current+=1;
},[username,email, users]);
const onRemove = useCallback(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;
📍
onChange
const {username, email} = inputs; const onChange = useCallback(e =>{ const {name, value}=e.target; setInputs({ ...inputs, [name]:value }); },[inputs]);
- 기존의 함수 다 감싸주면 됨
- 내부에서 의존하고 있는 값을 확인
-> 현재inputs
을useState
로 관리하고 있는 상태.
-> 두번째 파라미터 Deps배열에inputs
를 넣어줘야 함.
->onChange
함수는inputs
가 바뀔때만 함수가 새로 만들어지고, 그렇지 않다면 기존에 만든 함수 재사용함.
📍
onCreate
const onCreate = useCallback(()=>{ const user={ id:nextId.current, username, email, }; setUsers([...users, user]); setInputs({ username:'', email:'' }); nextId.current+=1; },[username,email, users]);
- 여기서 참조하는 것 :
username
,users
username
,
->const {username, email} = inputs;
이런식으로inputs
에서 바깥으로 빼준 값.
-> 결국은 상태이기 때문에 Deps에 넣어줘야함.- 만약에 넣는 것을 깜빡한다면 함수 내부에서 해당 상태들을 참조하게 될 때 가장 최신 상태를 참조하게 되는 것이 아니라, 이전에 컴포넌트가 처음 만들어질 때 옛날 상태를 참조하게 되는 불상사가 일어날 수 있음.
useCallback
내부에서 참조하는 상태 혹은props
로 받아온 어떤 값이 있다면 Deps에 넣어주기.
만약App
컴포넌트가 어떤 함수를props
로 받아온다고 치면, ( ex)App({onDo}){ }
)
-> 이것을useCallback
내부에서 사용하게 된다면, 이 또한 Deps에 넣어줘야 한다 !
📍
onRemove
const onRemove = useCallback(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]);
📍
onToggle
const onToggle = useCallback(id =>{ setUsers(users.map( user=>user.id ===id ?{ ...user, active: !user.active} : user )); }, [users])
여기까지만 하면 아직 눈에 띄는 최적화는 없다.
나중에 컴포턴트 리렌더링 최적화 작업을 해줘야 성능이 좋아진다.
그 작업을 하기 전에 어떤 컴포넌트가 현재 리렌더링 되고 있는지 알기 위해서
React Developer Tools를 설치해줘야 한다!
물론 어떤 컴포넌트들이 리렌더링 된지 알고 싶다다면, 단순히 , React Developer Tools를 쓰는것이 더 현명한 방법!user
컴포넌트에서 console.log(user)
해도 되지만
개발자 도구에 들어가서 보면 현재 리엑트 컨포넌트 들이 어떻게 구성되어 있는지 알 수 있다.
Highlight updates when component render 를 체크해준다! 계정명 text를 수정해보자. 계정명 text를 수정한다고 해서 렌더링이 될 필요가 없는데, 계속 렌더링이 발생하는 것을 볼 수 있다.
이런 경우는 성능이 확달라지지 않지만 데이터 개수가 몇만개 정도가 된다면 느려진다.
그렇다면 어떻게 최적화를 할 수 있을까??! 🤔 => click~!😎
학습 : 벨로퍼트와 함께 하는 모던 리엑트