숭어 개발 블로그

[React] 상태 관리 본문

React

[React] 상태 관리

숭어싸만코 2024. 4. 24. 09:06

1-1. 리액트가 마운트 시에 하는 작업들

  • props로 받은 값을 컴포넌트의 로컬 상태로 설정
  • 외부 API 요청(REST API 등)
  • 라이브러리 사용 (D3, Video.js 등...)
  • setInterval을 통한 반복작업 혹은 setTimeout을 통한 작업 예약

💥 참고로 리액트 컴포넌트는 부모 컴포넌트가 리렌더링 되면 자식 컴포넌트 또한 리렌더링 된다 (바뀐 내용이 없다 할지라도!)

1-2. 리액트가 언마운트 시에 하는 작업들

  • setInterval, setTimeout 을 사용하여 등록한 작업들 clear 하기 (clearInterval, clearTimeout)
  • 라이브러리 인스턴스 제거

1-3. Hook 컴포넌트를 작성할때 유의할점

  • return문이 hook보다 위에 있으면 안된다
    👉 컴포넌트 안에 존재하는 hook은 무조건 다 실행이 되어야한다.
          예외 처리로 작성한 if문 안에 return 문이 있을 경우에는 hook보다 위에 존재해서는 안되며,
          가장 아래쪽에 작성하는게 좋다
  • Hook끼리는 중첩이 불가능하다
  • (이거는 불확실한데...) useEffect hook은 해당 컴포넌트 함수 return문 바로 위에 위치하는게 좋다 (안전빵인가 뭔가...)
출처: https://kyung-a.tistory.com/38#1-1._리액트가_마운트_시에_하는_작업들 [Hi, Kyung-a log:티스토리]

 

[출처] https://jjongbin.tistory.com/entry/React-%EC%A0%84%EC%97%AD-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC

 


상태 (State)

- 어떠한 의미를 지닌 값

- 지속적으로 변경 될 수 있는 값

로컬 상태 (Local State)

- 특정 컴포넌트 내에서만 데이터를 관리하

- 함수형 컴포넌트에서는 리액트 hook 과 props 를 사용하여 상태관리를 하는 방법이다.

- 하위 컴포넌트에 데이터를 전달하기 위해, props를 사용

- 어플리케이션의 크기가 점점 커질 수록 props drilling이 증가하여, 반복작업이 많아지고 유지 보수가 어려워지는 단점

 

Hook 의 종류

- Hook은 React 16.8에 새로 추가된 기능이다. Hook은 class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있게 해준다.

1. useState

- 컴포넌트 안에서 상태관리를 할때 쓰는 가장 기본적인 Hook

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

function Example() {
  const [count, setCount] = useState(0); // # useState 

  return (
    <div>
     
    </div>
  );
}

 

2.useEffect

- 리액트 컴포넌트가 리렌더링 될 떄마다 특정한 작업을 수행 할 수 있도록하는  Hook

- dependency array (deps) 를 통해 특정 객체의 상태 값이 변할 떄마다 리렌더링 시켜서 useEffect 에 선언된 로직을 수행 

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

function Example() {

  const [count, setCount] = useState(0); // # useState 

  useEffect(() => { // # useEffect
    
    console.log("useEffect!!!")
  }.[count]); // # dependency array

  return (
    <div>
     
    </div>
  );
}

# useEffect 사용시 주의점

- useEffect 안에서 사용하는 상태나 props가 있다면 useEffect의 deps에 넣어줘야한다 

- 만약 넣지 않게 된다면 useEffect에 등록한 함수가 실행 될 때 최신 props, 상태를 가르키지 않게 된다

-필수는아니며 오히려 deps를 넣어줬을때 무한 렌더링에 걸리는 상태가 발생하므로 적절히 판단

3. useLayoutEffect

4. useMemo

5. useCallback

6.useReducer

< React 18 Verion 추가된  Hook >

7. useId

8. useTransition

9.useDeferreValue

10. useInsertionEffect


전역 상태 (Global State)

- 전역 상태는 프로젝트 전체의 상태관리를 총괄하는 방법으로, 대표적으로 Redux, Context API, Zustand 등이 존재

- 전역에서 관리하기 때문에, 어떤 컴포넌트에서든 상태 값을 반영하고, 변경시키는 것이 가능

- props 과정이 생략되어, 작업이 적고 유지 보수가 간편해지는 장점이 있으나, 상태관리 오류시 나타나는 사이드 이펙트가 커지는 단점이 존재

전역 상태관리 종류

1. Context API

- 전역 상태를 공유할 수 있도록 리액트에서 제공

-트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 값을 공유하도록 할 수 있다.

Context
전역 상태를 저장하는 역할
Context 내부에 Provider와 Consumer가 정의되어있고, Consumer는 Context를 통해서 상태에 접근이 가능하다. 

Provider
전역 상태를 제공하는 역할
Context에 상태를 제공해서 다른 컴포넌트가 상태에 접근할 수 있도록 도와준다. 제공된 상태에 접근하기 위해서는 Provider 하위에 컴포넌트가 포함되어있어야한다. 보통 모든 컴포넌트에 접근해야하는 상태를 제공하기 위해서는 루트 컴포넌트에서 Provider를 정의한다.

Consumer
제공받은 전역 상태를 받아서 사용하는 역할
Context는 Consumer 사이에 있는 첫 객체를 Context에 인자로 전달하기 때문에, 바로 JSX를 작성하면 안되고, 빈 객체를 작성하고나서 JSX를 작성해야한다.
const MyContext = React.createContext(defaultValue);
// defaultValue 매개변수는 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰이는 값

const [value, setValue] = useState(초기값)

return (
  <MyContext.Provider value={{value, setValue}}>
  	<하위 컴포넌트 />
    ...
  </MyContext>
)
return(
  <ThemeContext.Consumer>
    {value => <span>{value}</span>}
  </ThemeContext.Consumer>
)

//===========================================================================

const {value, setValue} = useContext(MyContext);

return (
  <div>
    <span>{value}</span>
  </div>
);

장점

  • React에서 기본적으로 제공하기 때문에 별도 설치 필요하지 않다.

 단점

  • 컨텍스트가 둘러싼 모든 컴포넌트가 리랜더링된다. (memo 사용으로 방지 가능)

 

2. Redux

- Flux 패턴을 적용한 상태 관리 라이브러리 

# Flux 패턴 참조 : https://ai-mugil.tistory.com/93

 

- Store 에 상태들을 저장하고, 해당 어떠한 변화가 필요할 때 Action 을 Dispatch 하여 Reducer 에서 이를 받아 정해놓은 흐름으로 상태를 변화시키는 방식

Store
전역상태를 저장한다.
자바스크립트 객체 형태로 저장되어 있으며, 오직 Reducer를 통해서만 접근할 수 있다. Store는 1개만 존재할 수 있다. 
 
Action
Reducer에게 보내는 Store에 대한 행동을 정의한다.
Action을 Reducer에게 전달하기 위해서는 dispatch 메소드를 사용해야한다. dispatch는 Store의 내장 함수 중 하나로 액션 객체를 넘겨줘서 상태를 업데이트한다. 이벤트를 일어나게하는 이벤트 트리거의 역할을 한다. 
 
Reducer
이전 상태와 액션을 받아, 다음 상태를 반환하는 역할
Reducer를 통해서만 전역 상태를 변경하고 업데이트할 수 있다. 어떤 액션이 들어오는지 그 유형에 따라 이벤트를 처리하는 이벤트 리스너라고 볼 수 있다. 이전 상태를 변경한다는 점이 아니라 새로운 상태 객체를 생성해서 반환해야 한다.

dispatch
action을 발생시키는 것
상태를 업데이트하는 유일한 방법은 store.dispatch() 메서드를 부르고 action 객체를 넘겨주는 것이다.

장점

  • 외부에서 관리 Context API처럼 리랜더링 x

 단점

  • 리덕스 스토어 환경 설정의 복잡성
  • 리덕스를 제대로 사용하기 위해 붙는 여러개의 미들웨어
  • 보일러플레이트, 즉 어떤 일을 하기 위해 꼭 작성해야 하는 (상용구)코드가 많이 요구된다.
  • 설계 철학에 요구되는 코딩 방식을 꼭 추구해야해서 오히려 코드가 복잡해질수 있다.

 

3. Recoil

Context API 기반으로 페이스북에서 만든 함수형 컴포넌트에서만 사용 가능한 전역 상태관리 라이브러리이다.

- Recoil 을 사용하려면 상위 컴포넌트(App 또는 Router)를 RecilRoot 를 통해서 감싸줘야 한다.

- Atoms는 상태의 단위이며, 업데이트와 구독이 가능하다.

- 고유값인 key를 설정해주고 default에 기본값을 할당해준다.

function App() {
  return (
    <RecoilRoot>
      <Router />
    </RecoilRoot>
  );
}


//=================================================


export const userState = atom<userProps>({
  key: `userState/${v1()}`,
  default: {
    id: '',
    nickname: '',
    hair: '',
  },
});
const [user, setUser] = useRecoilState(userState);

Selector는 atoms나 다른 selectors를 입력으로 받아들이는 순수 함수(pure function)다.

상태를 기반으로 하는 파생 데이터를 계산하는 데 사용된다. 최소한의 상태 집합만 atoms에 저장하고 다른 모든 파생되는 데이터는 selectors에 명시한 함수를 통해 효율적으로 계산함으로써 쓸모없는 상태의 보존을 방지한다.(https://recoiljs.org/ko/docs/introduction/core-concepts/)

const showUserState = selector({
  key: 'showUserState',
  get: ({get}) => {
    const {id, nickname, hair} = get(userState);
    return `
      id: ${id}
      nickname: ${nickname}
      hair: ${hair}
    `;
  },
});

장점

  • 러닝커브가 작다.
  • 비동기 처리가 내장되어 있다.

단점

  • 함수형 컴포넌트에서만 사용이 가능

 

4. Zustand

flux패턴을 기반으로 한 개의 중앙에 집중된 형식의 스토어 구조를 활용한다.

import { create } from "zustand";

interface SearchOptions {
    //ts를 사용하기때문에 타입지정이 필요.js사용시 미사용 !!
    searchOptions: any;
    setSearchOptions:(options: any) => void;
}

const useTsmStore = create<SearchOptions>((set) => ({
    searchOptions:"",
    setSearchOptions: (options: any) => set({ searchOptions: options })
   
}));


export default useGlobalStore;

장점

  • Context API를 사용할 때와 달리 상태 변경 시 불필요한 리랜더링을 일으키지 않도록 제어하기 쉽다.
  • 상태 변경 시 불필요한 리랜더링을 일으키지 않도록 제어하기 쉽다.
  • 러닝커브가 작다.

 

5. jotai

atomic 패턴으로 한 상태관리 라이브러리이다.

import { atom } from 'jotai'

const countAtom = atom(0);
const mangaAtom = atom({ 'label': '', 'content': '' });
const [count, setCount] = useAtom(countAtom)

3가지의 atom 형태

  • 읽기 전용 atom
  • 쓰기 전용 atom
  • 읽기-쓰기 atom
const readOnlyAtom = atom((get) => get(priceAtom) * 2);

const writeOnlyAtom = atom(
  null, //  첫 번째 인수에 'null'을 전달하는 규칙입니다.
  (get, set, update) => {
    // update는 atom을 업데이트하기 위해 받아오는 값입니다.
    set(priceAtom, get(priceAtom) - update.discount)
  }
);

const readWriteAtom = atom(
  (get) => get(priceAtom) * 2,
  (get, set, newPrice) => {
    set(priceAtom, newPrice / 2)
    // 동시에 원하는 만큼 atom을 설정할 수 있습니다.
  }
);

추가적으로 다음과 같은 장점을 가지고 있어 일부 개발자들 사이에서 Recoil보다 선호되는 경향이 있다고 한다.

  • 경랑화된 API (Minimalistic API)
  • String key의 미사용
  • 타입스크립트 기반
  • utils 함수들의 제공
출처 : https://jjongbin.tistory.com/entry/React-%EC%A0%84%EC%97%AD-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC

 

'React' 카테고리의 다른 글

[React] 라이프사이클  (0) 2024.05.08
[React] Flux 패턴  (0) 2024.04.24
Comments