본문 바로가기
■ Front ■/React

useCallback, useReducer, React.memo, React.Fragment의 개념 정리

by Try Coding 2022. 7. 29.

 

 

 

서론

React Hooks의 개념들중 useCallback, useReducer, React.memo와 React.Fragment의 개념에 대해 공부한 내용을 정리해보았다. 

 


본론

1. useCallback

Memoization 기법으로 컴포넌트의 성능을 최적화 하기위해 사용한다. 

반복적으로 계산해야되는 값이 있다면 이전의 값을 캐싱해둠으로 해당 값이 또 필요할 때 메모리에서 꺼내서 사용하는 형태이다.

 

useMemo의 경우 인자로 콜백함수를 넣어주면  함수가 리턴하는 값 즉 아래의 thisValue를 Memoization 해준다 

 

useMemo(() => {
  return thisValue
}, [item])

 

 

useCallback의 경우 콜백함수 그 자체를

 

() => {

    return thisValue
}
Memoization 해준다 
useCallback(() => {
  return thisValue
}, [item])
 

 

useCallback으로 함수를 감싸서 해당 함수를 새로 생성하는 것이 아닌 필요할 때 마다 메모리에서 가져와서 재사용한다.

 

예를들어 useCallback으로 함수를 감싸서 해당 함수를 새로 생성하는 것이 아닌 필요할 때 마다 메모리에서 가져와서 재사용한다.

 

우리가 함수형 컴포넌트가 렌더링이 된다는 의미를 이해해보면

 

함수형 컴포넌트가 렌더링이 된다 -> 해당 Component 함수를 호출한다 -> 해당 컴포너넌트 내부의 변수는 초기화 된다는 의미이다.

 

이 때 해당 함수를 useCallback으로 감싸서 Memoization 해준다면

Memoize된 함수를 재사용 하기 때문에 최적화가 가능해진다.

 

useCallback은 두개의 인자를 받으며

첫번째 인자로는 Memoization 해 줄 콜백함수,

두번째 인자로는 의존성배열을 받는다.

 

함수를 useCallback으로 감싸주면  Memoization되기 때문에 

의존성배열의 내용이 변경되지 않는한 다시 초기화되지 않는다. 

 

간단히 숫자를 변경하는 input 태그를 만들고 그 숫자가 변경되는 것을 확인해보는 코드를 만든 후 

<div>
  <h1>count :: {count}</h1>
  <input 
      type="number" 
      value={count} 
      onChange={(e) => setCount(e.target.value)}
      />
  <button onClick={numberGenerator}>
    Number
  </button>
</div>

useCallback이 사용된 경우와 사용되지 않은 경우에 대해 비교해 보면 

 

👉 useCallback이 사용 안된경우

state가 변경될때마다  렌더링 되기 때문에 해당 useEffect의 콘솔 또한 계속 불리게 된다.

 const notUseCallbackNumberGenerator = () => {
    console.log(`count ${count}`);
    return;
  }
  
  useEffect(() => {
    console.log('setCount 실행 ');
  }, [notUseCallbackNumberGenerator]);

 

 

👉 useCallback이 사용된 경우

state가 변경되더라도 useCallback을 사용하면 Memoization된 캐시를 사용하기 때문에 useEffect가 불리지 않음을 확인할 수 있다. 

const numberGenerator = useCallback(() => {
    console.log(`count ${count}`);
    return;
  }, []);

 

 

 

2. useReducer

React에서 state 관리를 위한 Hook으로 우리는 useState를 사용했다. useReducer 또한 state 관리를 위한 React Hook의 하나다

useReducer를 사용하기 위해서는 선행해서 알아야 할 요소 3가지가 있다

1. Reducer (Action을 처리함) 

2. Dispatch (요구)

3. Action (내용)

위의 3요소를 이해하기 위해 아래의 예문을 살펴보자. 

 

 길동이는  은행업무를 하기위해 까꿍은행(Reducer)을 방문했다. '홍길동'은 '김두환'에게 1만원 송금(Action)을 까꿍은행(Reducer)에게 요구(Dispatch)했고 계좌의 입출금 내역(State)은 까꿍은행(Reducer)이 대신 업데이트 해주었다.

 

Component의 관점으로 이해한다면

State를 업데이트 하기 위해 Dispach 함수에 인자로 Action을 넣어 Reducer에게 전달을한다.

그럼 Reducer는 해당 State를 Action의 내용대로 업데이트 해준다. 

 

아래의 예시코드를 보면 

function UseReducerTest(){
  const [number, setNumber] = useState(0);
  const [money, dispatch] = useReducer(reducer, 0);

  return (
    <>
      <h1>까꿍은행</h1>
      <p>현재 길동이의 계좌상태 :: {money}</p>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(parseInt(e.target.value))}
        />
        {/* 액션을 오브젝트 형태로 전달한다  */}
      <button onClick={() => dispatch({ type: 'INCREMENT', payload: number })}>
        입금
      </button>
      <button onClick={() => dispatch({ type: 'DECREMENT' , payload: number })}>
        출금
      </button>
      <button onClick={() => dispatch({ type: 'INIT'})}>
        전액출금
      </button>
      <br/>
    </>
  );
}

각각의 버튼 '입금', '출금', '전액출금' 이라는 길동이의 희망하는 Action의 선택에 따라 Reduce(까꿍은행)에게 Dispach(요구)되는 것이며 해당 Dispach는 Action을 Object 형태로 Reducer에게 전달하면 Reducer에서

 

const reducer = (state, action) => {
  console.log('reducer 작동',state, action);
  switch (action.type) {
    case 'INCREMENT':
      return state + action.payload;
    case 'DECREMENT':
      return state - action.payload;
    case 'INIT':
      state = 0
      return state
    default:
      throw new Error();
  }
}

액션에서 받은 Type에 따라 입출금내역 (State)값을 업데이트 시켜준다

 

3. React.memo

React에서는 거의 같은 결과를 제공한다 부모컴포넌트가 렌더링 되면 부모를 따라 자식도 렌더링이된다. 자식 컴포넌트는 

불필요한 렌더링으로 인하여 전체적인 성능을 저하하는 결과를 초래한다. 

 

자식 컴포넌트에서 같은결과가 아닌 결과값이 달라질 때만 렌더링이 필요한 경우 React.memo를 활용한다.

React에서 제공하는 고차 컴포넌트(어떠한 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환해주는 함수)이다.

React.memo에서의 memo는 Memoization을 의미하며 글자 그대로 메모리상에 미리 저장해서 필요할 때 꺼내서 재사용한다.

React.memo를 활용하여 어떤 컴포넌트가 들어왔을 때 이 컴포넌트의 내용이 변경이 있는지를 확인한 후 이를 렌더링 할 지를 판단한다.

오직 Props의 변화에만 의존하는 최적화 기법이기 때문에 useState, useReducer, useContext와 같은 상태와 관련된 Hook을 사용시에는 Props 변화가 없더라도 다시 리렌더링된다는 것을 기억해야한다. 

 

 

❗️주의할 점

Memoization이라는 기법은 결국 메모리상에 미리 저장해놔야한다는 것으로 무분별하게 활용한다면 이것 또한 성능 저하에 영향을 끼칠 수 있으니 해당 컴포넌트가 같은 Props로 빈번하게 렌더링 되거나 해당 렌더링 실행시 성능에 영향을 끼칠만한 로직을 처리해야한다면 사용을 고려한다. 

 

 

 

부모의 주소지 변경과 자식의 주소지 변경을 표현한다면

function UseReactMemoTest(){
  const [parentAddress, setParentAddress] = useState('');
  const [childAddress, setChildAddress] = useState('');


  const changeParentAddress = (e) => {
    setParentAddress(e.target.value);
  }

  const changeChildAddress = (e) => {
    setChildAddress(e.target.value);
  }

  console.log('부모 렌더링 완료')

  return (
    <div>
      
      <h1>부모 홍길남의 주소</h1>
      부모의 주소지 변경 ::: 
      <input 
        type="text" 
        value={parentAddress} 
        onChange={changeParentAddress}
        />
      <p>주소지 : {parentAddress}</p>
      <br /><br />
      자식의 주소지 변경 ::: 
      <input 
        type="text" 
        value={childAddress} 
        onChange={changeChildAddress}
        />
      <br />
      ================================================================================
   

      <UseReactMemoTestChild childAddress={childAddress} name="홍길동"></UseReactMemoTestChild>
    </div>
  );
  
}

자식인 UseReactMemoTestChild는 

React.memo가 사용되지 않았다면 부모의 주소지가 변경되었을 때 자식또한 자동으로 렌더링된다

하지만 React.memo를 사용하여 최적화한다면 부모가 렌더링 되는것과는 상관없이 오직 자식의 변화가 있을 때만

렌더링 된다. 

 

 

import React, {memo} from 'react'

const UseReactMemoTestChild = memo((props) => {
    console.log('자식 렌더링');
    return (
            <div>
                <div>자식의 이름 : {props.name}</div>
                <div>자식의 주소 : {props.childAddress}</div>
            </div>
        );
    }
);

export default memo(UseReactMemoTestChild);

 

 

 

 

 

4. React.Fragment

들어가기전, Fragment의미는 무엇일까를 찾아보면  

출처 : Naver English 사전

React 상에서 우리는 이미 사용해본 경험이 있을것이다.

<> </> 로 감싸진 태그를 본적이 있지 않은가? 이는 <React.Fragment></React.Fragment>를 축약한 형태이다.
참고 : https://reactjs.org/docs/react-api.html#reactfragment

 

React Top-Level API – React

A JavaScript library for building user interfaces

reactjs.org

 

를 축약한 형태이다.

 

해당 컴포넌트는 React.Fragment로 감싸준 것이 아닌 <div></div>로 감싸주어 리턴한다.

import React from 'react';

const FragmentComponent = () => {
    return (
        <div>
            <h1>Fragment Component</h1>  
        </div>
    );
}
    
export default FragmentComponent;

App.js파일 안에서는 해당 컴포넌트를 불러서 사용해보면 

<div>
  <FragmentComponent />
</div>

이처럼 의미없는 <div></div> 태그가 하나 더 붙어있는것을 확인할 수 있다.

 

이를 React.Fragment를 활용하면 

const FragmentComponent = () => {
    return (
        <React.Fragment>
            <h1>Fragment Component</h1>  
        </React.Fragment>
    );
}

 

이전과는 달리 의미 없는 <div></div> 태그가 사라진것을 확인할 수 있다.

 

이에대한 사용 효과는

1. 스타일링 진행시 

2. 테이블처럼 정해진 구조를 따를 때

3. map() 사용시 fragment에 key를 전달할 때 (현재로서는 Raact.Fragment 상에서는 key라는  prop밖에 받을 수 있다.)

 

최상위 <div></div>가 사라지기 때문에 보다 효율적인 코딩을 진행할 수 있다.


 

 

 

결론

React Hooks의 내용들을 살펴보고 성능 최적화를 위한 방법을 익혀 이를 실무에 적용하는것은 반드시 필요하다.

 


 

 

 

 

◇ 요약 ◇

① 

 

 

 

 

참고자료
https://reactjs.org

 

'■ Front ■ > React' 카테고리의 다른 글

React와 NEXT.js & Firebase를 활용한 CRUD 만들기  (0) 2022.08.10
2. React, 리스트와 키, 폼  (0) 2022.07.11
1. React 기본  (0) 2022.06.29

댓글