[React] Recoil 톺아보기

PinkTopaz·2023년 5월 2일
0
post-thumbnail

Recoil의 등장배경

Recoil 등장 배경에 대한 이해를 돕기 위해 Props DrillingContext API 이라는 것이 무엇인지 먼저 알아보자.

Props Drilling이란?

리액트는 단방향의 흐름을 가진다.

위 그림과 같이 상위 컴포넌트의 state를 하위 컴포넌트의 props로 내려주고, 그 props를 또 다시 하위 컴포넌트에 넘겨주는 방식으로 데이터를 전달한다.

이 과정에서 A 컴포넌트의 동작 자체 필요한 props가 아니더라도 B 컴포넌트에게 전달하기 위한 목적만으로 A 컴포넌트는 상위 컴포넌트의 state를 props로 받아와야하는 상황이 발생할 수 있다.
이러한 상황을 Props Drilling이라고 부른다.

function UserList({ users, onRemove, onToggle }) {
  return (
    <div>
      {users.map(user => (
        <User
          user={user}
          key={user.id}
          onRemove={onRemove}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
}

상기한 예시 코드를 보면 UserList에서 users, onRemove,onToggle을 전달하는 다리에 불과하다. 직접 해당
props들을 이용하는 경우도 없고, 그저 자식 컴포넌트에 props를 넘겨주기만 한다.

단순히 3개 정도의 컴포넌트 사이에서는 이러한 상황이 크게 문제가 되지 않지만, 애플리케이션이 성장하며 여러 계층의 컴포넌트가 만들어진 상태에서 Props Drilling 이 발생하게 되면 여러 문제가 발생할 수 있다.

대표적으로

  • 이 과정에서 컴포넌트 A와 C에서만 필요한 데이터를 B-C로 전달하면서 컴포넌트 B에서 불필요한 리렌더링이 발생할 수 있고
  • A-B-C 컴포넌트 순으로 props를 전달했는데 B-C에서 전달하는 props의 이름을 변경한다면 C에서 문제가 생겼을 때 해당 Props가 어디에서부터 온 건지 추적하기가 힘들어지기도 한다.

Context란?

리액트 컴포넌트에서 별도의 props를 내려주지 않더라도 Context 하위 컴포넌트들이 value로 넣어준 값을 useContext를 통해 사용할 수 있는 일종의 상태관리 API로, Props Drilling을 문제점을 해결할 수 있는 방법 중 하나이다.

Context는 간단하게 다음과 같이 사용한다.

//✅ createContext 함수를 불러와서 context를 생성하고 
import {createContext} from 'react';
const MyContext = creatreContext();

//✅  Context 객체 내 Provider라는 컴포넌트를 이용해 컴포넌트 간 공유하고자 하는 값을 value 라는 Props로 설정한다. 
function App() {
  return (
    <MyContext.Provider value="Hello World">
    	<Children />
	</MyContext.Provider>
  );
}
//✅ value에서 공유된 state를 쓰고 싶은 컴포넌트에서 useContext를 선언하여 state를 꺼내 사용한다.
import {useContext} from 'react';
function Message() {
  const value = useContext(MyContext);
  return <div>Received: {value}</div>;
}

만약 Props를 이용한다면?

만약 props를 이용해서 최하단 컴포넌트까지 props를 전달한다면다음과 같이 최하단 컴포넌트에서만 props2를 사용함에도 B,C와 같은 상위 컴포넌트에서 불필요한 리렌더링이 발생할 것이다.

Context를 이용한다면?

하지만 Context 를 사용한다면 다음과 같은 구조가 될 것이다.

context가 필요한 컴포넌트에서만 C와 같은 중간 컴포넌트를 건너뛰고 바로 대상으로 하는 컴포넌트로 데이터를 전달할 수 있어 불필요한 리렌더링이 사라진다.

본 글은 Recoil을 알아보는 것이 주목적이기에 Context에 대한 더 깊은 이해는 다른 사람들이 안 알려주는 리액트에서 Context API 잘 쓰는 방법을 참고보면 좋겠다.

하지만 Context에도 한계는 있다.

Context 역시 내부의 state가 갱신되면 하위 컴포넌트들이 전부 렌더링되는, 리액트 컴포넌트의 룰을 벗어나지 않기 때문에 불필요한 리렌더링을 완전히 피할 수는 없다. React.Memo 등으로 최적화를 할 수도 있지만, 모든 최적화는 결국 적지 않은 비용이기에 최우선으로 고려할 내용은 아니다.

\rightarrow 복잡한 컴포넌트 구조를 가신 어플리케이션에서는 Context API가 Props Drilling에 대한 완벽한 해답이 되지 않는다.


그렇게 등장한 상태관리 라이브러리

이에 따라 mobx, redux, recoil 과 같은 상태관리 라이브러리들이 등장하기 시작했다.

각 상태관리 라이브러리 간단히 비교하기

  1. redux
    • redux-saga, redux-thunk, redux-devtools와 같은 추가 기능을 제공하는 라이브러리를 사용할 수 있다.
    • 작은 상태 하나를 변경하려고 해도, actions, reducer, type 등 보일러 플레이트 코드를 많이 작성해야 하는 번거로움이 있고, 작성하는 코드의 양이 많아진다.
    • 긴 역사를 자랑하는 만큼 안정성이 어느 정도 보장된다.
  1. mobx
    • React 뿐만 아니라 Vanilla JS나 Angular, Vue와 같은 프레임워크에서도 사용 가능
    • 보일러 플레이트 코드 등 작성할 것이 많은 Redux에 비해 가볍다.
    • 클래스형 컴포넌트를 기준으로 만들어져 객체 지향 프로그래밍 방식으로 코드를 작성할 수 있다. 이 때문에 Mobx와 리액트의 함수형 컴포넌트의 Hooks를 함께 사용하면 오류가 발생할 수 있다.
  1. recoil
    • React의 라이브러리가 아니기 때문에 React 내부 스케쥴러에 접근이 어려운 mobx, redux와 달리 Recoil을 만든 Facebook에서 발표한 리액트 전용 상태관리 라이브러리이다.
    • 동시성 모드와 suspense의 제공을 통해 렌더링을 인터럽트 가능하도록 만들어 최적의 UX를 제공한다. (참고자료)
      • 동시성 모드 제공 : React가 각 컴포넌트에서 실행되는 비동기 요청 등을 보고 판단하여 렌더링 우선 순위를 정해 렌더링 최적화를 하는 모드. 렌더링 될 준비가 완료되어야 렌더링을 시작한다.
      • Suspense: 컴포넌트가 렌더링 될 준비가 되지 않으면 렌더링을 하지 않고 suspense 상태로 둔다. 아래와 같이 fallback 속성을 전달하면 대신 렌더링 할 컴포넌트를 지정할 수 있다.

Recoil vs Redux

최근 6개월 간의 recoil과 redux의 npm 다운로드 수를 비교한 자료이다.

긴 역사를 가지고, devtool을 제공하며 큰 커뮤니티를 가진 redux의 사용이 아직도 압도적인 것이 사실이지만, API와 의미 및 동작을 가능한 React답게 유지한 Recoil에 대한 선호도도 꾸준히 유지되고 있다.

Recoil의 사용에 대해 조금 더 톺아보자.

왜, Recoil?

Recoil 공식문서 에서는 Recoil이 등장하게 된 동기를 다음과 같이 서술하고 있다.

  1. React 컴포넌트의 상태는 공통된 상위요소까지 끌어올려야만 공유될 수 있으며, 이 과정에서 거대한 트리가 다시 렌더링 되는 효과를 야기하기도 한다.
    \rightarrow 위에서 살펴본 Props Drilling을 언급하고 있다.

  2. Context는 단일 값만 저장할 수 있으며, 자체 소비자(consumer)를 가지는 여러 값의 집합을 담을 수는 없다.
    \rightarrow Context 섹션에서 살펴본 것처럼 context1: "new"와 같이 하나의 Context에서는 단일값(숫자, 문자열, 객체 등)만 저장할 수 있는데, recoil에서는 뒤에서 살펴볼 atom이라는 개념을 통해 이 한계를 해결하였다.

  3. 이 두 가지 특성이 트리의 최상단(state가 존재하는 곳)부터 트리의 말단(state가 사용되는 곳)까지의 코드 분할을 어렵게 한다.


Recoil, 어떻게 사용할까?

Recoil에는 크게 Atoms, Selector 이렇게 두 가지 핵심 개념이 존재한다.
Recoil은 이 둘 개념을 이용해 atoms (공유 상태)에서 selectors (순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph를 만들어 상태를 관리한다.

Atoms

Atoms는 상태의 단위이며, 업데이트와 구독이 가능하다.
atom이 업데이트되면 각각 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링 된다.
Atoms는 React의 로컬 컴포넌트의 상태 대신 사용할 수 있다.
동일한 atom이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트는 상태를 공유한다.
-Recoil 공식문서-

Facebook에서 Recoil을 소개한 영상에 등장하는 그림이다.
상단 1,2,3,4,5,6이 atom 이며 하단 Web 에서 atom을 가져다 사용하고 있다.

atom은 하나의 상태로, 컴포넌트가 구독할 수 있는 react state라고 생각하면 되는데, 개인적으로는 전역 상태를 담고 있는 저장소 정도로 이해했던 것 같다.
각각의 atom은 필수적으로 key, default를 가지는데 각각 다음과 같다.

  • key : 내부적으로 atom을 식별하는데 사용되는 고유한 문자열로, 해당 atom을 필요로 하는 컴포넌트에서는 이 key를 이용해 atom 내 데이터를 가져온다. 그렇기에 Atoms은 App 전체에서 다른 Atom과 Selector와 구분되는 Unique한 Key값을 가져야한다.
  • Default : atom에 지정해주는 초깃값이다.
// ✅ atom() 함수를 호출해 atom을 생성한다. 
const counter = atom({
  key: 'myCounter',
  default: 0,
});

atom에 옵션으로 effects_UNSTABLEdangerouslyAllowMutability 을 넣어줄 수도 있다.

  • effects_UNSTABLE : atom을 위한 선택적인 Atom Effects 배열인데, Atom Effects는 부수효과를 관리하고 Recoil의 atom을 초기화 또는 동기화하기 위한 API이다. 상태 지속성, 상태 동기화, 히스토리 관리와 같은 다양한 옵션이 있으니 참고해보면 좋겠다.
  • dangerouslyAllowMutability : boolean값으로,Recoil 상태값을 불변성을 지키지 않는 방식으로 업데이트할 수 있게 한다.

dangerouslyAllowMutability 보충설명

Recoil 상태를 수정할 때 일반적으로는 set 메서드를 사용하고, 이때 set 메서드는 이전 상태를 복사하고, 이전 상태와 새로운 값을 결합하여 새로운 상태를 생성한다.
이렇게 함으로써 Recoil 상태의 불변성을 유지하고, 상태 업데이트를 안전하게 처리한다.

하지만 dangerouslyAllowMutability 옵션을 사용하면, 이전 상태를 복사하지 않고 직접 수정할 수 있다.
이는 상태 업데이트를 더 빠르게 처리할 수 있게 해주지만, 상태 불변성을 깨뜨리는 위험성이 있다.

그렇기 때문에 Recoil의 성능을 높이기 위해 사용할 수는 있지만, 일반적으로는 안정성을 위해 dangerouslyAllowMutability 옵션을 사용하지 않고, 불변성을 유지하는 방식으로 상태를 업데이트하는 것이 좋다.


Selector

Selector는 atoms나 다른 selectors를 입력으로 받아들이는 순수 함수(pure function)다.
상위의 atoms 또는 selectors가 업데이트되면 하위의 selector 함수도 다시 실행된다.
컴포넌트들은 selectors를 atoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링 된다.
-Recoil 공식문서-

처음 recoil을 사용할 때 공식 문서의 해당 부분을 보고 atom을 set 메서드를 사용해서 업데이트 시키는 것과 Selector를 사용하는 것의 차이를 이해하기가 어려웠다.

사용을 해보며 이해한 내용은 다음과 같다.

💡 atom은 단순히 상태를 저장하고 업데이트하는 데 사용되지만, selector는 상태를 파생시켜서 계산하는 데 사용된다. 즉, selector를 사용하면 상태 값을 다른 상태 값을 기반으로 동적으로 계산할 수 있다.

예를 들어 투두리스트를 하나 만든다고 가정해보자.

const todoListState = atom({
  key: 'todoListState',
  default: [],
});

atom에는 다음과 같이 todoList의 상태, 즉 투두리스트의 항목들을 저장한다.

export const todoListStatsSelector = selector({
  key: 'todoListStatsSelector',
  get: ({ get }) => {
    const todoList = get(todoListState);
    const totalNum = todoList.length;
    const totalCompletedNum = todoList.filter((item) => item.isComplete).length;

    return {
      totalNum,
      totalCompletedNum,
    };
  },
});

그리고 만약 투두 항목의 전체 개수, 혹은 완료되 투두의 개수를 세고 싶다면, todoListState(atom)을 입력으로 받아들여 연산을 실행하여 반환할 수 있다.

그리고 이 연산 결과를 이용하고 싶은 컴포넌트에서

const { totalNum, totalCompletedNum } = useRecoilValue(todoListStatsSelector);

다음과 같이 받아와 사용할 수 있는 것이다.

즉, atom은 단순히 데이터를 담고 있는 저장소의 역할을 하는 것이고 그 데이터를 가공하는 것은 selector가 한다고 이해할 수 있을 것이다.


Example

필자가 프론트엔드 개발에 참여한 Pic.me에서 팀원이 작성한 코드를 예시로 들어보겠다.

Pic.me에서는 유저가 사진 2장을 업로드하고 해당 사진에 익명 투표를 받는 형식인데, 투표 결과를 보여주거나 투표 현황을 보여주는 등 유저가 첨부한 사진을 많은 컴포넌트에서 필요로 하기에 효율적으로 관리하기 위해 Recoil을 사용했다.

먼저 atom.ts 에 유저가 사진을 첨부하기 전 해당 정보를 필요한 컴포넌트에서 참조할 수 있도록 atom에 참조할 수 있는 unique한 key와 초깃값을 저장한다.

//✅ 새로고침, 혹은 페이지가 변경되더라도 상태관리를 유지하기 위해 persistAtom을 옵션으로 넣어주었다. (새로고침해도 첨부한 사진이 그대로 화면에 보인다.)
const { persistAtom } = recoilPersist();

export const votingImageState = atom<VotingImageInfo>({
  key: 'votingImage',
  default: {
    title: '',
    imageUrl: [],
  },
   effects_UNSTABLE: [persistAtom],
});

그리고 유저가 사진을 첨부하면 useRecoilState 를 이용해 유저가 첨부한 사진 정보를 atom - votingImageState에 업데이트한다.

const [votingForm, setVotingForm] = useRecoilState(votingImageState);
...
//✅ setVotingForm을 통해 유저가 첨부한 사진 url 값을 담아 atom 값을 변경한다. 
setVotingForm({ ...votingForm, imageUrl: copyImageForm });

또한 Selector를 사용한 경우를 살펴보자.

첨부한 사진으로 투표를 생성한 유저에게 투표가 종료된 후 사진에 부착된 스티커의 총 개수를 보여주려고 한다.

먼저 atom에 stickerResultState를 선언해 유저가 부착한 스티커 정보를 담는다.
상기한 예시처럼 set 메서드를 통해 업데이트된 atom은 다음과 같을 것이다.

export const stickerResultState = atom<StickerResultInfo[]>({
  key: 'stickerResult',
  default: [
    {
      stickerLocation: [{10,10}, {20,20}], // ☑️ 스티커 좌표 정보
      emoji: 1, // ☑️ 몇 번 스티커인지
      count: 2, // ☑️ 총 몇개의 스티커가 부착되었는지 
    },
     {
      stickerLocation: [{30,10}, {50,50}], // ☑️ 스티커 좌표 정보
      emoji: 3, // ☑️ 몇 번 스티커인지
      count: 5, // ☑️ 총 몇개의 스티커가 부착되었는지 
    },
  ],
  effects_UNSTABLE: [persistAtom],
});

그리고 유저에게 총 스티커 개수를 보여주기 위해 연산을 수행하는 selector 함수를 만든다.

export const stickerCountSelector = selector({
  key: 'stickerCountSelector',
  get: ({ get }) =>
  //✅ reduce 함수를 이용하여 currentStickerResult의 각 요소에서 count 속성만 더해 합계를 내준다. 
    get(stickerResultState).reduce((accumulator, currentStickerResult) => accumulator + currentStickerResult.count, 0),
});

다음과 같이 리턴값을 가져와 총 합계를 보여줄 수 있다.

  const stickerResultCount = useRecoilValue(stickerCountSelector);
...

 <StFirstReason>{stickerResult.length ? stickerResultCount : 0}</StFirstReason>

마치며

Recoil을 공부하다 보니 확실히 상태 관리 라이브러리를 사용하여 전역 상태를 관리하는 것이 확실히 편리하다는 생각이 들었다.

하지만 이렇게 편리하다고 해서 상태관리에 대한 큰 고민 없이, 혹은 자식에서 부모로, 즉 역방향으로 데이터를 업데이트 해줘야만 하는 상황에서 바로 상태관리 라이브러리를 쓰는 태도가 옳은 태도일까?

실제로 Pic.me 개발 당시 자식 component에서 부모 component의 state를 바꿔야 하는 상황이 왔을 때 Recoil 사용을 고려했지만, 결국에는 자식에게 부모의 state를 modify 할 수 있는 setState 함수를 props로 넘겨주는 방식으로 해결한 경험이 있다.

해당 기능의 depth가 깊지 않았기에 할 수 있었던 방법이었지만, 그때 든 생각은 어떤 기술이든지 그 기술을 사용하는 상황에서 "왜?"에 대한 고민이 필요하다는 것이었다.

Recoil이 React 친화적으로 조금 더 편리하다고 느끼는 사람도 있겠지만, 상황에 따라, 그리고 서비스의 규모에 따라 Redux가 더 적합한 상황이 올 수도 있다. (보일러플레이트 문제에도 불구하고 국내 많은 대기업에서 redux를 사용하고 있다고 한다. devtools의 제공이 무시할 수 없는 원인이라고.)

따라서 Recoil을 사용할 때에도 충분한 고려와 다른 상태관리 라이브러리들과의 비교를 통해 스택 도입을 결정짓는 태도를 가지는 것이 좋겠다.


참고자료

리코일 공식 문서
다른 사람들이 안 알려주는 리액트에서 Context API 잘 쓰는 방법
props drilling과 context
Recoil, 리액트의 상태관리 라이브러리

profile
🌱Connecting the dots🌱

7개의 댓글

comment-user-thumbnail
2023년 5월 3일

역시 글을 참 잘쓰십니당.. 술술 읽혀서 재밌었어요!

props drilling부터 다른 상태관리 라이브러리와 Context를 차근히 짚고 비교해주셔서 recoil 사용의 필요성이 아주 명확하게 들어왔어요! 결정적으로 불필요한 리렌더링을 막는다는 점에서 성능 최적화를 할 수 있는 것, 또 코드 측면에서도 효율성을 끌어 올려준다는 것이 체감되네요!

dangerouslyAllowMutability 옵션과 관련해서 setState() 함수 동작 방식에 대해 한번 더 공부해보았는데요! setState() 함수는 호출되면 바로 전달받은 state로 값을 바꾸는 것이 아니라 이전의 리액트 엘리먼트 트리와 전달받은 state가 적용된 엘리먼트 트리를 비교하는 작업을 거치고, 최종적으로 변경된 부분만 DOM에 적용해요!
그래서 이 작업을 매 setState마다 하는 것이 비효율적이기 때문에 이벤트 핸들러 내에서 호출된 모든 setState를 모아 한 번에 비동기적으로 처리하고 일괄적으로 같은 state를 바라보고 state를 업데이트 한다고 합니다.
이 개념과 관계가 있겠네요 ㅎㅎ

좋은 글 감사합니다 :)

답글 달기
comment-user-thumbnail
2023년 5월 3일

Recoil에 대한 깔끔하고 명확한 설명 감사합니다!

저는 기술 스택을 공부할 때, 해당 스택이 등장하게 된 배경을 미리 알고 공부하는 것과 그렇지 않은 것의 차이가 크다고 생각해 등장 배경을 먼저 알아보는 편인데, 이 글에서는 등장 배경을 가장 먼저
소개해주어 Recoil이 어떤 이유로 나왔고 어떤 문제를 해결해주는지 확실하게 알 수 있어서 좋았어요.

그 다음으로는 ContextAPI를 소개하며 ContextAPI가 어떤 문제를 안고 있는지 설명해주셨는데, 이 부분도 크게 공감이 갔어요. 설명을 덧붙이자면 실제로 Recoil의 코드를 분석해보았을 때 내부적으로 ContextAPI를 기반으로 설계되어있음을 알 수 있는데, Recoil만의 여러 로직들을 통해서 ContextAPI의 장점은 가져오되 단점은 보완하여 좀 더 "전역상태관리"에 적합한 라이브러리를 만들었음을 알 수 있어요!!

또한 여러 상태관리 라이브러리를 비교해주셨는데, 처음에 Redux를 먼저 공부했던 입장에서 Redux의 작성해야하는 코드의 양이 크다는 말이 너무 공감이 갔어요. 처음 Redux 보일러플레이트를 마주했을 때는 진짜 막막했던 기억이 있거든요...! 그래서 더 Recoil을 마주했을 때의 간결함이 큰 장점으로 다가오는 것 같아요!! 앞으로 Recoil에 더 좋은 기능들이 추가되고 많은 곳에서 사용하게 되면 충분히 잠재력 있는 라이브러리라고 생각합니다!!!

마지막에 말씀해주신 것처럼 저도 프로젝트를 진행하면서 매번 어디까지 전역상태를 사용하고 어디까지 Props drilling을 사용해야할지 고민이 되는 것 같아요. 여기서 저는 관심사에 따른 컴포넌트 분리를 얘기하고 싶은데요..! 이전에 프로젝트를 진행하면서 하나의 관심사를 공유하는 컴포넌트 모음을 Compound Component로 설정한 뒤, 해당 컴포넌트의 최상위 요소에만 전역상태관리로 상태를 전달하고 이후 하위 컴포넌트들에는 최상위 요소로부터 props 를 받는 패턴을 사용한 적이 있었어요!! 이때 하나의 Compound Component 내부적으로는 ContextAPI를 적용해서 관심사를 지역상태로 관리할 수 있도록 해주었어요. 이렇게 진행하면 Compound Component 내부의 모두가 같은 상태를 사용하므로 불필요한 리렌더링이 방지됨과 동시에 하나의 관심사를 가지는 컴포넌트들을 분리할 수 있어 다방면에서 이점을 가지고 올 수 있었어요. ContextAPI의 컨셉이 '상태관리'는 맞지만 '전역상태관리'가 아닌 점을 고려하면 이러한 곳에서 부분적으로 유용하게 쓰일 수 있을 것 같더라구요!! Pic.me 에서도 이처럼 상태관리에 대한 깊은 고민을 한 흔적이 보여 좋았어요. 경험을 공유해주셔서 감사해요 :)

글에서 Selector를 예시와 함께 설명해주신 점 좋았어요. 약간 애매하게 넘어갈 수 있는 부분을 좋은 예시를 통해 이해할 수 있었어요!! 실제로 아직 Recoil을 이용한 프로젝트를 제대로 진행해본 적 없는 입장이기 때문에 Selector에 대한 이해를 확실히 하지 못하고 있었는데, 이 글에서 순수함수를 이용한다는 점과 이에 대한 대표적인 예시를 보여주셔서 확실하게 짚고 넘어갈 수 있었어요.

Recoil의 핵심 개념을 콕콕 찝어낸 좋은 글 작성해주셔서 감사합니다!!!

답글 달기
comment-user-thumbnail
2023년 5월 3일

직접 참여한 프로젝트를 예시를 들어 설명해주셔서 더 재미있게 읽었던 것 같아요!
이해 쏙쏙 깔끔한 정리 감사합니다~

적어주신 것을 바탕으로 또 추가적으로 recoil에 대한 장점을 정리해보면 1) 보일러플레이트(변화없이 여러군데 반복되는 코드)가 적다. 2)지역상태로서 단순한 get/set 인터페이스로 상태를 공유하기 쉽다. 3) 학습하기 쉽다!(러닝커브가 낮다.) 4)상태를 분산적으로 두고 관리하기 편하기에 코드 스프리팅이 가능하다. 가 있을 수 있네요!

그리고 먼저 Selector 을 활용하지 않고 atom 을 직접 가져와서 사용하면 어떻게 될까 궁금해서 찾아봤는데 역시 괜히 Selector가 있는게 아니더라구요! atom을 가져와 직접 변경하게 되면 해당 atom 을 바라보고 있는 모든 컴포넌트 에서 상태가 변경이 되어 예상치 못한 부작용(Side-effects)을 일으킬수 있다고 합니다! 그리고 데이터 구조가 복잡한 프로젝트일수록 복잡한 연산을 요구하기에 컴포넌트 내부가 너무 지저분해져 의존성과 여러 연산으로 인해서 변수가 어떤 역할을 하는지 직관적으로 파악하기 어려워 상태 값을 다른 상태 값을 기반으로 동적으로 계산할 수 있는 Selector를 사용해야할 필요가 있겠어요!

답글 달기
comment-user-thumbnail
2023년 5월 4일

아묻따 리코일이 유행이라니까 사용해왔었는데, 리코일이 등장하게 된 배경과 의미들에 대해서도 알 수 있는 글이어서 좋았습니다! 리코일이 프롭스드릴링을 줄여줄 수 있는 전역상태관리 기술이라는 큰 장점이 있지요, 하지만 리코일의 그림자같은 면모도 있는데요,
1. 변수의 생명주기 : 전역변수는 함수 안의 변수와 다르게 함수가 return될 때 생명주기가 끝나지 않고, 어플리케이션이 끝날 때까지 죽지 않아요. → 변수의 긴 생명주기는 어플리케이션의 성능저하로 이어진다고 해요
2. 암묵적 결합 : 언제 어디서든 전여변수를 참조하고 변경할 수 있는데, 의도치 않은 상태의 변경이 일어날 수 있어요.
리코일이 편리하긴 하지만, 이러한 이유로 무조건적으로 많이 사용하기보다는 적당한 프롭스를 사용하고, 프롭스 드릴링이 심해지는 경우에만 전역상태관리를 하는 방법도 좋을 것 같습니다!

답글 달기
comment-user-thumbnail
2023년 5월 4일

개념도 자세히 정리해주시고 그에 맞는 코드까지 첨부해주셔서 recoil에 대해 좀 더 꼼꼼히 살펴보고 이해할 수 있었어요! 그리고 실제 경험까지 공유해주셔서 흥미롭게 읽어나갈 수 있었어요ㅎㅎ

먼저 recoil 뿐만 아니라 다른 상태관리 라이브러리에 대해 간단히 비교해주신 부분이 좋았는데요, redux는 작은 상태 하나를 변경하려 해도 많은 양의 보일러 플레이트 코드를 작성해야하기 때문에 번거롭긴 하지만 redux-devtools와 같은 추가 기능을 제공한다는 것이 강점이 될 수 있겠다는 생각이 들었어요!

그리고 recoil을 크게 Atoms, Selector로 나눠서 설명해주신 부분이 가장 잘 와닿았어요. 'atom은 단순히 상태를 저장하고 업데이트 하는데 사용되지만, selector를 사용하면 다른 상태 값을 기반으로 상태 값을 동적으로 계산할 수 있다' 라는 부분을 보고 atom과 selector 사용에 대한 차이를 쉽게 이해할 수 있었습니다!

마지막으로 Pic.me에서 recoil을 사용한 코드와, 위의 코드와 반대로 recoil 사용을 고려했지만 setState 함수를 props로 넘겨주는 방식으로 문제를 해결한 경험까지 공유해주셔서, recoil을 언제 사용하는 것이 좋고 어느 상황에서는 지양하는 것이 좋은지에 대해 다시 한번 생각해볼 수 있었어요!

덕분에 recoil에 대해 제대로 톺아볼 수 있었네용 알찬 아티클 감사합니다:0~~!!!

답글 달기
comment-user-thumbnail
2023년 5월 4일

재밌게 읽었습니다!!

Recoil을 설명해주기 이전에 Context API를 설명해주시고 Context API가 있지만 왜 상태관리 라이브러리가 탄생되었는지 알 수 있어서 유익했어요!

더불어 Recoil 외에 Redux와의 비교도 유익했습니다! 저는 Recoil를 사용하기 이전에 Redux를 많이 사용했었는데
보일러 플레이트가 많긴 하지만 Redux Dev Tools를 사용할 수 있다는 점은 정말 편하더라구요!

더불어 atom은 편하게 사용했지만 selector는 자주 사용하지 못했는데 아티클을 읽고 나니 어떠한 상황에서 써야하는지 감이 오는거 같습니다!

수고하셨습니다~~

답글 달기
comment-user-thumbnail
2023년 5월 4일

그림과 함께 이해되지 않는 부분을 중간중간 추가해주셔서 이해하기 쉬운 좋은 글인 것 같다는 생각이 들었습니다!
Atoms는 React의 로컬 컴포넌트의 상태 대신 사용할 수 있기에 useState를 쓰지 않고도 지역상태관리 또는 여러개의 작은 단위의 상태관리가 가능하다는 것이 매력으로 느껴졌고
redux를 사용할때는 모든 상태를 redux에 저장하면 규모가 방대해지니 지역상태관리와 전역상태관리, 서버 데이터 상태관리를 나눠했던 기억이 있었고 소규모 사이드 프로젝트에서 redux를 사용했던 억울한? 기억이 있었는데 추후에 보다 작은 규모에서 recoil을 활용하는 방안도 한번 고려해보면 좋을 것 같습니다.

좋은 글 감사합니다!

답글 달기