[React] useCallback / useMemo

호두파파·2021년 4월 6일
0

React

목록 보기
4/38

본 글은 리엑트 공식 문서를 참고했습니다.
원문보기


useCallback

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

useMemo는 특정값이 바뀌었을때만, 특정함수를 실행해서 연산을 처리하도록 처리하고
값이 바뀌지 않았더라면 리랜더링할때 만들어놓았던 값을 재사용할 수 있도록 해준다.

const memoizedValue = useMeMo() => computeExpensiveValue(a,b) [a,b]);
// 함수가 호출되면, computeExpensiveValue를 실행한다. 
// deps에 들어간 값이 바뀔때에만 호출이되고, 그렇지 않으면 원래 함수를 재사용한다.

메모이제이션된 을 반환한다.

App.js

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

  
profile
안녕하세요 주니어 프론트엔드 개발자 양윤성입니다.

0개의 댓글