[React] Hooks

handa·2024년 1월 9일
0

Hooks


기존 함수 컴포넌트는 클래스 컴포넌트와는 다르게 코드도 굉장히 간결하고 별도로 스테이트를 정의해서 사용하거나 컴포넌트의 생명주기에 맞춰 어떤 코드가 실행되도록 할 수 없었다. 따라서 함수 컴포넌트에 이런 기능을 지원하기 위한것이 Hook이다.

1. useState()

import React, { useState } from "react"

function Counter(props) {
    var count = 0;

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

위 코드는 버튼을 클릭했을때 카운트를 증가시키고 현재 카운트를 보여주는 컴포넌트다.
하지만 카운트를 함수의 변수로 사용하여 버튼 클릭 시 카운트 값을 증가시킬 수는 있지만 재랜더링이 일어나지 않아 새로운 카운트 값이 화면에 표시되지 않는다.

import React, { useState } from "react"

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

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

useState Hook을 사용하여 카운트 값을 state로 관리하도록 만듬

2. useEffect()

Side Effect를 수행하기 위한 Hook
React에서의 Side Effect는 다른 컴포넌트에 영향을 미칠 수 있으며, 렌더링 중에는 작업이 완료될 수 없기 때문

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>
    );
}
//두개의 useEffectHook을 사용하는 예제
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);
        // componentWillUnmount 함수와 동일하게 작동컴포넌트가 unmount 될 때 호출됨
        return () => {
            ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
        };
    });

    if (isOnline === null) {
        return '대기 중...';
    }
    return isOnline ? '온라인' : '오프라인';
}
```javascript
import React, { useState, useEffect } from "react"

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

3. useMemo()

Memoized value를 리턴하는 Hook
연산량이 많이 드는 함수의 호출 결과를 저장해 두었다가 같은 입력 값으로 함수를 호출하면 새로 함수를 호출하지 않고 이전에 저장해 놨던 호출 결과를 바로 반환하는 것이 Memoization이고 Memoization된 결과 값을 Memoized value라고 한다.

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

의존성 배열이 빈 배열일 경우, 컴포넌트 마운트 시에만 호출 됨

const memoizedValue = useMemo(
	() => {
    	return computeExpensiveValue(a, b);  
    },
  	[]
);

4. useCallback()

useMemo() Hook과 유사하지만 값이 아닌 함수를 반환

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

동일한 역할을 하는 두 줄의 코드

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

4-1. useCallback() 예제

업로드중..
업로드중..

5. useRef()

Reference를 사용하기 위한 Hook
React에서 Reference는 특정 컴포넌트에 접근할 수 있는 객체

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

5-1Callback ref

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.rount(height)}px 입니다. </h2>
        </>
    );
}

6. Hook의 규칙

  1. Hook은 무조건 최상위 레벨에서만 호출해야 한다.
    Hook은 컴포넌트가 렌더링될 때마다 매번 같은 순서로 호출되어야 한다.

잘못된 Hook 사용법

function MyComponent(props) {
 	const [name, setName] = useState('Inje');
  	if (name !== '') {
     	useEffect(() => {
          	...
        });
    }
    ...
}

조건문에 따라 호출되는 Hook이 달라짐으로 잘못된 코드

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

7. Custom Hook

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

function UserStatus(props) {
 	const [isOnline, setIsOnline] = useState(null); 
  
  	useEffect(() => {
     	function handleStatusChange(status) {
         	setIsOnline(status.isOnline); 
        }
      	
      	ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
      	return () => {
         	ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange); 
        };
    });
  
  	return (
      	<li style={{ color: isOnline ? 'green' : 'black' }}>
      		{props.user.name}
		</li>
	);
}

Custom Hook 추출하기
이름이 use로 시작하고 내부에서 다른 Hook을 호출하는 하나의 자바스크립트 함수

import { useState, useEffect } from "react";
function useUserStatus(userId) {
	const [isOnline, setIsOnline] = useState(null);
  	useEffect(() => {
    	function handleStatusChange(status) {
        	setIsOnline(status.isOnline);
        }
      	ServerAPI.subscribeUserStatus(userId, handleStatusChange);
      	return () => {
        	ServerAPI.unsubscribeUserStatus(userId, handleStatusChange);
        };
    });
  	return isOnline;
}

Custom Hook 사용하기

function UserStatus(props) {
	const isOnline = 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>
  );
}

Custom Hook의 이름은 꼭 use로 시작해야 한다.
여러 개의 컴포넌트에서 하나의 Custom Hook을 사용할 때 컴포넌트 내부에 있는 모든 state와 effects는 전부 분리되어 있다. 각 Custom Hook 호출에 대해서 분리된 state를 얻게 된다.

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

const userList = [
  { id: 1, name: 'Inje' },
  { id: 2, name: 'Mike' },
  { id: 3, name: 'Steve' },
];

function ChatUserSelector(props) {
 	const [userId, setUserId] = useState(1);
  	const isUserOnline = useUserStatus(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>
      	</>
    );
}
profile
진짜 해보자

0개의 댓글