useCallback을 사용하여 재사용하기

작업
useCallback
리렌더링방지
코드의 효율화
진행 상태
완료
진행일시
2021/11/28
학습자

[ useCallback 이란 ? ]

특정 함수를 새로만들지 않고 재사용 하고싶을 때 사용

[ useCallback은 언제사용해야 할까? ]

함수를 선언하는 것 자체는 사실 메모리도, CPU 도 리소스를 많이 차지 하는 작업은 아니기 때문에 함수를 새로 선언한다고 해서 그 자체 만으로 큰 부하가 생길일은 없지만, 한번 만든 함수를 필요할때만 새로 만들고 재사용하는 것은 여전히 중요합니다.
그 이유는, 우리가 나중에 컴포넌트에서 props 가 바뀌지 않았으면 Virtual DOM 에 새로 렌더링하는 것 조차 하지 않고 컴포넌트의 결과물을 재사용 하는 최적화 작업을 할건데요, 이 작업을 하려면, 함수를 재사용하는것이 필수입니다.

[ useCallback 예시 ]

useCallback 은 이런식으로 사용합니다.
import React, {useRef, useState, useMemo, useCallback}from 'react'; // import React from 'react'; import './App.css' // import InputSample2 from './InputSample2'; import UserList from './UserList'; import CreateUser from './CreateUser'; // []의 의미는 key값으로 쓰겠다는 의미입니다. // [name] 이 username과 email 두가지의 경우가 있는데, // onChange라는 하나의 함수로 여러값을 저장하기 위해서 사용한것으로 보여집니다. // [e.target.name] : e.target.value 이런식으로 state에 저장을하면 input마다 // 다른 함수를 사용하지 않고 여러개 input값을 저장할 수 있습니다. // username:e.target.value, email: e.target.value // 이렇게 dynamic하게 key값이 들어갑니다^^ //useMemo를 사용하여 연산한 값 재사용 //App 컴포넌트에서 다음과 같이 countActiveUsers 라는 함수를 만들어서, active 값이 true 인 사용자의 수를 세어서 화면에 렌더링을 해보세요. function countActiveUsers(users) { console.log('활성 사용자 수를 세는중 ...'); return users.filter(user => user.active).length; } function App() { const [Inputs, setInputs] = useState({ username : '', email : '', id : '' }); const {username, email, id} = Inputs; const onChange = useCallback(e => { 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 }, ]); //다음값 id지정 const nextId = useRef(4); //새롭게 등록할 때 // useCallback으로 수정한부분 const onCreate = useCallback(() => { const user = { id : nextId.current, username : username, email : email } setUsers([...users, user]); setInputs({ username : '', email : '' }) nextId.current += 1; },[users, username, email]); //삭제하기위해서는 각 id를 알아야한다 // useCallback으로 수정한부분 const onRemove = useCallback(id => { //user.id가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬 // 즉 user.id 가 id인것만 제거하고 그 외에는 새로배열을 생성 setUsers(users.filter(user => user.id !== id)); },[users]); //수정 // useCallback으로 수정한부분 const onToggle = useCallback(id => { setUsers( users.map(user => user.id === id ? {...user, active : !user.active} : user) ) },[users]); //업데이트 // useCallback으로 수정한부분 const onUpdate = useCallback(() => { setUsers ( users.map(user => user.id === id ? {...user, username: username, email : email} : user) ); setInputs({ username : '', email : '', id : '' }) },[users,username,email,id]) // 활성 사용자 수를 세는건, users 에 변화가 있을때만 세야되는건데, //input 값이 바뀔 때에도 컴포넌트가 리렌더링 되므로 이렇게 불필요할때에도 호출하여서 자원이 낭비되고 있습니다. // 이러한 상황에는 useMemo 라는 Hook 함수를 사용하면 성능을 최적화 할 수 있습니다. // Memo 는 "memoized" 를 의미하는데, 이는, 이전에 계산 한 값을 재사용한다는 의미를 가지고 있습니다. // 한번 사용해볼까요? const count = useMemo(() => countActiveUsers(users),[users]); const onModify = (user) => { setInputs({ username : user.username, email : user.email, id : user.id }) }; return ( <> <CreateUser username = {username} email = {email} onChange = {onChange} onCreate = {onCreate} onUpdate = {onUpdate} /> <UserList users={users} onRemove={onRemove} onToggle={onToggle} onModify={onModify}/> <div>활성 사용자 수 : {count}</div> </> ); // return ( // <InputSample2 /> // ) } export default App;
JavaScript
복사

[ 주의 할 점]

함수 안에서 사용하는 상태 혹은 props 가 있다면 꼭, deps 배열안에 포함시켜야 된다는 것 입니다. 만약에 deps 배열 안에 함수에서 사용하는 값을 넣지 않게 된다면, 함수 내에서 해당 값들을 참조할때 가장 최신 값을 참조 할 것이라고 보장 할 수 없습니다. props 로 받아온 함수가 있다면, 이 또한 deps 에 넣어주어야 해요.
사실, useCallback 은 useMemo 를 기반으로 만들어졌습니다. 다만, 함수를 위해서 사용 할 때 더욱 편하게 해준 것 뿐이지요. 이런식으로도 표현 할 수 있습니다.