DEVLOG

React Hooks에 대해 알아보자 (2) :: useMemo, useCallback, useRef 본문

frontend/react

React Hooks에 대해 알아보자 (2) :: useMemo, useCallback, useRef

meroriiDev 2021. 2. 9. 09:33
728x90
반응형

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

 

  • useState
  • useEffect
  • useRef
  • useMemo
  • useCallback
  • useReducer
  • 커스텀 Hooks

○ useRef

useRef의 역할은 두가지이다.

1) ref와 같이 DOM을 선택하는 역할

2) 로컬변수(렌더링과 상관없이 바뀔 수 있는 값)을 활용

 

1) ref와 같이 DOM을 선택하는 역할

먼저 DOM을 선택하는 역할부터 살펴보자

쉽게 생각해서 javascript의 querySelector처럼 DOM에 직접 접근해야할때 사용하면 되는 것이다!

리액트에서 DOM에 접근하는 일이 사실 많이 없지만

가끔씩 아래와 같은 경우에 DOM을 직접 선택해야하는 상황이 발생할 때도 있다.

  • 특정 엘리먼트의 크기를 가져올 때
  • 스크롤바의 위치를 설정하거나 가져올 때
  • 포커스를 설정해야할 때
  • video.js JWPlayer같은 HTML5 Video관련 라이브러리 / D3, chart.js같은 그래프 관련 라이브러리 등 외부 라이브러리를 사용해야 할 때 등...

예시로 등록버튼을 누르면 이름을 입력하는 input에 focus가 가도록 연습해보자

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

function InputSample() {
  const [inputs, setInputs] = useState({
    name: '',
    nickname: ''
  });
  const nameInput = useRef(); //<==============여기서 만들어주고

  const { name, nickname } = inputs; 

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

  const onReset = () => {
    setInputs({
      name: '',
      nickname: ''
    });
    nameInput.current.focus();	//<===========여기서 focus지정!
  };

  return (
    <div>
      <input name="name" placeholder="이름" onChange={onChange} value={name} ref={nameInput}/> {/*<==ref설정*/}
      <input name="nickname" placeholder="닉네임" onChange={onChange} value={nickname}/>
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b>
        {name} ({nickname})
      </div>
    </div>
  );
}

export default InputSample;

ref객체를 만들어 원하는 DOM에 지정해준 후

.current값이 우리가 원하는 DOM을 가르키게 된다.

 

2) 로컬변수(렌더링과 상관없이 바뀔 수 있는 값)을 활용

useRef로 컴포넌트 안에서 조회 및 수정 할 수 있는 변수도 관리할 수 있다.

useRef로 관리하는 변수는 값이 바뀐다고 컴포넌트가 리렌더링되지 않는다.

 

이 변수를 사용하여 다음과 같은 값을 관리 할 수 있다.

  • setTimeout, setInterval 을 통해서 만들어진 id
  • 외부 라이브러리를 사용하여 생성된 인스턴스
  • scroll 위치

배열에 항목을 추가하는 과정에서 고유 id값을 useRef로 관리해보는 연습을 해보자!

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

function App() {
  const users = [
    {
      id: 1,
      username: 'tester',
      email: 'tester@example.com'
    },
    {
      id: 2,
      username: 'tester2',
      email: 'tester2@example.com'
    }
  ];

  const nextId = useRef(3);
  const onCreate = () => {
    // 나중에 구현 할 배열에 항목 추가하는 로직
    // ...

    nextId.current += 1;
  };
  return <UserList users={users} />;
}

export default App;

useRef를 사용해서 다음 id값을 넣어준후

수정할때는 .current를 수정 / 조회할때는 .current를 조회하며 된다.

 

○ useMemo

함수형 컴포넌트 내부에서 발생하는 연산된 값을 재사용하여 성능을 최적화하는 Hook이다.

 

먼저 state로 평균을 보여주는 함수형 컴포넌트를 만들어보자

import React, {useState} from 'react';

const getAverage = numbers => {
    console.log('평균값 계산 중');
    if(numbers.length === 0) return 0;
    const sum = numbers.reduce((a, b) => a+b);
    return sum/numbers.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('');
    }
    const onKeyPress = (e) => {
        if(e.key === 'Enter'){
            onInsert();
        }
    }

    return(
        <div>
            <input value={number} onChange={onChange} onKeyPress={onKeyPress}/>
            <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;

input에 값을 입력하고 등록버튼을 누를때마다 입력된 값들의 평균을 계산하는 코드인데

    const onKeyPress = (e) => {
        if(e.key === 'Enter'){
            onInsert();
        }
    }

편의를 위해 엔터를 누를때도 동작할 수 있도록 코드를 추가해주었다. (재밌자농^,^)

여기서 .reduce()함수가 쓰였는데 어떤일을 하는 함수인지는 여기서 알아보자알아보자~

 

{getAverage(list)}
{useMemo(()=>getAverage(list), [list])}

 

위 코드에서 getAverage를 호출해주는 부분에서 useMemo를 사용해주면

input값이 바뀔때마다 호출되는게 아니라

list내용이 변경될 때만 함수가 호출되어 더 최적화된 실행이 가능하다.

 

○ useCallback

useCallback은 useMemo와 비슷한 함수이다. 주로 렌더링 성능을 최적화 하는 상황에서 사용한다.

이 Hook를 사용하면 이벤트 핸들러 함수를 필요할 때만 생성할 수 있다!

 

예를들어 바로 위에서 만든 Average컴포넌트에서 onChagne와 onInsert라는 함수는 컴포넌트가 리렌더링될 때마다 이 함수들이 새로 생성이 된다.

만약 렌더링이 자주 발생하거나 컴포넌트의 개수가 많아지면 이런 부분에서 최적화가 필요하다.

따라서 컴포넌트가 처음 렌더링될때, number나 list가 변경되었을 때만 함수가 생성되도록 useCallback을 이용해서 최적화시켜보자!

 

    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가 변경될때만 함수 생성

useCallback의 첫번째 파라미터는 생성하고 싶은 함수를

두번째 파라미터에는 배열을 넣으면 된다. 이 배열에는 어떤 값이 바뀌었을때 함수를 생성해야하는지 그 값을 적어준다.

비어있는 배열을 넣으면 렌더링될 때 한번만 함수가 생성되며,

배열안에 내용을 넣어주면 그 내용이 변경될때마다 즉, 인풋내용(number)이 바뀌거나 새로운항목(list)이 추가될때마다 함수가 생성된다.

기존값을 조회해서 생성해야하는 경우에는 deps를 절대 비워둬서는 안된다!

 

useCallback은 결국 useMemo로 함수를 반환하는 상황에서 더 편하게 사용할 수 있는 Hook이다.

숫자, 문자열, 객체처럼 일반 값을 재사용하려면 useMemo를,

함수를 재사용하려면 useCallback을 사용하면 되겠다!

useCallback(() => {
	console.log('hello world!');
}, []);

useMemo(() => {
    const fn = () => {
    	console.log('hello world!');
    },
    return fn;
}, []);

 

728x90
반응형
Comments