React 계속되는 Hook #10

CoderS·2021년 12월 15일
0

리액트 Remind

목록 보기
10/32

#10 Hook 종류 넘 많다 ㅠㅠ

useMemo의 사용방법

이번에는 성능 최적화를 위해서 연산된 값을 useMemo라는 Hook을 이용해 재사용해보겠다.

App 컴포넌트에 countActiveUsers라는 함수를 만들고, active 값이 true 인 사용자의 수를 세어서 화면에 렌더링해보겠다.

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.active).length;
}

function App() {
  const [inputs, setInputs] = useState({
    username: '',
    email: ''
  });
  const { username, email } = inputs;
  const onChange = e => {
    const { name, value } = e.target;
    setInputs({
      ...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 => {
    setUsers(
      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;

countActiveUsers 함수에서 콘솔에 메시지를 출력하도록 한 이유는, 이 함수가 호출될때마다 우리가 알수있게 할려고 한다.

다른 계정명을 클릭해서 초록색으로 만들어서 활성 사용자 수를 업데이트 할 것이다.

하지만 여기서 발생하는 문제가 있다. 바로, input의 값이 바꿀때에도 countActiveUsers 함수가 호출된다는 것이다.

우리가 활성 사용자 수를 셀때에는, users에 변화가 있을때만 해당이되야 하는데 input에 값이 바뀔 때마다 컴포넌트가 리렌더링 되므로 불필요한 호출을하고 있는 중이다.

이 때, 우리는 useMemo 라는 Hook 함수를 사용해서 성능을 최적화 할 것이다.

Memo 는 "memoized" 를 의미하는데, 이는, 이전에 계산 한 값을 재사용한다는 의미를 가지고 있다.

App 파일을 다음과 같이 수정해준다.

App.js

import React, { useRef, useState, useMemo } 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 = e => {
    const { name, value } = e.target;
    setInputs({
      ...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 => {
    setUsers(
      users.map(user =>
        user.id === id ? { ...user, active: !user.active } : user
      )
    );
  };
  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는 useEffect와 비슷한 파라미터를 갖는데, 첫 번째로는 어떻게 연산할지 정의하는 함수를 넣어주면 되고 두 번째로는 deps 배열을 넣어주면 된다.

만약 이 배열 안에 넣은 내용이 바뀌면, 우리가 등록한 함수를 호출해서 값을 연산해주고, 만약에 내용이 바뀌지 않았다면 이전에 연산한 값을 재사용하게 된다.

결과는...

input 값을 넣어도 콘솔에는 아무런 변화가 없어서 완성된 것 이다.

useCallback 사용법

useCallback은 useMemo와 비슷한 Hook이다.

useMemo 는 특정 결과값을 재사용 할 때 사용하는 반면,
useCallback 은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용한다.

App 파일에서 구현한 onCreate, onRemove, onToggle 함수를 확인해보겠다.

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 도 리소스를 많이 차지 하는 작업은 아니기 때문이지만 한번 만든 함수를 필요할때만 새로 만들고 재사용하는 것은 여전히 중요하다.

이 때, useCallback을 사용하면된다.

App.js

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 => {
      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]);

  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 에 넣어주어야 한다.

useCallback 을 이런식으로도 표현이 가능하다.

const onToggle = useMemo(
  () => () => {
    /* ... */
  },
  [users]
);

useCallback 을 사용 함으로써 바로 이뤄낼수 있는 눈에 띄는 최적화는 없다.
컴포넌트 렌더링 최적화 작업을 해주어야만 성능이 최적화가 된다.

참고 : 벨로퍼트와 함께하는 모던 리액트

느낀점 :

  • 오늘은 Hook의 두 종류인 useMemo와 useCallback에 대해 알아봤다.
  • 둘이 비슷한 면이 있지만, 사용되는 용도가 달라서 잘 알아보고 써야겠다.
profile
하루를 의미있게 살자!

0개의 댓글