React Hook 정리

·2024년 3월 10일

Hook이란?

Hook은 함수형 컴포넌트에서 React state와 생명주기 기능을 “연동(hook into)“할 수 있게 해주는 함수이다.

  • Hook은 class 안에서는 동작X (class 없이 React를 사용할 수 있게 해주는 것)
  • 반복문, 조건문, 중첩된 함수내에서 호출X (최상위 에서만 호출 가능)
  • 리액트 훅은 일반적인 JS 함수에서 호출X

왜 필요할까?

함수형 컴포넌트들은 기본적으로 리렌더링이 될때, 함수 안에 작성된 모든 코드가 다시 실행됨. 이는 함수형 컴포넌트들이 기존에 가지고 있던 상태(state)를 전혀 관리(기억)할 수 없게 만듦. 하지만 Hook의 등장으로, 브라우저에 메모리를 할당 함으로써, 함수형 컴포넌트가 상태(state)를 가질 수 있게 한 것.

Hook 종류

useState

인자로 초기 state 값을 하나 받고

현재의 state 값과 이 값을 업데이트 하는 함수를 같이 제공

life cycle & useEffect

useEffect를 통해 Mounting/Updating/Unmounting 처리가 가능

Component가 re-render 될때마다 호출

useEffect 의 첫번째 parameter : rendering이 될때 실행시킬 함수

useEffect 의 두번째 parameter : 배열 (dependency array - 의존성 배열)

componentDidMount

**//** 의존성 배열**[]**에 아무것도 넣지 않으면 Mount시에만 렌더해주고 끝남.(1번만 실행)

useEffect(()=>{
		console.log("마운트 됨!!")
	},**[]**)

componentDidUpdate와 비슷

// 의존성 배열이 없기 때문에 뭐 하나라도 바뀌면 무조건 다시 실행
useEffect(()=>{
		console.log("수정하고 다시 그려짐!!")
	})

// someState가 수정될때만 리렌더
useEffect(()=>{
		console.log("수정하고 다시 그려짐!!")
	},[someState])

componentWillUnmount

useEffect(()=>{
		console.log("수정하고 다시 그려짐!!")

		//이부분이 끝나고 진행할 것들
		return(()=>{
			console.log("여기서 나갈래요!!")
		})

	})

componentDidUpdate와 비슷하지만 다른점 하나는, Mount 되자마자 실행되는 점.

왜 리턴문이 필요할까??

⇒ useeffect가 실행되기 전에 먼저 실행되는 return 문을 clean up함수라고 하는데, 만약 settimeout함수를 실행시키려는 useeffect일때 의존성 배열도 없고 return도 없다면 렌더링 될때마다 settimeout함수가 실행될 것이다. 나중가면 타이머가 여러개 생겨서 버그를 일으킬 수도 있기 때문에 이럴때 cleanup함수를 사용해서 타이머 함수를사용하기 전에 기존 타이머들을 다 제거해줄수가 있다.

말그대로 cleanup해준다고 생각하면 편할듯!

useEffect 사용시 주의 사항

💡 **의존성 배열[ ]**

의존성 배열이 함수형 컴포넌트의 생명주기를 결정하는 키포인트!!

💡 ***useEffect 안에서 setState의 사용*** useEffecrt 내에서 setState를 사용할때는 **정말 필요한 경우가 아니라면 지양**

컴포넌트가 마운트된 이후에 setState를 적용하게 되면,
1. state가 변경되고,
2. 변경된 state로 컴포넌트가 다시그려짐(=리렌더)

즉, useEffecrt 내에서 setState를 사용하게 되면 불필요한 리렌더나 무한루프를 일으키게 되고 성능면에서 비효율적이게 됨

useRef

React에서 DOM을 직접 컨트롤할때 사용함

useCallback / useMemo

규모가 큰 프로젝트에서 불필요한 리렌더링이 자주 일어나면 성능 저하 문제를 발생시키기 때문에 최적화라는 작업이 필요함

⇒ 이미 수행된 연산에 대한 결과를 기억해두었다가 동일한 연산이 다시 수행될 경우 그 값을 반환하는 방법 (callback은 함수 반환, memo는 값 반환)

주의 사항

💡 막 쓰면 그건 그거 나름대로 성능 저하 문제를 가져옴.

계산이 복잡해질때 적절히 사용해야함

React.memo

React.memo는 고차 컴포넌트(High Order Component)

고차 컴포넌트는 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수

React.memo는 props가 변화하지 않으면 반환되지 않는 강화된 컴포넌트를 반환함

부모 컴포넌트인 App은 count와 text 두 가지 state를 가지고 이를 각각 CountView와 TextView의 prop으로 전달하고 있다. 만약 setCount()이 실행되면 count state의 값이 변해 App 컴포넌트가 리렌더링 되기때문에 자식 컴포넌트인 CountView와 TextView 또한 리렌더링된다. 하지만 TextView의 경우 count state와 아무 관련이 없는 컴포넌트이기 때문에 리렌더링 될 필요가 없다. 반대로 setText()가 실행되는 경우에도 CountView는 리렌더링 될 필요가 없다. 이런 경우 React.memo를 통해 리렌더되지 않도록 해주어야한다.

useMemo와의 차이점

  • useMemo는 React Hooks인 반면, React.memo는 HOC(고차 컴포넌트)
  • React Hooks인 useMemo는 컴포넌트 내부에서만 사용이 가능함.

Context란?

데이터는 props를 통해서 부모에서 자식에게 전달 되지만, 어플리케이션 안의 여러 컴포넌트들에게 props를 전달해줘야 하는 경우 context를 이용하면 명시적으로 props를 넘겨주지 않아도 값을 공유할 수 있게 해주는 것

즉, 데이터가 필요할 때마다 props를 통해 전달할 필요가 없이 context 를 이용해 공유 가능

context API를 사용하기 위해서는 Provider , Consumer , createContext 이렇게 세가지 개념을 알고 있으면 된다.

  • createContext : context 객체를 생성한다.
  • Provider : 생성한 context를 하위 컴포넌트에게 전달하는 역할을 한다.
  • Consumer : context의 변화를 감시하는 컴포넌트이다.

App.js

import React, { createContext } from "react";
import Children from "./Children";

// AppContext 객체를 생성한다.
export const AppContext = createContext();

const App = () => {
  const user = {
    name: "김예린",
    job: "대학생"
  };

  return (
    <>
      <AppContext.Provider value={user}>
        <div>
          <Children />
        </div>
      </AppContext.Provider>
    </>
  );
};

export default App;

Children.js

import React from "react";
import { AppContext } from "./App";

const Children = () => {
  return (
      <AppContext.Consumer>
        {(user) => (
          <>
            <h3>AppContext에 존재하는 값의 name은 {user.name}입니다.</h3>
            <h3>AppContext에 존재하는 값의 job은 {user.job}입니다.</h3>
          </>
        )}
      </AppContext.Consumer>
  );
};

export default Children;

useContext

Children.js

import React, { useContext } from "react";
import { AppContext } from "./App";

const Children = () => {
  // useContext를 이용해서 따로 불러온다.
  const user = useContext(AppContext);
  return (
    <>
      <h3>AppContext에 존재하는 값의 name은 {user.name}입니다.</h3>
      <h3>AppContext에 존재하는 값의 job은 {user.job}입니다.</h3>
    </>
  );
};

export default Children;

App.js 에서 Context를 생성하고 Provider를 통해 전달하는 코드는 그대로지만 Children.js에서 AppContext 를 사용하는 과정에서 <AppContext.Consumer> 같은 코드를 사용해서 복잡하게 작성하지 않고 const user = useContext(AppContext)를 통해 Context를 불러 온 후 바로 사용이 가능하게 바뀌었다.

따라서 useContext 를 사용하면 기존의 Context 사용 방식보다 더 쉽고 간단하게 Context를 사용이 가능하고, 앞서 다뤘던 useState, useEffect와 조합해서 사용하기 쉽다는 장점이 있다

💡 **Context는 상태 관리 도구가 아니다

리액트에서 상태관리를 하는 이유 중 대표적인 게 prop-drilling방지라고 생각했다. 그래서 context도 prop-drilling을 피하기 위해 사용하니까 상태관리 도구와 비슷하지 않을까? 라고 생각했는데, 결론적으로는 아니다.

context는 어떤 것도 저장하거나 관리하지 않는다.

애초에 상태관리란 다음을 의미한다.

  • 초기 값 저장/현재 값 읽기/값 업데이트

Context는 이 조건에 부합하지 않으므로 상태 관리 툴이 아니라고 한다.
컨텍스트는 이미 어딘가에 존재하는 상태를 다른 컴포넌트와 공유하는 방법일 뿐이다.**

useReducer

  • useState를 대체할 수 있는 함수
  • React에서 컴포넌트의 상태 관리를 위해 기본적으로 가장 많이 쓰이는 hook은 state인데, 좀 더 복잡한 상태 관리가 필요한 경우 reducer를 사용할 수 있다.
  • reducer는 이전 상태와 Action을 합쳐, 새로운 state를 만드는 조작을 말한다.

dispatch 함수

⇒ 첫번째 인자인 reducer 함수를 실행시킨다.

⇒ 컴포넌트 내에서 state의 업데이트를 일으키키 위해 사용하는 함수

⇒ action 객체를 인자로 받으며 action 객체는 어떤 행동인지를 나타내는 type 속성과 해당 행동과 관련된 데이터(payload)를 담고 있다.

⇒ action을 이용하여 컴포넌트 내에서 state의 업데이트를 일으킨다.

<button onClick={() => dispatch({ type: "INCREMENT", payload: 1 })}>증가</button>

reducer 함수

⇒ dispatch 함수에 의해 실행되며, 컴포넌트 외부에서 state를 업데이트 하는 로직을 담당

⇒ state, action 객체를 인자로 받아, 기존의 state를 대체하여 새로운 state를 반환하는 함수

counter 예시

import React, { useReducer }from "react";

functionreducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + action.payload };
case "DECREMENT":
return { count: state.count - action.payload };
default:
thrownewError("unsupported action type: ", action.type);
  }
}

const Counter = () => {
const initialState = { count: 0 }; //초기 state
const [state, dispatch] =useReducer(reducer, initialState);

return (
    <><h2>{state.count}</h2><button onClick={() => dispatch({ type: "INCREMENT", payload: 1 })}>
        증가

💡 Context 와 useReducer
컴포넌트 복잡도가 보통 수준 or 외부 라이브러리를 사용하고 싶지 않을때 상태관리 목적으로 사용 가능하다고 한다.
상태관리 라이브러리와 몇가지 차이점이 있다면,
Redux ⇒ 모든 UI에 독립적이므로 별개로 사용 가능
Context & useReducer ⇒ 리액트에서만 사용가능
useReducer에는 미들웨어가 없음
얼핏 보기에는 Context + useReducer가 Redux와 거의 같아보일지 몰라도, 절대 동일하지 않고 기능적으로 리덕스를 대체할 수는 없다고 한다.

profile
말 배우는 감자

0개의 댓글