Javascript/React

[리액트를 다루는 기술] Hooks (2)

  • -
728x90

https://wonillism.tistory.com/264

 

[리액트를 다루는 기술] Hooks (1)

Hooks는 리액트 v16.8에 새로 도입된 기능으로 함수형 컴포넌트에서 상태 관리를 할 수 있는 useState, 렌더링 직후 작업을 설정하는 useEffect 등의 기능을 제공하여 기존의 함수형 컴포넌트에서 할 수

wonillism.tistory.com

 

useMemo

useMemo를 사용하면 함수형 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있다.

 

import React, { useState } from 'react';

const getAverage = nums => {
  console.log('평균값 계산 중...');
  if (nums.length === 0) return 0;
  const sum = nums.reduce((a, b) => a + b);
  return sum / nums.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState('');

  const onChange = e => {
    setNumber(e.target.value);
  };

  const onInsert = e => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber('');
  };

  return (
    <div>
      <input value={number} onChange={onChange} />
      <button onClick={onInsert}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>
        <b>평균값: </b> {getAverage(list)}
      </div>
    </div>
  );
};

export default Average;

위와 같이 코드를 작성하면 숫자를 등록할 때뿐만 아니라 인풋 내용이 수정될 때도 getAverage함수가 호출되는 것을 확인할 수 있다.

인풋 내용이 바뀔 때는 평균값을 다시 계산할 필요가 없는데, 이렇게 렌더링할 때마다 계산하는 것은 낭비이다.

 

이를 위해 존제하는 것이 useMemo이다.

 

비슷한 기능을 하는 React.memo가 있는데 공통점과 차이점을 알아보자.

 

React.memo

React.memo는 Higer-Order Components(HOC)이다.

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

일반 컴포넌트는 인자로 받은 props를 UI에 활용하는 반면에, HOC는 인자로 받은 컴포넌트를 새로운 별도의 컴포넌트로 만든다. HOC는 리액트의 API가 아니라 리액트가 컴포넌트를 구성하는데 있어서 일종의 패턴이라고 보면된다.

만약 컴포넌트가 같은 props를 받을 때 같은 결과를 렌더링한다면 React.memo를 사용하여 불필요한 컴포넌트 렌더링을 방지할 수 있다. 즉, 컴포넌트에 같은 props가 들어온다면 리액트는 컴포넌트 렌더링 과정을 스킵하고 마지막에 렌더링된 결과를 재사용한다.

 

React.memo는 오직 props가 변경됐는지 아닌지만 체크한다. 만약 React.memo에 감싸진 함수형 컴포넌트가 함수 내부에서 useState나 useContext같은 훅을 사용하고 있다면, state나 context가 변경될 때마다 리렌더링된다.

 

useMemo

useMemo는 메모이즈된 값을 return하는 hook이다.

인자로 함수와 의존 값을 받는다. useMemo는 두번째 인자로 준 의존 인자 중에 하나라도 변경되면 값을 재 계산한다. 이를 통해 매 렌더시마다 소요되는 불필요한 계산을 피할 수 있다. 만약 dependencies인자로 아무것도 전달되지 않는다면, 렌더시마다 항상 값을 새롭게 계산하여 return 한다.

 

React.memo와 useMemo 모두 props가 변하지 않으면 인자로 넘긴 함수는 재실행되지 않고, 이전의 메모이즈된 결과를 반환한다.

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

 

useMemo를 사용하여 위 코드를 수정해보자.

// useMemo 추가
import React, { useMemo, useState } from 'react';

const getAverage = nums => {
  console.log('평균값 계산 중...');
  if (nums.length === 0) return 0;
  const sum = nums.reduce((a, b) => a + b);
  return sum / nums.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState('');

  const onChange = e => {
    setNumber(e.target.value);
  };

  const onInsert = e => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber('');
  };

// useMemo 사용, list값이 변할때만 적용
  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      <input value={number} onChange={onChange} />
      <button onClick={onInsert}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>
      {/* getAverage -> avg */}
        <b>평균값: </b> {avg}
      </div>
    </div>
  );
};

export default Average;

 

 

useCallback

useCallback은 useMemo와 비슷한 함수이다. 주로 렌더링 성능을 최적화해야하는 상황에서 사용한다. 이 Hook을 사용하면 만들어 놨던 함수를 재사용할 수 있다.

 

위에서 useMemo를 사용한 Average컴포넌트를 보면 컴포넌트가 리렌더링될 때마다 새로 만들어진 함수를 사용하게 된다. 컴포넌트의 렌더링이 자주 발생하거나 렌더링해야 할 컴포넌트의 개수가 많아지면 이 부분을 최적화해 주는 것이 좋다.

import React, { useMemo, useState, useCallback } from 'react';

const getAverage = nums => {
  console.log('평균값 계산 중...');
  if (nums.length === 0) return 0;
  const sum = nums.reduce((a, b) => a + b);
  return sum / nums.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState('');

  const onChange = useCallback(e => {
    setNumber(e.target.value);
  }, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성

  const onInsert = useCallback(
    e => {
      const nextList = list.concat(parseInt(number));
      setList(nextList);
      setNumber('');
    },
    [number, list],
  ); // number, list 가 바뀌었을 때만 함수 생성

  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      <input value={number} onChange={onChange} />
      <button onClick={onInsert}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>
        <b>평균값: </b> {avg}
      </div>
    </div>
  );
};

export default Average;

 

useCallback의 첫 번째 파라미터에는 생성하고 싶은 함수를 넣고, 두 번째 파라미터에는 배열(dependencies)을 넣으면 된다. 

 

 

useRef

useRef는 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해 준다.

 

Javascript를 사용할 때에는, 우리가 특정 DOM을 선택해야 하는 상황에 getElementById, querySelector 같은 DOM Selector 함수를 사용해서 DOM을 선택한다.

 

리액트를 사용하는 프로젝트에서도 가끔씩 DOM을 직접 선택해야 하는 상황이 발생 할 때도 있다. 예를 들어 특정 엘리먼트의 크기를 가져와야 한다던지, 스크롤바 위치를 가져오거나 설정하는 등 다양한 상황이 있을 수 있다.

 

import React, { useMemo, useState, useCallback, useRef } from 'react';

const getAverage = nums => {
  console.log('평균값 계산 중...');
  if (nums.length === 0) return 0;
  const sum = nums.reduce((a, b) => a + b);
  return sum / nums.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState('');
  // useRef 사용
  const inputEl = useRef(null);

  const onChange = useCallback(e => {
    setNumber(e.target.value);
  }, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성

  const onInsert = useCallback(
    e => {
      const nextList = list.concat(parseInt(number));
      setList(nextList);
      setNumber('');
      inputEl.current.focus();
    },
    [number, list],
  ); // number, list 가 바뀌었을 때만 함수 생성

  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      {/* ref 적용 */}
      <input value={number} onChange={onChange} ref={inputEl} />
      <button onClick={onInsert}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>
        <b>평균값: </b> {avg}
      </div>
    </div>
  );
};

export default Average;

useRef를 사용하면 ref를 설정하면 useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킨다.

728x90
300x250
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.