Web/React

React Hooks

React의 함수형 컴포넌트에서 사용하지 못했던 상태 관리, Lifecycle Method 등 여러 기능을 대체하기 위해 등장한 개념. React 16.8 버전에서 추가되었다.

 

클래스형 컴포넌트의 단점

  • 번들링 시 코드 압축이 잘 되지 않는다.
  • 컴파일 단계에서 코드를 최적화하기 어렵게 만든다.
  • 서로 연관성이 없는 여러 로직을 하나의 생명 주기 메서드에 작성하는 경우가 많다.
  • Hot Reload를 적용한 상황에서 개발자가 개발 시 디버깅하기 힘든 버그를 발생시키는 경우가 있다.
  • 재사용 가능한 로직을 만들 때 고차 컴포넌트를 사용하거나 렌더 속성값 패턴을 사용함으로써 React Element Tree가 깊어지면서 성능에 부정적 영향을 끼친다.

 

Hook의 장점

  • 앞서 말한 클래스형 컴포넌트의 단점이 해결한다.
  • 비슷한 로직을 한 곳으로 모을 수 있어서 가독성이 좋다.
  • 단순한 함수이므로 정적 타입 언어로 타입을 정의하기 쉽다.
  • React에서 제공하는 기본 Hook과 개발자가 직접 만든 custom hook을 섞어서 쉽게 custom hook을 만들 수 있다.

 

Hook의 규칙

  1. (필수) 무조건 컴포넌트 최상위 레벨에서만 호출되어야 한다.
  2. (필수) 절대 조건문, 반복문 내에서 사용하면 안 된다.
  3. (필수) 하나의 컴포넌트에서 Hook을 호출하는 순서는 항상 같아야 한다.
  4. (필수) 함수형 컴포넌트와 custom hook 안에서만 호출되어야 한다.
  5. (권고) 네이밍은 use로 시작되어야 하고, 네이밍에 camel case를 적용한다. (linter가 react hook인지 판별하는 기준)

 

Hook의 종류

useState

컴포넌트의 상태(state)를 관리하는 hook. 클래스형 컴포넌트의 멤버 변수를 대체하는 효과를 가지고 있다.

// 기본 사용법
const [state, setState] = useState(initialValue);
// useState를 사용함으로 인해 컴포넌트가 리렌더링되더라도 이전 state가 변경되지 않는다.
// 클래스형 컴포넌트의 클래스 프로퍼티(멤버 변수)를 대체하는 효과

// 여러 상태를 한 번에 관리하기
const [stateObject, setStateObject] = useState({
  firstdata: 1,
  seconddata: 2,
  thirddata: 3
});

// **중요** 상태를 변경할 때는 상태의 불변성을 유지해야 한다.
// 객체 불변성 유지하기
const newStateObject = {
  ...stateObject,
  thirddata: 4,
};
setStateObject(newStateObject);

// 배열 불변성 유지하기
const newStateList = stateList.filter(element => element !== targetValue);
setStateList(newStateList);
  • useState를 통해 상태를 변경할 때, 해당 상태값이 객체거나 배열일 경우에는 불변성을 꼭 유지해야 한다. 불변성을 지켜주어야만 리액트 컴포넌트에서 상태가 업데이트가 됐음을 감지 할 수 있고 이에 따라 필요한 리렌더링이 진행된다. 상태값을 직접적으로 변경하면 React가 리렌더링을 하지 않는다.

 

useEffect

클래스형 컴포넌트에서 사용하던 React Lifecycle Method를 대체하는 hook

useEffect(() => {
// ComponentDidMount, ComponentDidUpdate
// 마운트와 업데이트 모두에서 useEffect()가 실행되는 이유: 중간에 prop이나 state가 바뀔 경우를 대비

// cleanUp
  return () => {
    // ComponentWillUnmount
  }
}, deps);
  • deps는 의존값이 들어있는 배열로, deps에 있는 값이 변경되지 않으면 useEffect 내부의 로직이 실행되지 않는다. 이를 통해 컴포넌트가 리렌더링될 때마다 모든 리렌더링에서 useEffect를 실행되지 않도록 할 수 있다.

 

useReducer

  • useState를 대체하여 상태 관리를 할 수 있는 hook
  • 복잡한 정적 로직을 만드는 경우, 다음 state가 이전 state에 의존적인 경우 사용하면 좋다.
  • reducer(state, action): 현재 상태와 액션 값을 전달받아 새로운 상태를 반환하는 함수
  • useReducer를 사용하면 컴포넌트 업데이트 로직(reducer)를 컴포넌트 밖에서 정의할 수 있다는 장점이 생긴다.
  • Redux에서 사용하는 action과 다르게 useReducer의 action 객체는 타입에 대한 어떠한 제약도 없다.
    const [state, dispatch] = useReducer(reducer, initialStateValue);
    // state: 현재 가리키고 있는 상태
    // dispatch(action): reducer에 action을 전달하여 실행시키는 함수. 파라미터로 액션 값을 넣어주면 리듀서 함수가 호출된다.
     

useMemo

함수형 컴포넌트 내부에서 발생하는 연산을 최적화하는 hook. 주로 일반적인 값과 객체를 메모이제이션하는 데 쓰인다.

// deps 내부에 있는 값이 바뀔 때만 factory를 연산한다.
useMemo(factory, deps)
  • deps는 의존값이 들어있는 배열로, deps에 있는 값이 변경되지 않으면 factory가 실행되지 않는다는 특징을 이용해서 의존하는 값이 변경될 때만 바뀌는 값을 메모이제이션하는 용도로 사용한다.

 

useCallback

함수형 컴포넌트의 렌더링 성능을 최적화하는 hook. 주로 함수를 메모이제이션하는 데 쓰인다.

  • 사실상 useMemo와 똑같이 사용할 수 있다. (일반 값을 재사용할 때 useMemo, 함수를 재사용할 때 useCallback을 쓰면 된다.)
  • useCallback(callback, deps)useMemo(() => callback, deps)은 정확히 똑같이 작동한다.
    // deps 내부에 있는 값이 바뀔 때만 callback 함수를 생성한다.
    // (단, !deps.length일 경우 컴포넌트가 처음 렌더링될 때만 함수를 생성한다.)
    useCallback(callback, deps)
     

useRef

  • JavaScript의 querySelector(), getElementById() 메소드를 대체하는 hook
  • DOM을 직접 선택해야 하는 상황(포커스, 스크롤, element 속성 가져오기 등)에서 사용한다.
  • useRef는 호출되어도 리렌더링이 발생하지 않기 때문에 로컬 변수를 사용할 때 활용하기도 한다.
    // 컴포넌트 로컬 변수로 useRef 사용하기
    const SampleComp = () => {
    const id = useRef(1); // id가 SampleComp의 로컬 변수가 된다.
    }
     

Custom Hook

  • 여러 컴포넌트에서 자주 비슷한 hooks를 사용하게 될 경우, Custom hook을 만드는 것이 좋다.
  • Custom hook도 결국 hook이기 때문에, 위에서 언급한 hook의 규칙을 지켜야 한다.
// 커스텀 hook 만들기
import { useReducer } from 'react';

function reducer(state, action) {
  return {
    ...state,
    [action.name]: action.value;
  }
}

export default function useInputs(initialForm) {
  const [state, dispatch] = useReducer(reducer, initialForm);
  const onChange = e => {
    dispatch(e.target);
  };
  return [state, onChange];
}
// 커스텀 hook 사용하기
import useInputs from './useInputs';
const Info = () => {
  const [state, onChange] = useInputs({
    id: '',
    name: '',
    password: '',
  });
  const { id, name, password } = state;
  // ...
}