useCallback

gyomni·2022년 2월 25일
0

React

목록 보기
5/9
post-thumbnail

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]);
  • 기존의 함수 다 감싸주면 됨
  • 내부에서 의존하고 있는 값을 확인
    -> 현재 inputsuseState로 관리하고 있는 상태.
    -> 두번째 파라미터 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,email, users
  • username, email
    -> 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를 설치해줘야 한다!
물론 어떤 컴포넌트들이 리렌더링 된지 알고 싶다다면, 단순히 user컴포넌트에서 console.log(user)해도 되지만, React Developer Tools를 쓰는것이 더 현명한 방법!

개발자 도구에 들어가서 보면 현재 리엑트 컨포넌트 들이 어떻게 구성되어 있는지 알 수 있다.
Highlight updates when component render 를 체크해준다! 계정명 text를 수정해보자. 계정명 text를 수정한다고 해서 렌더링이 될 필요가 없는데, 계속 렌더링이 발생하는 것을 볼 수 있다.
이런 경우는 성능이 확달라지지 않지만 데이터 개수가 몇만개 정도가 된다면 느려진다.
그렇다면 어떻게 최적화를 할 수 있을까??! 🤔 => click~!😎

학습 : 벨로퍼트와 함께 하는 모던 리엑트

profile
Front-end developer 👩‍💻✍

0개의 댓글