상세 컨텐츠

본문 제목

[리액트 스타터3] 8장. hooks

23-24/React.js 3

by 롱롱😋 2023. 11. 24. 10:00

본문

728x90

Hook

Hook은 React 버전 16.8부터 React 요소로 새로 추가되었다. Hook을 이용하여 기존 Class 바탕의 코드를 작성할 필요 없이 상태 값과 여러 React의 기능을 사용할 수 있다. 
 


 
useState
가장 기본적인 Hook으로, 함수형 컴포넌트에서도 가변적인 상태를 지닐 수 있게 해 준다. 
 
📌사용법

const [변수명, set함수명] = useState(초기값); 

 

useState 기능을 사용한 숫자 카운터 구현

import React, { useState } from 'react';
 
const Counter = () => {
  const [value, setValue] = useState(0);
 
  return (
    <div>
      <p>
        현재 카운터 값은 <b>{value}</b>입니다.
      </p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>
    </div>
  );
};

export default Counter;

 
 


useEffect
- Side effect를 수행하기 위한 Hook으로, 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있게 한다.
-
또한, 생명 주기 함수와 동일한 기능을 수행할 수 있다. 
 
📌 Side effect
- 리액트에서의 사이드 이펙트는 그냥 효과&영향을 뜻하는 것에 가깝다.  
- 다른 컴포넌트에 영향을 미칠 수 있고, 렌더링 중에는 작업이 완료될 수 없다.
- 렌더링이 끝난 이후에 실행되어야 하는 작업들이, 사이드로 실행된다는 의미이다. 
 
📌사용법
- 배열의 값이 변경되었을 때, 이펙트 함수가 실행된다. 
이펙트 함수는 처음 컴포넌트가 렌더링 된 이후, 업데이트로 인한 재렌더링 이후에 실행된다. 

useEffect(이펙트 함수, 의존성 배열); 

 
- 다음은 이펙트 함수가 mount, unmount시에 한 번씩만 실행된다. 

useEffect(이펙트 함수, []); 

 
- 다음은 컴포넌트가 업데이트될 때마다 호출된다. 

useEffect(이펙트 함수); 

 
- 다음은 컴포넌트가 unmount 될 때 호출된다. 

useEffect(() => {
// 컴포넌트가 마운트 될 때 실행되는 코드

// 반환된 함수는 언마운트 시 실행된다.
return () =>
{ console.log('컴포넌트가 원마운트될 때 정리 작업을 수행합니다.');
};
}, []); // 빈 의존성 배열을 전달하여 마운트 될 때 한 번만 실행

 
정리하면 다음과 같다. 

useEffect(() -> {
  // 컴포넌트가 마운트 된 이후, 
  // 의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행된다. 
  // 의존성 배열에 빈 배열( [] )을 넣으면 마운트와 언마운트시에 단 한 번씩만 실행된다. 
  // 의존성 배열 생략 시 컴포넌트 업데이트 시마다 실행된다. 
  . . .

  return () => {
    // 컴포넌트가 마운트 해제되기 전에 실행된다. 
    . . .
  }
}, [의존성 변수1, 의존성 변수2, . . . ]); 

 
 

여기서 useEffect는 컴포넌트가 렌더링 될 때마다 실행된다. 그러나 의존성 배열이 비어 있기 때문에 어떤 특정한 상태의 변경을 감지하지 않고 항상 실행된다. 

import React, { useState, useEffect } from 'react';
 
const Info = () => {
  const [name, setName] = useState('');
  const [nickname, setNickname] = useState('');
  useEffect(() => {
    console.log('렌더링이 완료되었습니다!');
    console.log({
      name,
      nickname
    });
  });
 
  const onChangeName = e => {
    setName(e.target.value);
  };
 
  const onChangeNickname = e => {
    setNickname(e.target.value);
  };
 
  return (
    (...)
  );
};
 
export default Info;

 


 
useReducer
useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트해 주고 싶을 때 사용한다. 
 
📌 리듀서
- 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 액션(action) 값을 전달받아 새로운 상태를 반환하는 함수이다.
- 리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜 주어야 한다. 

function reducer(state, action) {
return { ... }; // 불변성을 지키면서 업데이트한 새로운 상태를 반환한다.
}

 
📌 액션 값의 형태 
useReducer에서 사용하는 액션 객체는 반드시 type을 지니고 있을 필요가 없다. 객체가 아니라 문자열이나 숫자여도 상관없다.

{
  type: 'INCREMENT',
  // 다른 값들이 필요하다면 추가로 들어간다.
}

 
 

useReducer를 활용하여 상태 관리를 수행하고, 버튼 클릭에 따라 카운터 값을 증가 또는 감소시켜 본다. 

import React, { useReducer } from 'react';

// Reducer 함수
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

const Counter = () => {
  // useReducer를 사용하여 상태와 디스패치 함수를 얻는다.
  const [state, dispatch] = useReducer(reducer, { count: 0 }); // 인자로 reducer 함수, 초기 상태 전달

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
    </div>
  );
};

export default Counter;

 
 


useMemo
- Memoized value를 리턴하는 Hook이다.
- 함수형 컴포넌트 내부에서 발생하는 연산을 최적화할 때 사용한다. 
- 일반적으로 렌더링이 일어나는 동안 실행돼서는 안 될 작업을 useMemo의 함수에 넣으면 안 된다. (ex. 사이드 이펙트)
 
📌 Memoized value
- 연산량이 많이 드는 함수의 호출 결과를 저장해 두는 값이다.
- 이후 같은 입력 값으로 함수를 호출하면, 새로 함수를 호출하지 않고 이전에 저장해 놨던 호출 결과를 바로 반환할 수 있다. 
 
📌사용법

const memoizedValue = useMemo(
  () => {
    // 연산량이 높은 작업을 수행하여 결과를 반환 
    return computeExpensiveValue(의존성 변수1, 의존성 변수2); 
  }, 
  [의존성 변수1, 의존성 변수2]
); 

 
✔️ 의존성 배열에 들어있는 변수가 변했을 경우에만 새로 creat함수를 호출하여 결과값을 반환한다. 
✔️ 의존성 배열을 넣지 않을 경우, 매 렌더링 때마다 함수가 실행된다. 
✔️ 의존성 배열이 빈 배열일 경우, 컴포넌트 마운트 시에만 함수가 실행된다. 
 
 

useMemo를 이용하여, data 배열이 변경되지 않는 한 불필요한 계산을 피할 수 있게 해 보자.

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

const ExpensiveCalculationComponent = ({ data }) => {
  // 가정: data 배열의 길이를 계산하는데 많은 비용이 드는 작업을 수행한다고 가정
  const calculateLength = (arr) => {
    console.log('Calculating length...');
    return arr.length;
  };

  // useMemo를 사용하여 계산 결과를 메모이제이션
  const length = useMemo(() => calculateLength(data), [data]);

  return (
    <div>
      <p>Data Length: {length}</p>
    </div>
  );
};

const App = () => {
  const [dataArray, setDataArray] = useState([1, 2, 3, 4, 5]);

  return (
    <div>
      <ExpensiveCalculationComponent data={dataArray} />
      <button onClick={() => setDataArray([...dataArray, Math.random()])}>
        Add Data
      </button>
    </div>
  );
};

export default App;

 


 
useCallback
- useCallback은 useMemo와 상당히 비슷한 함수이지만, 값이 아닌 함수를 반환한다. 
- 주로 렌더링 성능을 최적화해야 하는 상황에서 사용한다.
- 이 Hook을 사용하면 이벤트 핸들러 함수를 필요할 때만 생성할 수 있다.
- 특정 변수의 값이 변할 때만 함수를 다시 정의하도록 한다. 
 
📌사용법

const memoizedCallback = useCallback( 
  () => {
    doSomething(의존성 변수1, 의존성 변수2); // 콜백 
  },
  [의존성 변수1, 의존성 변수2] // 변경 시 콜백 함수 반환 
); 

 
따라서 아래 두 줄의 코드는 동일한 기능을 수행한다. 

useCallback(함수, 의존성 배열); 
useMemo(() => 함수, 의존성 배열); 



useCallback을 사용하여 increment 함수를 메모이제이션해 보자. 두 번째 매개변수로 [count]를 전달함으로써, count가 변경될 때만 함수를 다시 생성하게 된다.

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

const CallbackExample = () => {
  const [count, setCount] = useState(0);

  // useCallback을 사용하여 함수를 메모이제이션
  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      {/* 메모이제이션된 함수 사용 */}
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default CallbackExample;

 
 


useRef
useRefReference를 사용하기 위한 Hook이다. 
 
📌 Reference 
- 특정 컴포넌트에 접근할 수 있는 객체이다. useRef는 이 Reference 객체를 반환한다.
- Reference객체에는 current라는 속성이 있는데, 이는 현재 참조하고 있는 엘리먼트를 의미한다.
- useRef를 사용하여 Reference를 설정하면 useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킨다. 

 refObject.current 

 
📌사용법
- 초깃값으로 초기화된 Reference 객체를 반환한다. 
- 만약 초깃값이 null이라면, current의 값이 null인 Reference 객체를 반환한다. 
- 이 객체는 컴포넌트의 mount해제 전까지는 계속 유지된다.

const refContainer = useRef(초깃값); 

 

useRef를 사용하여 inputRef를 생성하고, 이를 통해 input 요소에 참조를 할당해 본다.

import React, { useRef } from 'react';

const RefExample = () => {
  // useRef를 사용하여 input 요소의 참조를 생성
  const inputRef = useRef(null);

  const focusInput = () => {
    // useRef를 사용하여 생성한 참조를 통해 input 요소에 접근
    inputRef.current.focus();
  };

  return (
    <div>
      {/* input 요소에 useRef를 통해 생성한 참조를 할당 */}
      <input type="text" ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
};

export default RefExample;

 


 
커스텀 Hooks 만들기
- 여러 컴포넌트에서 비슷한 기능을 공유할 경우, 이를 자신만의 Hook으로 작성하여 로직을 재사용할 수 있다.
 
📌 Hook의 규칙 
1. 무조건 최상위 레벨에서만 호출해야 한다. 
2. 리액트 함수 컴포넌트에서만 호출해야 한다. 
eslint-plugin-react-hooks 플러그인을 참고하면 규칙을 지키는 데 도움이 된다. 
 
 

간단한 커스텀 토글을 만들어 보자.

import { useState } from 'react';

// 간단한 커스텀 토글 훅
const useToggle = (initialValue = false) => {
  const [value, setValue] = useState(initialValue);
  const toggle = () => setValue((prevValue) => !prevValue);

  return [value, toggle];
};

// 컴포넌트에서 사용 예제
const ToggleComponent = () => {
  const [isToggled, toggle] = useToggle();

  return (
    <div>
      <p>Is Toggled: {isToggled ? 'Yes' : 'No'}</p>
      <button onClick={toggle}>Toggle</button>
    </div>
  );
};

export default ToggleComponent;

 


 
다른 Hooks
이번에 커스텀 Hooks를 만들어서 사용했던 것처럼, 다른 개발자가 만든 Hooks도 라이브러리로 설치하여 사용할 수 있다.

다른 개발자가 만든 다양한 Hooks 리스트는 다음 링크에서 확인할 수 있다.
 https://nikgraf.github.io/react-hooks/
 https://github.com/rehooks/awesome-react-hooks



 


Quiz 

1. 함수형 컴포넌트에서도 가변적인 상태를 지닐 수 있게 해주는 Hook은 ( useState )이다. 
2. useEffect는 (   Side effect  )를 수행하기 위한 Hook이다. 
3.(  리듀서  )는 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 액션(action) 값을 전달받아 새로운 상태를 반환하는 함수를 말한다. 
4. useMemo는 (  Memoized value  )를 리턴하는 Hook이다.
5. useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체 안의 (  current 값  )이 실제 엘리먼트를 가리킨다. 
6. Hook 무조건 (  최상위  ) 레벨에서만 호출해야 한다. 
7. Hook리액트 함수  ) 컴포넌트에서만 호출해야 한다. 


 
<프로그래밍 문제>
1.  'count' 상태 변수를 1씩 증가시키는 버튼을 만드시오. (버튼을 클릭할 때마다 'count' 상태가 업데이트되어 화면에 반영되어야 한다.) 

import React, { useState } from 'react';

function Counter() {
  // 여기에 코드를 작성하세요

  return (
    <div>
      {/* 여기에 버튼과 이벤트 핸들러를 작성하세요 */}
    </div>
  );
}

 
2.  'count' 상태가 변경될 때마다 "Count Updated!"라는 메시지를 콘솔에 출력하는 useEffect를 작성하시오. 

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

function Counter() {
  const [count, setCount] = useState(0);

  // 여기에 코드를 작성하세요

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

 
 
 
 
 


Answers 

1.

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

 
2. 

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

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("Count Updated!");
  }, [count]);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

 
 
 
 
 

출처 : 김민준, 『리액트를 다루는 기술』, 길벗(2019), p309-356.
Corner React.js 3 
Editor:  lyonglyong

 

728x90

관련글 더보기