React Hooks

유송현·2022년 8월 8일
0
post-thumbnail

들어가기전

취업 준비시절 react 프로젝트를 빠르게 도입하고자, 기술문서를 스킵하고 필요한 내용을 그때 그때 찾아서 학습하였다. 그 결과... 항상 쓰던 Hook들만 사용하고 정확히 어떤 역할을 하는지, 어떤 Hook들이 있는지 알지 못하고 그냥 쓰는 느낌이 강하게 들었다. 현재 3개월 가량의 온보딩 과정을 진행중이므로 남는 시간에 공식문서와 구글링을 통해 놓쳤던 개념들을 다시 한번 짚고 가야겠다.

Hook이 나온 배경

  • 이전에는 대부분 react 컴포넌트를 클래스형 컴포넌트로 개발을 진행했다. 하지만 class에 고질적인 이슈인 this 바인딩 때문에 많은 개발자가 클래스형 컴포넌트에 불편함을 호소 하였다. 자바스크립트 this는 다른 언어와 다르게 런타임에 결정되기 때문에 this 바인딩 이슈로인해 종종 에러를 이르킨다.

  • 이러한 문제들을 해결하기 위해 react 14.0버전에 함수형 컴포넌트가 등장하였지만, 라이프 사이클관련 이슈들을 해결하지 못해 사용하지 못하다가 16.8버전에 hooks에 추가와 함께 공식문서에서도 클래스형 컴포넌트보다 함수형 컴포넌트 사용을 권장한다.

Hooks API

Hook 사용 규칙

  • React 함수 컴포넌트에서만 hook을 호출해야 한다.
  • 최상위에서만 Hook을 호출해야 한다.
    - 반복문, 조건문 혹은 중첨된 함수 내에서 Hook을 호출하면 안된다.
    • 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장되기 때문이다.

useState

  • 상태 유지값과 그 값을 갱신하는 함수를 반환합니다.
const [state, setState] = useState(initialState)
setState(newState)

// 함수적 갱신
const [bool, setBool] = useState(true)
setBool((prev) => !prev)
// bool = true
  • 현재 state와 setState에 인자가 동일한 경우 처리를 종료합니다.

useEffect

  • 첫 번째 인자에 콜백함수로 처리할 로직, 두 번째 인자에 배열(deps) [ 상태 ]
    - deps에 값을 입력하지 않으면 mount / unMount시에만 실행됩니다.
  • react 라이프 사이클 mount / update / unMount를 처리할 수 있습니다.
  • 주의할점은 mount update unmount 모두 레이아웃 배치와 그리기를 완료한 후에 발생합니다.
  • 레이아웃 배치와 그리기전에 실행 하려면 useLayoutEffect를 사용해야 합니다.
useEffcet(() => {
	// mount or update 시 처리할 로직
  	return () => {
    	 // unMount 시 처리할 로직
    }
  
}, [의존성 데이터])

const [count, setConut] = useState(0)

useEffect(() => {
	console.log(count) // conut값이 변경될 때마다 콘솔 출력
}, [count])

useContext

  • context는 컴포넌트 트리 안에서 전역적으로 데이터를 공유할 수 있도록 고안된 방법이다.
    - 소프트웨어를 개발하다보면 상태가 공유되는 경우가 발생하는데 props drilling을 발생시키지 않고 데이터를 공유할때 사용
  • createContext : context 객체를 생성
  • Context.Provider : context를 구독하는 컴포넌트들에게 변화를 알리는 역할
  • Consumer : context 변화를 구독하는 React컴포넌트
export const AppContext = createContext()

function App() {

  const [count, _] = useState(50)
  return (
        <AppContext.Provider value={{count}}>
          <Context />
        </AppContext.Provider>
  );
}

export default App;

import React, {useContext} from "react";
import {AppContext} from "./App";
function Context() {
    const {count} = useContext(AppContext)

    return <div>{count}</div>
}

export default Context

useReducer

  • 현재 상태와 action을 인자로 받아서 새로운 상태를 반환하는 Hook
  • useState의 대체 함수로, (state, action) => newState의 형태로 reducer를 받고 dispatch 메서드와 짝의 형태로 현재state를 반환합니다.

    const [state, dispatch] = useReducer(reducer, initialState, init함수 : optional)
    state : 현재상태
    dispatch : action을 발생시키는 함수
    reducer : state와 action을 받아 새로운 State를 반환하는 함수
    initialState : 초기 값

function init(initialCount){
    return {count: initialCount}
}

function reducer(state, action) {
    switch (action.type) {
        case 'increment':
            return {count: state.count + 1};
        case 'decrement':
            return {count: state.count - 1};
        case 'reset':
            return init(action.payload);
        default:
            throw new Error();
    }
}

function Counter() {
    const initialCount = 0

    const [state, dispatch] = useReducer(reducer, initialCount, init);
    return (
        <>
            Count: {state.count}
            <button
                onClick={() => dispatch({ type: 'reset', payload: initialCount })}>
                Reset
            </button>
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
            <button onClick={() => dispatch({type: 'increment'})}>+</button>
        </>
    );
}

export default Counter;

useCallback

  • 함수를 메모이제이션 하기 위해서 사용되는 Hook입니다.
  • 첫 번째 인자로 넘어온 함수를, 두 번째 인자로 넘어온 deps내의 값이 변경될 때까지 저장해 놓고 재상용합니다.
  • 함수에 로직이 비용이 많이드는 로직일때 주로 사용합니다.
const memo = useCallback(doSomething(a,b), [a,b])

useMemo

  • 이전에 계산한 값을 재용하기 위해서 사용되는 Hook입니다.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • useCallback(fn, deps)은 useMemo(() => fn, deps)와 같습니다.

useRef

  • useRef hook은 current 프로퍼티를 가지고 있는 객체를 반환하는데, 인자로 넘어온 초기값을 current 속성에 할당합니다. 이 current 속성은 값을 변경해도 상태를 변경할 때 처럼 재렌더링 되지 않습니다.
  • useRef에 또 다른 사용법은 Dom 노드에 직접 접근할 때 사용합니다.
function App() {
    const countRef = useRef(0)
    
    const increaseRef = () => {
    	countRef.current = countRef.current + 1;
  }
    
  return (
      <div>{countRef}</div>
    	<button onClick={increaseRef}>plus</button>
  );
}
  • plus를 클릭했을때 conutRef가 증가된 값이 렌더 되지는 않지만, 콘솔이나 혹은 컴포넌트를 재렌더링 하게 되면 countRef값이 증가된것을 확인할 수 있다.
function App() {
    const ref = useRef(null)
    useEffect(() => {
        ref.current.focus()
    }, [])
  return (
      <input ref={ref} type={"text"} />
  );
}
// Dom에 접근하여 input focus

useImperativeHandle

useImperativeHandle은 ref를 사용할 때 부모 컴포넌트에 노출되는 인스턴스 값을 사용자화 (customizes)합니다.
useImperativeHandle는 forwardRef와 더불어 사용하세요
useImperativeHandle(ref, createHandle, [deps])

  • forwardRef : ref 자체를 props로 전달하지 못하기 때문에 forwarRef 메소드를 이용해 ref를 props로 전달 할 수 있다.

import React, {forwardRef, useRef} from "react";

function ParentComponent(){

    const inputRef = useRef(null);
    const onClick = () => {
        if(inputRef. current){
            console.log(inputRef.current)
        }
    }
    return (
        <>
            <ChildCompo ref={inputRef} />
            <button onClick={onClick}>확인</button>
        </>
    )
}

export default ParentComponent;
function ChildCompo(props,ref){

    const [value, setValue] = useState(0)
    useImperativeHandle(ref, () => ({
        squared : value => setValue(value + value),
        getValue : () => value,
    }))

    return (
        <>
            <input ref={ref} type={"text"}/>
        </>
    )
}

export default forwardRef(ChildCompo)
  • 위 예시 코드를 보면 ChildCompo에서 useImperativeHandle hook으로 지정한 squared, getValue 메소드들을 ParentComponent서도 접근이 가능합니다.
  • useImperativeHandle를 사용하지 않고 자식 컴포넌트에서 정의한 상태를 부모에게 전달할수 있는 방법은 전역 상태관리로 관리 할수밖에 없지만, 이러한 Case일 경우에는 useImperativeHandle Hook을 사용해 편리하게 사용할수 있습니다.

useLayoutEffect

  • useEffect와 거의 동일한 동작을 하지만, useEffect는 레이아웃을 전부 렌더링 한 후에 실행되지만, useLayoutEffect는 레이아웃을 렌더링 하기 이전에 동기적으로 수행되는 hook이다.
  • 사용 예시는 자동로그인 기능 처럼 특정 조건이 있을 때, 페이지 전환이 일어나는 상황에서 useEffect는 화면을 렌더링하고, 조건을 확인하기 때문에 사용자가 깜빡거리는 화면을 보게되지만 useLayoutEffect는 화면을 그리기 전에 조건을 확인하기 때문에 자연스러운 사용자 경험을 제공할 수 있다.

useDebugValue

  • React 개발자 도구에서 사용자 Hook 레이블을 표시하는데에 사용 할 수 있습니다.
  • 특정 컴포넌트가 어떠한 Hook을 사용했는지 확인할 때 주로 사용합니다.
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // 개발자 도구에서 확인을 위한 라벨 붙이기
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

Custom hook

  • 자신만의 Hook을 만들면 컴포넌트 로직을 함수로 뽑아내어 재사용 할 수 있다.
custom hook rules
- 사용자 정의 hook은 이름이 use로 반드시 시작해야한다. 
- React 함수 컴포넌트에서만 hook을 호출해야 한다.
- 최상위에서만 Hook을 호출해야 한다.
	- 반복문, 조건문 혹은 중첨된 함수 내에서 Hook을 호출하면 안된다.
    - 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장되기 때문이다.
// 온보딩 과정 중 Echart resize control을 위한 element observer hook
import { useEffect, useRef, useState } from "react";

type elementType = {
  current: HTMLDivElement | null;
};

const useElementObserver = (elRef: elementType) => {
  const [size, setSize] = useState(0);
  const observer = useRef(
    new ResizeObserver((entries) => {
      const { width } = entries[0].contentRect;
      console.log(entries);
      setSize(width);
    })
  );

  useEffect(() => {
    if (elRef.current) observer.current.observe(elRef.current);

    return () => {
      if (elRef.current) observer.current.unobserve(elRef.current);
    };
  }, [elRef, observer]);

  return size;
};
export default useElementObserver;
profile
주니어 프론트엔드 개발자

0개의 댓글