React

useEffect, useMemo, useState, useCallback, React.Memo() 개념 및 차이점

아뵹젼 2022. 3. 18.

useState  

 const [inputs, setInputs] = useState({
    username: '',
    email: ''
  });

첫 번째 배열인자인 inputs 는 실제로 데이터를 저장하는 변수이다. 그리고 두 번째 배열 인자는 변수를 변경하기 위한 함수이다. inputs 값을 변경하기 위해선 setInputs 를 이용해야만 한다. useState 괄호 안에 들어가는 값은 inputs 의 초기값이다.

 

 setInputs({
      ...inputs,
      [name]: value
    });

inputs 값을 변경하기 위해서는 다음과 같이 setInputs 함수만을 이용해야 한다.

위 코드에서는 inputs 의 배열 원소들을 그대로 가져온 후, name(username 혹은 email) 에 해당하는 값을 value 로 변경해주는 작업을 setInputs 에서 진행한다.

 

useState 를 사용하면, 화면 새로고침을 하지 않고도 변수의 값을 리렌더링 해줄 수 있다. 즉, 변수의 값을 변경하기 위해 이 함수를 사용하여 변경된 부분만 업데이트를 하는 것이다.

 

 

useMemo 

const count = useMemo(() => countActiveUsers(users), [users]);

첫 번째 인수에는 함수, 두번째 인수에는 배열을 넣어준다.

두 번째 인수에 넣어준 배열의 값이 바뀔때만 함수가 실행된다. 만약 변하지 않았다면 이전의 값을 재사용한다.

따라서 useMemo 는 렌더링 시, 값이 변하지 않았는데 불필요하게 렌더링 되는 것을 방지하기 위해 사용하는 것이다.

즉, 배열의 값이 변하였을 때만 리렌더링 함으로써 성능을 높여주는 역할을 한다. 

useMemo 는 값을 반환해준다.

 

import React, { useState, useRef, useMemo } from 'react';
import './App.css';
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
    },
     // 생략
  ]);

  const nextId = useRef(4); // 초기값이 4

  const onCreate = () => {
    const user = {
      id: nextId.current,
      username,
      email
    };
    setUsers(users.concat(user));

    setInputs({
      username: '',
      email: ''
    });

    nextId.current += 1;
  };

   // 생략

  /** 첫 번재 파라미터에는 어떻게 연산할지 정의하는 함수, 
   *  두 번째 파라미터에는 deps 배열을 넣어준다.
   *  이 배열에 넣은 내용이 바뀌면, 등록한 함수를 호출해서 값을 연산하고
   *  만약 내용이 바뀌지 않았다면 이전에 연산한 값을 재사용한다.*/
  const count = useMemo(() => countActiveUsers(users), [users]);

  return (
    <>
    // 생략
      <div>활성사용자 수 : {count}</div>
    </>
  );
}

export default App;

만약, input에 입력하는 이벤트가 발생한다면, App 컴포넌트의 state(username, email) 가 변경된다.

따라서 App Component가 리렌더링되면서 countActiveUsers가 재호출이 될 것이다.

이때 만약, count 를 useMemo 로 받아오지 않았다면, users 의 값이 변하지 않을 때도 countActiveUsers 함수를 불필요하게 호출하게 될 것이다. 그러나 useMemo 를 사용했기 때문에 users 배열이 변경되지 않았더라면, 이전의 count 값을 재사용하게 된다.

 

 

useEffect

useEffect(() => {
        console.log("useEffect");
}, [user]);

useEffect 는 랜더링이 될 때마다 호출된다. 

첫 번째 인자는 수행하고자 하는 작업이며, 두 번째 인자는 검사하고자 하는 배열 또는 빈 배열이 오게 된다.

 

 

컴포넌트가 화면에 처음 렌더링 될 때 한 번만 실행하게 하고 싶다면 두 번째 인자에 [] 와 같은 빈 배열을 넣으면 된다.

-> 마운트 될 때

useEffect(() => {
        console.log("마운트 될 때만 실행됨");
}, []);

 

 

만약, 리렌더링 될 때마다 실행하고 싶은 작업이 있다면 아예 인자를 생략하면 된다.

useEffect(() => {
        console.log("매 렌더링 마다 실행됨");
});

 

한편, 특정 배열 값이 업데이트(변경) 될 때마다 실행하고 싶은 작업이 있다면 배열 안에 검사하고 싶은 값을 넣어주면 된다. 이때는 user 배열의 값이 변경될 때마다 실행될 것이다.

useEffect(() => {
        console.log("user 값 변경됨");
}, [user]);

 

만약, 마운트가 아닌 언마운트(컴포넌트가 화면에서 사라짐) 될 때 실행하고 싶은 작업이 있다면 다음과 같은 cleanup 함수를 사용하면 된다. 마운트와 마찬가지로, 렌더링이 아닌 언마운트 될 때만 다음 작업을 실행하고 싶다면 두 번째 인자로 [] 빈배열을 주면 된다.

혹은, user 의 값이 업데이트 되기 직전에 작업하고 싶은 내용을 return 내부에 사용해도 된다.

cleanUp 함수는 두 번째 인자인 배열이 업데이트 되기 직전에 실행되기 때문이다.

useEffect(() => {
        return () => {
        	console.log("cleanUp");
        };
}, [user]);

 

 

useCallback

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

한번 만든 함수를 필요할때만 새로 만들고 재사용하는 것은 여전히 중요하다.

컴포넌트에서 props 가 바뀌지 않았으면 Virtual DOM 에 새로 렌더링하는 것 조차 하지 않고 컴포넌트의 결과물을 재사용 하는 최적화 작업을 하기위해선 함수를 재사용하는것이 필요하기 때문이다.

 

즉,  useMemo 는 deps (배열 값) 가 변화하면 첫 번째 인자에 담긴 함수를 실행하고 값을 반환하는데,

useCallback 은 deps 가 변화하면 첫 번째 인자의 함수를 반환해준다. 

따라서 useCallback 으로 함수를 선언한다면, 함수 컴포넌트에서 불필요하게 함수를 업데이트 하는 것을 방지해준다.

주의할 점은, 함수 안에서 사용하는 상태 혹은 props 가 있다면 꼭, deps 배열안에 포함시켜야 된다.

만약에 deps 배열 안에 함수에서 사용하는 값을 넣지 않게 된다면, 함수 내에서 해당 값들을 참조할때 가장 최신 값을 참조 할 것이라고 보장 할 수 없다.

 

const onChange = useCallback(e => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value
    });
  },
    [inputs]
  );

위 코드에서 inputs 배열의 값이 변하게 된다면 컴포넌트가 리렌더링 될 때 onChange 함수를 새로 만들게 된다.

이때 useCallback 은 매번 새로운 함수 onChange 를 반환하게 한다.

즉슨 inputs 의 값이 0일 때의 onChange 와 값이 1일 때의 onChange 는 서로 다른 함수라는 것이다.

 

 

React.Memo()

React.Memo 는 컴포넌트를 인자로 받아 새로운 컴포넌트를 다시 return해주는 함수이다.

렌더링이 일어날 때 props가 같다면, React는 메모이징된 내용을 재사용해줌으로써, 이 함수는 props가 변경됐는지 아닌지만 체크한다.

그렇다면 props가 변하지 않으면 첫 번째 인자로 넘긴 함수는 재실행되지 않고, 이전의 메모이즈된 결과를 반환한다는 점에서 useMemo 와 공통점이있다. 그렇다면 다른 점이 무엇일까?

React.memo는 HOC(HOC란 화면에서 재사용 가능한 로직만을 분리해서 component로 만들고, 재사용 불가능한 UI와 같은 다른 부분들은 parameter로 받아서 처리), useMemo는 hook(class없이 state를 사용)이다.

React.memo는 HOC이기 때문에 클래스형 컴포넌트, 함수형 컴포넌트 모두 사용 가능하지만, useMemo는 hook이기 때문에 오직 함수형 컴포넌트 안에서만 사용 가능하다.

 

import React from "react";

const CreateUser = ({ username, email, onChange, onCreate }) => {
    return (
        <div>
            // 생략
        </div>
    );
};

export default React.memo(CreateUser);

 

다음과 같이 CreateUser 은 리렌더링을 방지하고 싶은 코드를 감싸주었고, 재사용이 불가능한 부분은 parameter 로 받아주었다. 

 

 

또한 함수형 업데이트를 통해 성능을 최적화할 수 있다.

 const onRemove = useCallback(
    id => {
    setUsers(users => users.filter(user => user.id !== id));
  },[]);

위 코드에서 setState에 값을 그대로 전달하는 것이 아니라 함수를 전달함으로써 (users => //생략 )

deps 에서 users 를 참조하지 않아도 된다. setUsers 에서는 최신값을 알아서 참조해온다.

따라서 이와 같은 함수형 업데이트에서는 users 를 참조하지 않기 때문에 수정이 일어날 때만 리렌더링이 발생할 것이다.

댓글