본문 바로가기

FRONT

[React] useMemo, useCallback, React.memo 란 ?

728x90
  • useMemo : 연산된 값을 재사용하기 위함
  • useCallback : 특정함수를 재사용하기 위함
  • React.memo : 렌더링 된 결과를 재사용하기 위함

1. useMemo

활성 사용자 수를 세는건,

users 에 변화가 있을때만 세야되는건데, input 값이 바뀔 때에도 컴포넌트가 리렌더링 되므로 이렇게 불필요할때에도 호출하여서 자원이 낭비되고 있다.

이를 해결하기 위해 useMemo를 사용한다.

 

- App.js

import React,{useRef, useState, useMemo, useCallback} from 'react';
function countActiveUsers(users){
  console.log('활성 사용자 수를 세는중..');
  return users.filter(user =>user.active).length;
}
const count = useMemo(() => countActiveUsers(users),[users]);

 

import React, { useRef, useState, useMemo } from 'react'; //useMemo
import UserList from './UserList';
import CreateUser from './CreateUser';

function countActiveUsers(users) { //countActiveUsers함수 생성
  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 => {
    setUsers(users.filter(user => user.id !== id));
  };
  const onToggle = id => {
    setUsers(
      users.map(user =>
        user.id === id ? { ...user, active: !user.active } : user
      )
    );
  };
  
  //첫번째 파라미터에는 어떻게 연산할지 정의하는 함수를 넣어주면 되고 
  // 두번째 파라미터에는 deps 배열을 넣어주면 되는데, 
  //이 배열 안에 넣은 내용이 바뀌면, 우리가 등록한 함수를 호출해서 값을 연산해주고, 만약에 내용이 바뀌지 않았다면 이전에 연산한 값을 재사용하게 됩니다.
  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;

 

2. useCallback

- useMemo 는 특정 결과값을 재사용 할 때 사용하는 반면,  

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

 

- 컴포넌트가 리렌더링 될 때 마다 새로 만들어진다.

  함수를 선언하는 것 자체는 사실 메모리도, CPU 도 리소스를 많이 차지 하는 작업은 아니기 때문에 함수를 새로 선언한      다고 해서 그 자체 만으로 큰 부하가 생길일은 없지만, 한번 만든 함수를 필요할때만 새로 만들고 재사용하는 것은 중요.

  그 이유는, 우리가 나중에 컴포넌트에서 props 가 바뀌지 않았으면 Virtual DOM 에 새로 렌더링하는 것 조차 하지 않고

  컴포넌트의 결과물을 재사용 하는 최적화 작업을 할건데, 이 작업을 하려면, 함수를 재사용하는것이 필수.

 

- App.js

import React,{useRef, useState, useMemo, useCallback} from 'react';
const onChange = useCallback(e => {
    const {name, value} = e.target;
    setInputs({
      ...inputs,
      [name]: value
    });
  },[inputs]);
 
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]);

 

import React, { useRef, useState, useMemo, useCallback } from 'react'; //useCallback
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( //onChange에 useCallback 사용
    e => {
      const { name, value } = e.target;
      setInputs({
        ...inputs,
        [name]: value
      });
    },
    [inputs]); ////onchange는 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( //onRemove에 useCallback사용
    id => {
      setUsers(users.filter(user => user.id !== id));
    },
    [users]); //onRemove시 id가 같지 않을때만 함수가 새로 만들어지고 그렇지 않으면 기존에 만든 함수를 재사용하게 된다 
 
 const onToggle = useCallback( //onToggle에 useCallback사용
    id => {
      setUsers(
        users.map(user =>
          user.id === id ? { ...user, active: !user.active } : user
        )
      );
    },
    [users]);//onToggle시 id가 같을 때만 함수가 새로 만들어지고 그렇지 않으면 기존에 만든 함수를 재사용하게 된다 
 
  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;

 

3. React.memo

users 배열이 바뀔때마다 onCreate 도 새로 만들어지고, onToggle,onRemove 도 새로 만들어지기 때문에

User 중 하나라도 수정하면 모든 User 들이 리렌더링되고, CreateUser 도 리렌더링된다.

이는, 컴포넌트의 props 가 바뀌지 않았다면, 리렌더링을 방지하여 컴포넌트의 리렌더링 성능 최적화를 해줄 수 있다

 

- CreateUser.js

export default React.memo(CreateUser);

 

- UserList.js

특정 항목을 수정하게 될 때, 해당 항목만 리렌더링 

import React from 'react';

const User = React.memo(function User({ user, onRemove, onToggle }) {
  return (
    <div>
      <b
        style={{
          cursor: 'pointer',
          color: user.active ? 'green' : 'black'
        }}
        onClick={() => onToggle(user.id)}
      >
        {user.username}
      </b>
      &nbsp;
      <span>({user.email})</span>
      <button onClick={() => onRemove(user.id)}>삭제</button>
    </div>
  );
});

function UserList({ users, onRemove, onToggle }) {
  return (
    <div>
      {users.map(user => (
        <User
          user={user}
          key={user.id}
          onRemove={onRemove}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
}

export default React.memo(
  UserList,
  (prevProps, nextProps) => prevProps.users === nextProps.users
);

 

- App.js

    const onChange = useCallback(e => {
    const {name, value} = e.target;
    //setInputs({  ...inputs, [name]: value  }); },[inputs]);
    setInputs(inputs => ({  ...inputs, [name]: value  })); },[]);
 
    const onRemove =  useCallback(id =>{
     //setUsers(users.filter(user => user.id !== id)); },[users]);
     setUsers(users => users.filter(user => user.id !== id)); },[]);
 
    const onToggle =  useCallback(id => {
      //setUsers(users.map( user => user.id === id ? {...user, active: !user.active} : user )); },[users]);
      setUsers(users => users.map( user => user.id === id ? {...user, active: !user.active} : user )); },[]);
 
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 => ({
      ...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 = useCallback(() => {
    const user = {
      id: nextId.current,
      username,
      email
    };
    setUsers(users => users.concat(user));

    setInputs({
      username: '',
      email: ''
    });
    nextId.current += 1;
  }, [username, email]);

  const onRemove = useCallback(id => {
    setUsers(users => users.filter(user => user.id !== id));
  }, []);
  const onToggle = useCallback(id => {
    setUsers(users =>
      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;
728x90