[React] React Hooks

똔의 기록·2022년 7월 25일
0
post-thumbnail

저번 포스팅 때 React에서의 클래스 기반 컴포넌트에 대해 설명했다면 이번에는 함수형 컴포넌트에 대해 적어보려고 한다.

리액트 훅은 v16.8에 새로 도입되었으며 함수형 컴포넌트에서 기존에 라이프사이클 메서드가 없어서 사용할 수 없었던 기능들을 사용할 수 있게 만들어 주었다.

리액트 훅을 도입하게 된 목적

  1. 컴포넌트에서 상태관련 로직을 사용할 때 레이어 변화 없이 재사용할 수 있게하기 위함
  • 기존에는 여러가지 레이어로 둘러 쌓여있어서 구조가 복잡했기 때문이다.
  1. 기존의 라이프사이클 메서드 기반이 아닌 로직 기반으로 나눌 수 있어서 컴포넌트를 함수 단위로 잘게 쪼갤 수 있다는 이점 때문이다.

그 외에도 클래스 기반 컴포넌트를 지양하고자 하는 목적 등도 있다.

리액트 훅의 종류

useState

useState는 가장 기본적인 Hook이며, 함수형 컴포넌트에서 가변적인 상태를 지닐 수 있게 해 준다. 함수형 컴포넌트에서 상태를 관리해야 할 때 사용한다. state는 원시타입 뿐만 아니라 객체로 사용할 수도 있다.

const [sowon, setSowon] = useState('공부중');

여러개의 useState를 사용할 수도 있지만, 이와 같이 하나의 state에 여러 프로퍼티를 추가해서 두 가지 이상의 상태를 관리할 수도 있다.

  const [state, setState] = useState({
    name: "Sowon",
    id: sowonwow
  });

useEffect

useEffect는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다. 클래스형 컴포넌트의 componentDidMount와 componentDidUpdate, componentWillUnmount를 합친 형태로 보아도 된다.
이 훅을 통해서 함수형 컴포넌트에서 사이드 이펙트(side effect)를 수행할 수 있는데, 여기서 사이드 이펙트는 데이터 가져오기, 구독 설정, 수동으로 DOM 조작 등을 말한다.

useEffect는 리액트에서 컴포넌트 렌더링 이후 어떠한 일을 수행해야 하는지 말해준다. 우리가 넘긴 함수(effect라고 부름)를 기억했다가, DOM 업데이트 이후 불러온다. 이렇게 컴포넌트 안에서 불러오게 될 경우 effect를 통해 state나 props에 접근할 수 있게 된다. useEffect는 컴포넌트의 첫 번째 렌더링과 그 이후 모든 업데이트에서 수행이 된다.

만약에 useEffect에 설정한 함수를 매번 업데이트마다 수행시키지 않으려면 어떻게 해야 할까? 업데이트 될 때 실행하지 않으려면 함수의 두 번째 파라미터로 비어 있는 배열을 넣어 주면 된다. 그리고 만약 특정 값이 업데이트 될 때만 useEffect를 실행하고 싶다면 두 번째 파라미터 배열에 해당 값들을 넣어주면 된다.

// 첫 렌더링 때만 useEffect 실행
useEffect(() => {
  console.log('마운트 될 때만 실행');
}, []);

// 특정 값(name)이 바뀔 때만 useEffect 실행
useEffect(() => {
  console.log(`${name}이 바뀔 때만 실행`);
}, [name]);

프로젝트 때 이 useEffect에 넣어주는 값 때문에 고생을 많이 했다...

useReducer

useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용하는 Hook이다.
(state, action) => newState의 형태로 reducer를 받고 dispatch 메서드와 짝의 형태로 state를 반환한다.
하윗값이 복잡한 정적 로직을 만들거나, 다음 state가 이전 state에 의존적인 경우 보통 useState 대신 useReducer를 사용한다. 또한 useReducer는 자세한 업데이트를 트리거 하는 컴포넌트의 성능을 최적화 할 수 있는데, 이것은 Callback 대신 dispatch를 전달할 수 있기 때문이다.

const initialState = {count: 0};

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

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useReducer 첫번째 인자에는 reducer 함수를 넣고 두번째 인자에는 초기값을 넣어준다. 이 Hook을 사용하면 state값과 dispatch 함수를 받아온다. 여기서 state는 현재 가리키고 있는 상태고, dispatch는 액션을 발생시키는 함수이다. dispatch(action)과 같은 형태로 함수 안에 파라미터로 액션 값을 넣어주면 리듀서 함수가 호출되는 구조이다.

redux와 형태가 아주 유사한 것 같다. 다음 포스팅에는 redux와 useReducer의 차이에 대해서 조사해보아야겠군..

useMemo

useMemo를 사용하면 함수형 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있다. 이 Hook은 메모이제이션 된 값을 반환한다. useMemo는 의존성이 변경되었을 때만 메모이제이션 된 값을 다시 계산한다. 이 최적화는 모든 렌더링 시 고비용 계산을 방지하게 해 준다. useMemo가 성능 최적화를 위해서 사용하는 것은 맞지만, 가능하면 useMemo를 사용하지 않고도 동작할 수 있도록 코드를 작성하는 것이 더 바람직하다.

프로젝트 당시 하위 컴포넌트에 props로 배열을 넘겨주었는데 객체를 넘겨주어 렌더링 시 비교하면 같은 객체여도 다르게 인식하여 렌더링이 무한으로 일어나는 문제가 생겼다. 이를 해결하기 위해 useMemo를 사용하려고 하였으나 적용시키는게 상당히 어려워서 redux의 store에 저장하여 해결하였다.

useCallback

useCallback은 메모이제이션 된 콜백을 반환한다. 주로 렌더링 성능을 최적화 해야 하는 상황에서 사용하는데, 이 Hook을 통해서 이벤트 핸들러 함수를 필요할 때만 생성할 수 있다. 인라인 콜백과 그것의 의존성 값의 배열을 전달하면 useCallback은 콜백의 메모이제이션된 버전을 반환한다. 그 메모이제이션된 버전은 콜백의 의존성이 변경되었을 때만 변경된다. 이는 불필요한 렌더링을 방지하기 위해 참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용하다. useMemo와 비슷한 역할을 하고 useCallback은 결국 useMemo로 함수를 반환하는 상황에서 더 편하게 사용할 수 있는 훅이다. 숫자, 문자열, 객체 처럼 일반 값을 재사용하려면 useMemo를 사용하고, 함수를 재사용하려면 useCallback을 사용한다.

=> 위에 언급한 문제의 해결을 위해 모든 함수를 useCallback으로 수정했다.

useRef

useRef는 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해준다. useRef는 .current 프로퍼티로 전달된 인자(initialValue)로 초기화된 변경 가능한 ref 객체를 반환한다.

ref 속성을 사용하는 것보다 useRef() 훅을 사용하는게 더 유용한데, 그 이유는 useRef()가 순수 자바스크립트 객체를 생성하기 때문이다. useRef()와 {current: ...} 객체를 생성하는 것의 차이점은 useRef는 매번 렌더링을 할 때 동일한 ref 객체를 제공한다는 점이다.

하지만 useRef는 내용이 변경될 때 그것을 알려주지는 않는다. .current 프로퍼티를 변형하는 것이 리렌더링을 발생시키지는 않기 때문이다. 만약 리액트가 DOM 노드에 ref를 attach 하거나 detach할 때 어떤 코드를 실행하고 싶다면 callback ref를 사용하는 것을 권장한다.

useContext

useContext는 context 객체를 받아 그 context의 현재 값을 반환한다. context의 현재 값은 트리 안에서 이 Hook을 호출하는 컴포넌트의 가장 가까이에 있는 <객체.Provider>의 value prop에 의해 결정된다.

컴포넌트에서 가장 가까운 <MyContext.Provider>가 갱신되면 useContext는 <MyContext.Provider>에게 전달된 가장 가까운 context value를 사용하여 렌더러를 트리거 한다.상위 컴포넌트에서 React.memo나 shouldComponentUpdate를 사용하더라도 useContext를 사용하고 있는 컴포넌트 자체에서부터 다시 렌더링이 된다. 항상 인자는 context 객체 그 자체여야 한다.

useContext를 호출한 컴포넌트는 context 값이 변경되면 항상 리렌더링 된다. 따라서 이 비용이 많이 들면 메모제이션을 통해 최적화를 할 수도 있다.

참고 https://devowen.com/312?category=778540

profile
Keep going and level up !

0개의 댓글