FrontEnd/React

Hooks

Hook이란?

- 리액트 버전 16.8에서 생긴 개념

- 최근 리액트 개발은 대부분 hook을 사용

Function Component Class Component
state 사용 불가 생성자에서 state를 정의
Lifecycle에 따른 기능 구현 불가 setState() 함수를 통해 state 업데이트
Hooks Lifecycle methods 제공

 

원하는 시점에 정해진 함수를 호출하기 위함

 

대표적인 Hook

useState()

state를 사용하기 위한 Hook

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

//useState()함수 필요한 예제
import React, { useState } from "react";

function Counter(props) {
    var count = 0;

    // count값은 변화하나 재렌더링이 일어나지 않아 count값이 화면에 표시되지 않음
    // state를 사용해서 값이 변화 시 재렌더링이 발생할 수 있도록 useState 사용필요
    return (
        <div>
            <p>총 {count}번 클릭했습니다.</p>
            <button onClick={() => count++}>
                클릭
            </button>
        </div>
    )
}

import React, { useState } from "react";

function Counter(props) {
	// useState함수 정의
    const [count, setCount] = useState(0);

	// setCount로 클릭 시 state값 변화
    // 렌더링 발생하여 화면 갱신
    return (
        <div>
            <p>총 {count}번 클릭했습니다.</p>
            <button onClick={() => setCount(count + 1)}>
                클릭
            </button>
        </div>
    )
}

 

useEffect()

Side effect를 수행하기 위한 Hook

다른 컴포넌트에 영향을 미칠 수 있으며, 렌더링 중에는 작업이 완료될 수 없기 때문

ex) 서버에서 데이터를 받아오거나 수동으로 DOM을 변경하는 등의 작업

 

// useEffect() 사용법
useEffect(이펙트 함수, 의존성 배열);

// Effect function이 mount, unmount 시에 단 한 번씩만 실행 됨
useEffect(이펙트 함수, []);

//의존성 배열 생략하면 컴포넌트가 업데이트 될 때마다 호출 됨
useEffect(이펙트 함수);

//useEffect 예제 코드
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`;
    });

    return (
        <div>
            <p>총 {count}번 클릭했습니다.</p>
            <button onClick={() => setCount(count + 1)}>
                클릭
            </button>
        </div>
    )
}

// useEffect() ComponentWillUnmount 기능

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

function UserStatus(props) {
    const [isOnline, setIsOnline] = useState(null);

    function handleStatusChange(status) {
        setIsOnline(status.isOnline);
    }

    useEffect(() => {
        ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
        return () => { // 컴포넌트가 unmount 될 때 호출됨
            ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
        };
    });

    if (isOnline === null) {
        return '대기중 ...';
    }

    return isOnline ? '온라인' : '오프라인';
}

 

useEffect 정리

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

 


memoization

높은 비용의 작업의 결과값을 저장해두었다가 같은 입력값으로 함수를 호출 시
해당 결과(memoized value)를 반환하는 기법

useMemo

memoized value 반환하는 useMemo

// useMemo() 사용법
// useMemo로 전달된 함수는 렌더링이 일어나는 동안 실행
// 렌더링 진행 중 실행돼서는 안될 작업을 하면 함수에 넣으면 안됨
// ex) useEffectHook에서 실행돼야 할 사이드 이펙트
// 서버에서 데이터 받아오거나 수동으로 DOM 변경 작업
const memoizedValue = useMemo(
    () => {
        // 연산량이 높은 작업을 수행하며 결과를 반환
        return computeExpensiveValue(의존성 변수1, 의존성 변수2);
    },
    [의존성 변수1, 의존성 변수2]
);

// 의존성 배열을 넣지 않을 경우, 매 렌더링마다 함수가 실행 됨
const memoizedValue = useMemo (
    () => computeExpensiveValue(a, b)
);

// 의존성 배열이 빈 배열일 경우, 컴포넌트 마운트 시에만 호출 됨
const memoizedValue = useMemo (
    () => {
        computeExpensiveValue(a, b);
    },
    []
);

useCallback

memoized value가 아닌 함수를 반환하는 useCallback

 

// useCallback() 사용법
const memoizedCallback = useCallback(
    () => {
        doSomething(의존성 변수1, 의존성 변수2);
    },
    [의존성 변수1, 의존성 변수2]
);

/*
동일한 역할
useCallback(함수, 의존성 배열);
useMemo(() => 함수, 의존성 배열);
*/

// ======================================

import { useState } from "react";

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

    // 재렌더링 될 때마다 매번 함수가 새로 정의됨
    // 자식 컴포넌트까지 재 렌더링 됨
    const handleClick = (event) => {
        //클릭 이벤트 처리
    };
    
    // 컴포넌트가 마운트 될 때만 함수가 정의됨
    // 자식 컴포넌트의 불필요한 재 렌더링 X
    const handleClick = useCallback((event) => {
        //클릭 이벤트 처리
    }, []);

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

            <ChildComponent handleClick={handleClick} />
        </div>
    );
}

 

useRef()

Reference를 사용하기 위한 Hook

Reference : 특정 컴포넌트에 접근할 수 있는 객체

refObject.current => current : 현재 참조하고 있는 Element

 

// useRef() 사용법
// 컴포넌트의 마운트 해제 전 까지 유지됨
// useRefHook : 변경 가능한 current라는 속성을 가진 하나의 상자
const refContainer = useRef(초기값);

function TextInputWithFocusButton(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은 내부의 데이터가 변경되었을 때 별도로 알리지 않는다.

내부 변화를 알기 위해서는 Callback ref를 사용

function MeasureExample(props) {
    const [height, setHeight] = useState(0);

    const measureRef = useCallback(node =>  {
        if(node !== null) {
            setHeight(node.getBoundingClientRect().height);
        }
    }, []);

    return (
        <>
            <h1 ref={measureRef}>안녕, 리액트</h1>
            <h2>위 헤더의 높이는 {Math.round(height)}px 입니다.</h2>
        </>
    );
}

Hook의 규칙

1. Hook은 무조건 최상위 레벨에서만 호출해야 한다.

반복문이나 조건문 또는 중첩된 함수들 안에서 Hook을 호출하면 안된다.

Hook은 컴포넌트가 렌더링될 때마다 매번 같은 순서로 호출되어야 한다.

그래야 React가 다수의 useState hook과 useEffect hook에 호출해서 컴포넌트의 state를 올바르게 관리가 가능하다.

function MyComponent(props) {
    const [name, setName] = useState('devjun');

// 잘못된 Hook 사용법 (조건문 내부)
    if(name !== '') {
        useEffect(() => {
            ...
        });
    }
    ...
}

 

2. 리액트 함수 컴포넌트에서만 Hook을 호출해야 한다.

 

Custom Hook 만들기

반복적으로 사용되는 로직의 재사용을 위함

 

예제 코드

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

// 사용자의 상태에 따라 온라인, 오프라인을 텍스트로 나타내는 UserStatus 컴포넌트
function UserStatus(props){
	const [isOnline, setIsOnline] = useState(null);
  
    useEffect(()=>{
      const handleStatusChange = (status) => {
    	setIsOnline(status.isOnline)
    	}
    	ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
      return()=>{
      	ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
      };
    });
  
  if (isOnline === null){
  	return '대기중....';
  }
  
  return isOnline ? '온라인' : '오프라인';
  // 온라인 유저 이름을 녹색으로 표시 (중복)
  /*
  return(
	<li style={{color: isOnline? 'green' : 'black'}}>
  		{props.user.name}
	</li>
 )
 /*
}

 

 

Custom Hook 추출

import {useState, useEffect} from "react";

// use로 시작
// Hook의 두 가지 규칙을 지켜야함
function useUserStatus(userId){
	const [isOnline, setIsOnline] = useState(null);
  
    
    useEffect(()=>{
      const handleStatusChange = (status) => {
    	setIsOnline(status.isOnline)
    	}
    	ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
      return()=>{
      	ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
      };
    });
  
  return isOnline;
}

 

 

Custom Hook 사용

// use로 시작 필수
function UserStatus(props){
	const inOnline = useUserStatus(props.user.id);
  
  	if (isOnline === null) {
    	return '대기 중...';
    }
  return isOnline ? "온라인" : "오프라인";
}

function UserListItem(props){
	const isOnline = useUserStatus(props.user.id);
  
 return(
		<li style={{color: isOnline? 'green' : 'black'}}>
  			{props.user.name}
		</li>
	);
}

 

 

같은 커스텀 훅을 사용하는 컴포넌트는 state를 공유하는가?

아니다. 커스텀 훅은 단순히 state와 연관된 로직을 재사용이 가능하게 만든 것

다수 컴포넌트에서 하나의 커스텀 훅 사용시 컴포넌트 내부의 모든 state와 effects는 전부 분리되어 있다.

각각의 커스텀 훅 호출에 분리된 state를 얻게 됨

 

Hook들 사이에서 데이터를 공유하는 방법

 

const userList = [
  {id: 1, name: 'Joah'},
  {id: 2, name: 'Mike'},
  {id: 3, name: 'Dustin'}
]

function ChatUserSelector(props){
	const [userId, setUserId] = useState(1); // userId state 생성
  	const isUserOnline = useUserStatus(userId);
    // userId를 매개로 변경 시 새로 선택된 사용자의 온라인 여부 구독
  return (
  	<>
    	<Circle color={isUserOnline? 'green' : 'red'}/>
		<select
			value={userId}
			onChange={event => setUserId(Number(event.target.value))}
		>
          {userList.map(user=> (
           	<option key={user.id} value={user.id}>
              {user.name}
			</option>
           ))}
    	</select>
    </>
  );
}

Referenced By

인프런 - 처음 만난 리액트(React)

 

[지금 무료] 처음 만난 리액트(React) | Inje Lee (소플) - 인프런

Inje Lee (소플) | 자바스크립트와 CSS 기초 문법과 함께 리액트의 기초를 탄탄하게 다질 수 있습니다., 깔끔한 강의자료, 꼼꼼한 설명으로쉽게 배우는 리액트 강의입니다. 👨‍🏫 리액트의 세계로

www.inflearn.com

 

https://ko.legacy.reactjs.org/docs/hooks-intro.html

 

Hook의 개요 – React

A JavaScript library for building user interfaces

ko.legacy.reactjs.org

https://react.vlpt.us/basic/07-useState.html

 

7. useState 를 통해 컴포넌트에서 바뀌는 값 관리하기 · GitBook

7. useState 를 통해 컴포넌트에서 바뀌는 값 관리하기 지금까지 우리가 리액트 컴포넌트를 만들 때는, 동적인 부분이 하나도 없었습니다. 값이 바뀌는 일이 없었죠. 이번에는 컴포넌트에서 보여줘

react.vlpt.us

https://www.npmjs.com/package/eslint-plugin-react-hooks

 

eslint-plugin-react-hooks

ESLint rules for React Hooks. Latest version: 4.6.0, last published: 2 years ago. Start using eslint-plugin-react-hooks in your project by running `npm i eslint-plugin-react-hooks`. There are 9223 other projects in the npm registry using eslint-plugin-reac

www.npmjs.com

 

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

Conditional Rendering  (0) 2024.04.19
Handling Events  (0) 2024.04.17
State and Lifecycle  (0) 2024.04.08
Components and Props  (1) 2024.04.05
Rendering Elements  (1) 2024.04.04