본문 바로가기
[FrontEnd]/React

React - Hooks

by 황원용 2023. 6. 2.
728x90
Function
Component
Class
Component
state 사용 불가 생성자에서 state를 정의
Lifecycle에 따른 기능 구현 불가 setState() 함수를 통해 state 업데이트
Hooks을 통해 클래스 Component의 기능을 모두 구현 Lifecycle methods 제공

 

Hook

  • 리액트 클래스 Component에서만 가능했던 기능을 따로 작성할 필요 없이 함수 Component에서 사용할 수 있게 도와주는 라이브러리

 

 

Hook의 규칙

  • Hook은 무조건 리액트 함수 컴포넌트의 최상위 레벨에서만 호출해야 한다.
    • 따라서 반복문이나 조건문, 중첩된 함수 안에서 Hook을 호출해서는 안 된다.
    • Hook은 컴포넌트가 렌더링 될 때마다 매번 같은 순서로 호출되어야 한다.
    • 이렇게 해야 리액트가 다수의 useState()와 useEffect() Hook을 호출해서 컴포넌트의 state를 올바르게 관리할 수 있다.
  • 리액트 함수 컴포넌트에서만 Hook을 호출해야 한다.
    • 그렇기 때문에 일반적인 자바스크립트 함수에서 Hook을 호출해서는 안 된다.
  • eslint-plugin-react-hooks라는 Hook의 규칙을 따르도록 강제해 주는 패키지가 있다. 

 

useState() Hook

  • 함수 Component에서는 기본적으로 state를 제공하지 않는데 useState() Hook을 이용하면 state를 사용할 수 있다.

 

import React, { useState } from "react";

function Counter(props) {
	var count = 0;
    
    return (
    	<div>
        	<p>총 {count}번 클릭했습니다.</p>
            <button on Click={() => count++}>
            클릭
            </button>
        </div>
    );
}
  • 위의 코드는 Counter()라는 함수 Component이다.
  • Counter() 함수는 버튼을 클릭하면 count 값이 1씩 오르고 현재 count 값을 화면에 출력한다.
  • 그런데 위의 코드처럼 count를 함수의 변수로 선언하게 되면 버튼 클릭 시 count 값을 증가시킬 수는 있지만 리렌더링이 일어나지 않아 새로운 count 값이 화면에 출력되지 않는다.
  • 따라서 state를 사용해 count 값이 바뀔 때마다 리렌더링이 일어나도록 해야 한다.
  • 함수 Component에는 state 기능이 없기 때문에 useState()를 사용하여 state를 선언하고 업데이트해야 한다.

 

useState() 사용법

const [변수명, set함수명] = useState(초기값);
  • 초기값을 넣어 useState를 호출하면 리턴값으로 배열이 나온다.
  • 배열 안에는 두 가지 항목이 들어가 있다.
  • 첫 번째 항목은 state로 선언된 변수이고, 두 번째 항목은 해당 state의 set 함수입니다.

 

예시

import React, { useState } from "react";

function Counter(props) {
	const [count, setCount] = useState(0); // 변수 각각에 대해 set 함수가 따로 존재한다.
    
    return (
    	<div>
        	<p>총 {count}번 클릭했습니다.</p>
            <button on Click={() => setCount(count + 1)}>
            클릭
            </button>
        </div>
    );
}
  • 위의 코드는 useState를 사용하여 count 값을 state로 관리하도록 만든 것이다.
  • 버튼이 눌리면 setCount 함수를 호출하여 count에 1을 더한다.
  • count 값이 변경되면 Component가 리렌더링 되면서 화면에 새로운 count 값이 표시된다.
  • 클래스 Component에서는 setState함수 하나를 이용해 모든 state 값을 업데이트할 수 있지만 함수 Component의 경우에는 변수 각각에 대해 set 함수가 따로 존재한다.

 

 

useEffect() Hook

  • 리액트의 함수 컴포넌트에서 Side effect를 실행할 수 있게 해주는 Hook이다.
  • 리액트에서 Side effect는 효과, 영향이라는 뜻에 가깝다.
  • 예를 들면 서버에서 데이터를 받아오거나, 수동으로 DOM을 변경하는 경우를 effect라고 말한다.
  • effect라고 부르는 이유는 다른 컴포넌트에 영향을 끼칠 수 있으며, 렌더링 중에는 작업이 완료될 수 없기 때문이다. 렌더링이 끝난 이후에 수행되는 작업이다.
  • 이러한 작업들은 Main이 Side에서 실행된다는 의미에서 Side effect라고 한다.
  • 클래스 Component에서 제공하는 생명주기 함수인 componentDidMount(), componentDidUpdate(), componentWillUnmount()의 기능을 하나로 통합하여 제공한다.

 

useEffect() 사용법

// 컴포넌트가 마운트 된 이후

useEffect(이펙트 함수, 의존성 배열);
// 의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행됨

useEffect(이펙트 함수, []);
// 의존성 베열에 빈 배열을 넣으면 mount와 unmount 시에 단 한 번씩만 실행됨

useEffect(이펙트 함수);
// 의존성 배열 생략 시 컴포넌트 업데이트 마다 실행됨

...

return () => {
	// 컴포넌트가 마운트 해제되기 전에 실행됨
	...
}
  • 의존성 배열이란 useEffect 함수가 의존하는 배열로, 배열 안에 있는 변수 중에 하나라도 값이 변경이 되면 이펙트 함수가 실행된다.
  • 기본적으로 이펙트 함수는 Component가 처음 렌더링 된 이후와 업데이트로 인한 리렌더링 이후에 실행된다.
  • 만약 이펙트 함수가 mount와 unmount 시에 단 한 번씩만 실행되게 하고 싶다면 useEffect(이펙트 함수, []) 이런 식으로 빈 배열을 넣으면 된다.
  • 이렇게 하면 해당 이펙트가 props나 state에 있는 어떤 값에도 의존하지 않는 것이 되므로 여러 번 실행되지 않는다.
  • 의존성 배열을 생략하면 Component가 업데이트될 때마다 호출된다.

 

예시

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

function Counter(props) {
	const [count, setCount] = useState(0); 
    
    // componentDidMount, componentDidUpdate와 비슷하게 작동한다.
    useEffect(() => {
    	// 브라우저 API를 사용해서 document의 title을 업데이트한다.
    	document.title = 'you clicked ${count} times'; // count state에 접근하여 해당 값을 사용
    });
    
    return (
    	<div>
        	<p>총 {count}번 클릭했습니다.</p>
            <button on Click={() => setCount(count + 1)}>
            클릭
            </button>
        </div>
    );
}
  • 위 코드처럼  의존성 배열 없이 useEffect를 사용하면 리액트는 DOM이 변경된 이후에 해당 effect 함수를 실행하라는 의미로 받아들인다.
  • 그래서 기본적으로 Component가 처음 렌더링될 때를 포함해서 매 렌더링 시에 이펙트 함수가 호출된다.
  • 위 코드의 경우 처음 mount 되었을 때 실행되고, 그다음부터는 컴포넌트가 업데이트될 때마다 실행된다.
  • 또한 effect는 함수 Component 안에서 선언되기 때문에 해당 Component의 props와 state에 접근할 수 있다.
  • 위 코드에서는 count라는 state에 접근하여 해당 값이 포함된 문자열을 생성해서 사용한다.

 

함수 Component에서의 componentWillUnmount() 구현

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

function UseStatus(props) {
	const [isOnline, setIsOnline] = useState(null);
    
    function handleStatusChange(status) {
    	setIsOnline(status.isOnline);
    }
    
    useEffect(() => {
    	ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
        return () => {
        	ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
        };
    });
    
    if (isOnline === null) {
    	return '대기중...';
    }
    return isOnlinde ? '온라인' : '오프라인';
}
  • 위 코드는 서버 API를 사용하여 사용자의 상태를 구독하고 있다.
  • useEffect 함수는 Component가 unmount 되면 return 함수를 호출한다.
  • Component가 unmount 되면 구독을 해제하는 코드가 리턴에 들어가 있다.
  • 결과적으로 useEffect() 함수의 리턴문이 하는 역할은  클래스 Component에서의 componentWillUnmount() 함수가 하는 역할과 동일하다.

 

function UserStatusWithCounter(props) {
	const [sount, setCount] = useState(0);
    useEffect(() => {
    	document.title = '총 ${count}번 클릭했습니다.';
    });
    
   const [isOnline, setIsOnline] = useState(null);
   useEffect(() => {
        ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
        return () => {
			ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
        };
   });
   
   function handleStatusChange(status) {
   		setIsOnline(status.isOnline);
   }
}
  • 또한 useState(), useEffect() Hook은 하나의 Component 안에 여러 개를 사용할 수 있다.
  • 위 코드는 하나의 Component 안에 두 개의 useState()와 useEffect() Hook이 들어가 있다.

 

 

useMemo() Hook

  • Memoized value를 리턴하는 Hook이다.

 

Memoization

  • 컴퓨터 분야에서 최적화를 위해 사용하는 개념이다.
  • 연산량이 많은(비용이 높은) 함수의 호출 결과를 저장해 두었다가 같은 입력값으로 함수를 호출하면 다시 연산하지 않고 이전에 저장한 호출 결과를 반환하는 것을 말한다.
  • Memoized value란 이렇게 저장된 호출 결괏값을 의미한다.

 

const memoizedValue = useMemo(
	() => {
    	// 연산량이 높은 작업을 수행하여 결과를 반환한다.
        return computeExpensiceValue(의존성 변수1, 의존성 변수2);
    },
    [의존성 변수1, 의존성 변수2]
);
  • useMemo() Hook은 파라미터로 memoized value를 생성하는 create 함수와 의존성 배열을 받는다. 
  • Memoization 개념처럼 의존성 배열에 들어있는 변수가 변했을 경우에만 새로운 create 함수를 호출하여 결괏값을 반환하며, 그렇지 않으면 기존 함수의 결괏값을 그대로 반환한다. 
  • useMemo() Hook을 사용하면 컴포넌트가 리렌더링 될 때마다 연산량이 많은 작업을 반복하는 것을 피할 수 있다.

 

주의할 점

  • useMemo()로 전달된 함수는 렌더링이 일어나는 동안 실행된다. 그렇기 때문에 렌더링이 일어나는 동안 실행되서는 안 될 작업을 useMemo() 함수에 넣으면 안 된다.
    • useEffect() Hook에서 실행되어야 할 사이드 이펙트를 예로 들 수 있다.
    • 서버에서 데이터를 받아오거나, 수동으로 DOM을 변경하는 작업은 렌더링이 일어나는 동안 실행되어서는 안 되기 때문에 useMemo()가 아닌 useEffect() Hook을 사용해야 한다.

 

const memoizedValue = useMemo(
	() => computeExpensiceValue(의존성 변수1, 의존성 변수2);
);
  • 의존성 배열을 넣지 않을 경우 매 렌더링마다 함수가 실행된다. 따라서 useMemoHook()에 의존성 배열을 넣지 않고 실행하는 것은 아무런 의미가 없다.

 

const memoizedValue = useMemo(
	() => {
        return computeExpensiceValue(a, b);
    },
    []
);
  • 의존성 배열이 빈 배열일 경우에는 컴포넌트 마운트시에만 creat 함수가 호출된다. 마운트 이후에는 값이 변경되지 않기 때문에 마운트 시에만 값을 계산할 필요가 있을 경우에 사용하면 된다.
  • 사용하는 경우는 드물다.

 

 

useCallback() Hook

  • useMemo() Hook과 유사하지만 값이 아닌 함수를 반환한다는 차이점이 있다.
  • 컴포넌트가 렌더링될 때마다 매번 함수를 정의하는 것이 아니라 의존성 배열이 바뀐 경우에만 함수를 재정의하고 리턴한다.

 

const memoizedcallBack = usecallBack(
	() => {
        doSmoething(의존성 변수1, 의존성 변수2);
    },
    [의존성 변수1, 의존성 변수2]
);

 

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

useMemo(() => 함수, 의존성 배열);
  • 두 코드는 동일한 역할을 한다.
    • 의존성 배열이 바뀐 경우에만 함수를 재정의한다.

 

 

useRef()

  • Reference를 사용하기 위한 Hook이다.
  • Reference 객체를 반환한다.

 

Reference

  • 특정 컴포넌트에 접근할 수 있는 객체를 의미한다. 

 

refObject.current 

  • reference에는 current라는 속성이 있는데 현재 참조하고 있는 Element를 의미한다.

 

const refContainer = useRef(초기값);
  • 초기값을 넣으면 해당 초기값으로 초기화된 reference를 반환한다.
  • 만약 초기값이 null이라면 current의 값이 null인 reference 객체가 반환된다.
  • 이렇게 반환된 객체는 컴포넌트의 라이프 사이클 전체에 걸쳐 유지된다.
  • useRef() Hook은 변경 가능한 current라는 속성을 가진 하나의 상자라고 생각하면 된다.

 

function TextIputWithFocusButton(props) {
	const inputElem = useRef(null);
 
	const onButtonClick = () => {
    // 'current'는 마운트된 input element를 카리킨다.
    inputElem.current.focus();
    };
    
    return (
    	<>
        	<input ref={inputElem} type="text" />
            <button onClick={onButtonClick}>
            	Focus the input
            </button>
        </>
    );
}
  • 이 코드는 UseRef() Hook을 사용하여 버튼 클릭 시 input에 focus를 하도록 하는 코드이다.
  • 초기값은 null을 넣었고, 결과로 반환된 inputElem이라는 reference 객체를 input 태그 안에 넣는다.
  • 버튼 클릭 시 호출되는 함수에서 inputElem.current를 통해 실제 element에 접근하여 focus 함수를 호출한다.

 

<div ref={myRef}>
  • 리액트에서는 위와 같이 코드를 작성하면 노드가 변경될 때마다 myRef의 current 속성에 현재에 해당되는 DOM node를 저장한다.
  • ref 속성과 기능은 비슷하지만 useRef() Hook은 클래스의 인스턴스 필드를 사용하는 것과 유사하게 다양한 변수를 저장할 수 있다는 장점이 있다.
  • 이것이 가능한 이유는 useRef() Hook이 일반적인 자바스크립트 객체를 리턴하기 때문이다.
  • 그렇다면 개발자가 직접 current 속성이 포함된 자바스크립트 객체를 만들어 사용해도 되는 것 아닌가 하는 생각이 들 수 있다.
  • useRef() Hook을 사용하는 것과 직접 current 속성이 포함된 모양의 객체를 만들어 사용하는 것의 차이점은 useRef() Hook은 매번 렌더링 될 때마다 항상 같은 reference 객체를 반환한다는 것이다. 

 

 

Callback ref

  • useRef() Hook은 내부의 데이터가 변경되었을 때 따로 알리지 않기 때문에 current 속성을 변경한다고 해서 리렌더링이 일어나지 않는다.
  • 따라서 ref에 DOM node가 연결되거나 분리되었을 경우에 어떤 코드를 실행하고 싶다면 callback ref를 사용해야 한다.
  • DOM node의 변화를 알기 위한 가장 기초적인 방법은 callback ref를 사용하는 것이다.
  • 리액트는 다른 노드에 연결될 때마다 callback을 호출한다.

 

function MeasureExample(props) {
	const [height, setHeight] = useState(0);
    
    const measureRef = useCallback(node => {
    	if (node != null) {
        	setHeight(node.getBoundingClientRect().height);
        }
    }, []);
    
    return (
    	<>
        	<h1 ref ={measuredRef}>안녕, 리액트</h1>
            <h2>위 헤더의 높이는 {Math.round(height)}px 입니다.</h2>
        </>
    );
}
  • 이 코드는 reference를 위해서 useRef() Hook을 사용하지 않고 useCallback() Hook을 사용하는 callback ref 방식을 사용했다. useRef() Hook을 사용하게 되면 reference 객체가 current 속성이 변경되었는지를 따로 알리지 않기 때문이다.
  • callback ref 방식을 사용하게 되면 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있고 이를 통해 다른 정보를 업데이트할 수 있다.
  • 이 예제 코드에서는 h1 태그의 높이값을 매번 업데이트하고 있다.
  • useCallback() Hook의 의존성 배열로 비어있는 배열을 넣었는데 이렇게 하면 h1 태그가 마운트, 언마운트 될 때에만 callback 함수가 호출되며 리렌더링이 일어날 때에는 호출되지 않는다.

 

 

 

참고

인프런 - 처음 만난 리액트

728x90

'[FrontEnd] > React' 카테고리의 다른 글

React - Handling Events  (0) 2023.06.17
React - Custom Hook 만들기  (0) 2023.06.06
React - State & Lifecycle  (0) 2023.06.02
React - Components & Props  (0) 2023.06.02
React - Rendering Elements  (0) 2023.06.01