리액트와 상태관리 라이브러리 (1)

keemsebeen·2024년 6월 6일
0

5.1 상태 관리는 왜 필요한가?

여기서 상태는 어떠한 의미를 지닌 값이며, 시나리오에 따라 지속적으로 변경될 수 있는 값을 의미한다.

리액트 상태관리의 역사

Flux 패턴의 등장
당시에는 웹 애플리케이션이 비대해지고 상태도 많아짐에 따라 어디서 상태가 변했는지 등을 추적하고 이해하기가 어려운 상황이었다고 한다. 페이스북 팀은 이러한 문제의 원인을 양방향 데이터 바인딩으로 봤다. 뷰에서 모델을 변경할 수 있으며 반대의 경우도 가능했다. 이는 변경 시나리오가 복잡할 수록 관리가 어렵다.

따라서 페이스북팀은 양방향이 아닌 단방향으로 데이터 흐름을 변경하는 것을 제안했는데 이것이 바로 Flux 패턴의 시작이다.

  • 액션 : 어떠한 작업을 처리할 액션과 그 액션 발생 시 함께 포함 시킬 데이터를 의미한다. 액션 타입과 데이터를 각각 정의해 이를 디스패처로 보낸다.
  • 디스패처 : 액션을 스토어에 보내는 역할을 한다. 콜백 함수 형태로 앞서 액션이 정의한 타입과 데이터를 모두 스토어에 보낸다.
  • 스토어 : 여기에서 실제 상태에 따른 값과 상태를 변경할 수 있는 메서드를 가지고 있다. 액션의 타입에 따라 어떻게 이를 변경할지가 정의돼 있다.
  • 뷰 : 리액트 컴포넌트에 해당하는 부분이다. 스토어에서 만들어진 데이터를 가져와 화면을 렌더링하는 역할을 한다.


장점
1. 반면 데이터의 흐름을 추적하기 쉽고 코드를 이해하기 수월해진다.

단점
1. 사용자의 입력에 따라 데이터를 갱신하고 화면을 어떻게 업데이트해야 하는지 코드로 작성해야 한다.
2. 코드의 양이 많아지고 개발자가 수고로워진다.

시장 지배자 리덕스의 등장
Flux 구조와 더불어 Elm 아키텍처를 도입한 라이브러리다.

리덕스는 하나의 상태 객체를 스토어에 저장해두고, 이 객체를 업데이트 하는 작업을 디스패치해 업데이트를 수행한다.
reducer 함수는, 웹 애플리케이션 상태에 대한 완전히 새로운 복사본을 반환한 다음, 애플리케이션에 새롭게 만들어진 상태를 전파한다.

장점
1. prop drilling 문제를 해결해준다.
2. 스토어가 필요한 컴포넌트까지 connect만 쓰면 스토어에 접근이 가능하다.

단점
1. 해야 할 일이 많다. (어떠한 액션 타입인지 선언, 액션을 수행할 careator 함수 생성)
2. dispatcher, selector 필요, 새로운 상태를 어떤 식으로 변경할지 등등을 정의해야한다.

Elm이란, 데이터 흐름을 세가지로 분류하고 이를 단방향으로 강제해 상태를 안정적으로 관리하는 아키텍처이다.

Context API와 useContext
prop drilling에 대한 문제는 리덕스가 등장한 이후에도 지속됐고, 이에 리액트 16.3에서 전역상태를 하위 컴포넌트에 주입할 수 있는 새로운 Context API를 출시했다.

export default class MyApp extends Component<{},Counter> {
  state = { count : 0 }

  componetnDidMount() {
    this.setState({count: 1})
  }

  handleClick = () => {
    this.setState((state) => {count: state.count + 1})
  }

	render(){
    return (
      <CounterContext.Provider value={this.state}>
        <button onClick={this.handleClick}>Increment</button>
        <DummyParent />
      </CounterContext.Provider>
     )
  }
}

MyApp에서 상태를 선언하고 이를 Context로 주입한다. 이에따라 하위 컴포넌트에서 상태를 사용가능하게 된 것이다.

그러나 Context API는 어디까지나 상태관리가 아닌 주입을 도와주는 기능이며, 렌더링을 막아주는 기능 또한 존재하지 않으니 사용할 때 주의가 필요하다.

훅의 탄생, 그리고 React Query와 SWR
훅 API는 기존 무상태 컴포넌트를 선언하기 위해서만 제한적으로 사용됐던 함수 컴포넌트가 클래스 컴포넌트 이상의 인기를 구가할 수 있도록 많은 기능을 제공했다.

이러한 훅과 state의 등장으로 이전에는 볼 수 없던 방식의 상태 관리가 등장하는데 바로 React Query와 SWR이다. 두 라이브러리 모두 외부에서 데이터를 불러오는 fetch를 관리하는데 특화된 라이브러리지만, API 호출에 대한 상태를 관리하고 있기 때문에 HTTP요청에 특화된 상태 관리 라이브러리라 볼 수 있다.

import React from "react";
import useSWR from "swr";

const fetcher = (url) => fetch(url).then((res) => res.json());

export default function App() {
  const { data, error } = useSWR("/api/hello", fetcher);

  if (error) return <div>failed to load</div>;
  if (!data) return <div>loading...</div>;
  return (
    <div>
      <p>{JSON.stringify(data)}</p>
    </div>
  );
}

swr 예시이다. 첫번째 인수인 API 주소는 키로도 사용되며, 이후 다른 곳에서 동일한 키로 호출하면 재조회하는것이 아니라 useSWR이 관리하고 있는 캐시의 값을 활용한다.

Recoil, Zustand, Jotai, Valtio에 이르기 까지

// Recoil
const counter = atom({key:'counter', default:0});
const todoList = useRecoilValue(counter);

//Jotai
const countAtom = atom(0);
const [count,setCount] = useAtom(countAtom);

// Zustand
const useStore = create(set => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
}));
const count = useCountStore(state => state.count);

// Valtio
const state = proxy({ count: 0 });
const snap = useSnapshot(state);
state.count += 1;

요즘 새롭게 떠오르고 있는 많은 상태관리 라이브러리는 기존의 리덕스와는 다르게 훅을 활용해 작은 크기의 상태를 효율적으로 관리한다는 것이다.

profile
프론트엔드 공부 중인 김세빈입니다. 👩🏻‍💻

0개의 댓글