[Recoil] Recoil 200% 활용하기

juno7803·2021년 4월 12일
71
post-thumbnail

🙏 들어가기

안녕하세요! 이번에 쿠키파킹을 개발 하면서 recoil을 처음으로 사용해 보았고 베타버전이 출시된 이후 운영해 보면서 리팩토링과 최적화에 신경쓰다보니 recoil의 atom 만 전역 state 개념으로 사용했지 다른 기능들을 사용하지 못했던 느낌을 받아, 200% 활용하기 두 번째 포스팅으로 정하여 공부하고 실제로 쿠키파킹을 리팩토링 하려고 합니다.

이 포스팅에서 recoilselector에 대한 기본적인 usage는 React스러운 상태관리 라이브러리 :: Recoil 를 참고해 주세요!

⭐️ 이 순서대로 말씀 드릴게요

1. 상태 관리의 중요성
2. 뭔가 탐탁치 않은 Redux, 그리고 나온 Recoil
3. Recoil의 기본, Atom
4. Selector 와 비동기 처리(Suspense, Loadable)
5. 아직 미흡한 DevTools, Snapshot
6. 장 / 단점 및 마치며

 본 포스팅에선 상태관리의 중요성 부터, 왜 Redux가 아닌 Recoil을 선택하였는지 그리고 어떻게 200% recoil을 사용할 수 있을지 등을 순서대로 말씀드리고자 합니다!

1️⃣   상태 관리의 중요성

 양뱡향 바인딩을 하는 Angular.js와는 달리, React.js는 단방향으로 바인딩을 하는 라이브러리 입니다.
부모 ➡️ 자식 방향으로만 stateprops로 전달할 수 있고, 자식의 props를 부모에게 전달하는 방법은 존재하지 않습니다.
대신 자식 component에서 부모 componentstate를 바꿀 수 있는 두 가지 방법이 존재합니다.

  1. 자식에게 부모의 state를 modify 할 수 있는 setState 함수를 props로 넘겨준다.
  1. state management tool(redux, recoil)을 사용한다.

Parent.js

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

const Parent = () => {
	const [name, setName] = useState('foo');
	return(
    	<div>
        	<Children name={name} setName={setName}/>
        <div />
    );
}

export default Parent;

Children.js

import React from "react";

const Children = ({name, setName}) => {
  useEffect(()=>{
  	setName('bar');
  },[]);
	return(
    	<div>
        	MY Parent's name is {name}
        </div>
    );
}

export default Children;

 다음과 같은 예시처럼, 부모 component의 setName() 함수를 자식 component의 props로 내려보내 자식 component에서 state를 변경할 수 있습니다.

 하지만 1번과 같은 방법은 to-do-list 와 같이 기본적인 앱이 아닌, 조금만 규모가 큰 app에 대해선 상태 관리에 어려움을 겪에 됩니다. 자식을 내려보내는 depth가 조금만 깊어지면 1번과 같은 방법은 효율적이지 않습니다.

그래서 2번과 같은 state management tool이 나오게 된 것 입니다.
다음은 Redux를 예시로 든 모습인데, Redux에서는 전역 객체인 store를 두고 statestore에 전역으로 관리하여 어떤 components 에서도 state를 변경하고 조회할 수 있습니다.

2️⃣   뭔가 탐탁치 않은 Redux, 그리고 출시된 Recoil

원래 React 프로젝트의 대부분은 아래의 MVC 아키텍처 이라는 구조로 설계하고 있었습니다. Controller가 여러 Model을 제어하고 있는 구조였죠. 하지만, 프로젝트의 규모가 커지면서 상태도 많아졌기 때문에 Model과 View가 양방향으로 영향을 미치는 이 구조의 관리가 어려워졌습니다.

특히 규모가 큰 Facebook에서 이러한 패턴으로는 단순한 상태의 변화도 예측가능한 범위를 벗어나 관리가 힘들다고 판단해 새로운 아키텍처를 고안해 내었죠.

그래서 Facebook이 고안한 Flux 아키텍처 가 많이 사용되고 있습니다. 그림과 같이 데이터의 흐름은 무조건 단방향으로 흐르게 됩니다. dispatcher에서 action을 통해 store로, store에서 view로 또 다시 view 에서 action을 통해 dispatcher로 흐르게 되는 구조입니다.

 이 Flux 아키텍처를 따라간 구현체로 Redux가 가장 인지도가 높고 많이 사용되는 상태관리 툴 입니다. action, reducer, selector, store를 초기 세팅하는 부분은 간단한 상태 하나를 관리하더라도 많은 코드를 소모하였고 React에 최적화 된 라이브러리가 아니었기에 사용에 불편함이 많았습니다.

명확한 대체재가 없었고, 가장 큰 생태계를 구성하고 있기 때문에 사용량은 제일 높았지만, 그에 비해 만족도는 Vuex에 비해서도 낮은 수준을 기록하고 있습니다.

그리고 Facebook의 Recoil 팀은 Recoil을 다음과 같이 표현하며 출시합니다.

A state management library for React

이번 쿠키파킹을 개발하면서 Recoil을 쓰게 된 이유는 정말 단 하나였습니다. 익숙한 Redux 대신 대학생 개발팀으로 이루어져 처음 개발해보는 친구들에게 Redux의 많은 코드들을 단시간에 배우기엔 러닝커브가 높고 복잡했기 때문에 Recoil을 선택하게 되었습니다.

React를 위한 상태관리 라이브러리, 정말 동감했습니다. hook을 써본 React 개발자라면 쉽게 적응할 수 있었고 전역 state 라는 개념만 이해한다면 atom 정도는 쉽게 활용할 수 있었기 때문입니다. 아래에 이어서 recoil에 대해 자세히 설명드리겠습니다!

3️⃣   Recoil의 기본, Atom

⚛️ Atom

An atom represents state in Recoil. The atom() function returns a writeable RecoilState object.

공식문서에서의 atom에 대한 설명은 다음과 같습니다.
atom 은 기존의 redux에서 쓰이는 store 와 유사한 개념으로, 상태의 단위입니다.

atom이 업데이트 되면, 해당 atom을 구독하고 있던 모든 컴포넌트들의 state가 새로운 값으로 리렌더 됩니다.
unique 한 id인 key로 구분되는 각 atom은, 여러 컴포넌트에서 atom을 구독하고 있다면 그 컴포넌트들도 똑같은 상태를 공유합니다(상태가 바뀌면 바뀐 값으로 해당 컴포넌트들이 re-render 됩니다)

state.js

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

atom은 다음과 같이 작성해볼 수 있습니다. 정말 간단하죠? 설명과 같이 key로 고유한 atom을 구분하고, default에 기본으로 atom에 저장되는 값을 지정할 수 있습니다.
usage 또한 hook과 거의 유사한 형태를 띄고 있습니다. 이 또한 코드로 확인하겠습니다.

Cookie.js

import React from 'react'
import { cookieState } from '../../state';
import { useRecoilState } from 'recoil';

const Cookie = () => {
	const [cookies, setCookies] = useRecoilState(cookieState);
  
  return(
    <div>
        {cookie.map((cookie, index) => (
          <Card
            cookies={cookie}
            key={index}
            idx={cookie.id}
           />
       ))}
      </div>
  );
}
export default Cookie;

React의 기본 hook인 useState와 굉장히 유사한 형태를 가지고 있습니다.

const [cookies, setCookies] = useRecoilState(cookieState);

구조 분해 할당으로 state와 state를 set 하는 함수를 각각 cookiessetCookies에 받아올 수 있습니다. 이를 통해 atom의 state에 어떤 컴포넌트에서든 전역으로 받아올 수 있고, setCookies로 state를 변경할 수 도 있습니다.

물론, 어떤 컴포넌트에서 state를 변경하더라도 이를 구독하고 있던(사용하고 있는) 컴포넌트들은 모두 그 값이 갱신되어 re-render 됩니다!

useRecoilState() 과 그 친구들 👫

앞으로 자주 만나볼 개념이라 조금만, 아주 조금만 더 이 함수에 대해서 알아볼게요!
뒤에서 설명할 selector, loadable, snapeshot 에서도 이 함수의 역할과 비슷한 역할을 하는 함수들이 계속 언급됩니다.

atom에서는 state, loadable에서는 state or conents snapshot 에서는 snapshot의 value에 접근하거나 value를 set 할 수 있는 함수를 불러올 수 있는 hook에 해당합니다.

하지만 우린 이들을 전역으로 관리하기 때문에, value만 필요한 컴포넌트도 있을 것이고, state를 변경하기만 하는 컴포넌트도 있을 것 입니다. 이를 위해 useRecoilState() 의 역할을 반으로 쪼개면(?)

useRecoilValue()useSetRecoilState() 로 나눌 수 있겠습니다.

import { useRecoilValue, useSetRecoilState } from 'recoil';
import { cookieState } from '../../state';

const cookies = useRecoilValue(cookieState);
const setCookies = useSetRecoilState(cookieState);

이는 위의 useRecoilState() 를 사용했을 때와 완전히 같은 역할을 하게 됩니다.

참고로 useResetRecoilState() 라는 hook도 있는데, 이 hook은 인자로 받아온 atom의 state를 default 값으로 reset 시키는 역할을 합니다.

const resetCookies = useResetRecoilState(cookieState);

4️⃣   Selector 와 비동기 처리(Suspense, Loadable)

atom 만을 사용해서는 비동기 처리를 할 수 없습니다.
사실 이번 쿠키파킹 프로젝트에서는 atom 만을 사용하였기 때문에, api통신과 같은 비동기 처리 부분은 component에서 해주고 가져온 data를 atom에 저장하는 식으로 구현하였습니다.

하지만 이 과정을 selector에서 한 번에 처리할 수 있습니다. 먼저 기존의 코드를 보겠습니다.

Cookies.js

import React, { useState, useEffect } from 'react'  
import { useRecoilState } from 'recoil';
import { cookieState } from '../../recoil';
import {Loading, Card} from '../../components';

const Cookies = () =>{
  const [cookie, setCookie] = useRecoilState(cookieState);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    (async () => {
      const { data } = await getApi.getCookies();
      setCookie(data);
      // 다음과 같이 컴포넌트에서 직접 비동기 처리를 해주고, api 통신 결과로 받아온 data를 직접 atom에 set 해줍니다.
    })();
    setLoading(false);
  }, [setCookie]);

return (
  <>
  {Loading ? 
   <Loading /> // 로딩 중 일때 보여줄 view
   :
   (<div>
        {cookie.map((cookie, index) => (
          <Card
            cookies={cookie}
            key={index}
            idx={cookie.id}
           />
       ))}
      </div>)
  </>
}

export default Cookies;

 다음과 같이 작성해도 큰 문제는 없습니다. atom의 상태가 변할때 마다 각 컴포넌트에서 이렇게 따로 비동기 처리를 해준다면, 같은 atom구독하고 있던 컴포넌트들은 알아서 re-render 되기 때문입니다.

하지만, selector 에서는 이러한 로직을 한번에 처리해 줄 수 있는 동시에 캐싱 기능이 있어, 이미 받아왔던 정보에 대해서는 빠른 피드백이 가능해 성능적으로 유리합니다.

🔀 selector

A selector represents a piece of derived state

selector는 공식문서의 설명과 같이 파생된 state를 저장하고 있습니다.
이게 무슨 말일까요? 코드를 보면서 설명해 보겠습니다.

state.js

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

export const getCookieSelector = selector({
  key: "cookie/get",
  get: async ({ get }) => {
    try{
      const { data } = await client.get('/cookies');    
      return data.data;
    } catch (err) {
    	throw err;
    }
  },
  set: ({set}, newValue)=> {
    set(cookieState, newValue)
  }
});

 다음과 같이 비동기 로직을 selector에선 한 번에 처리 할 수 있습니다. 주의하셔야 할 점은, selector순수함수 여야 한다는 것 입니다.
순수함수란, 같은 입력이 들어오면, 해당 입력에 대한 출력은 항상 같은 함수라는 뜻을 가지고 있습니다.

 또한 selector는 read-onlyRecoilValueReadOnly 객체로서 return 값 만을 가질 수 있고 값을 set 할 순 없는 특징을 가지고 있습니다.

function selector<T>({
  key: string,

  get: ({
    get: GetRecoilValue
  }) => T | Promise<T> | RecoilValue<T>,

  set?: (
    {
      get: GetRecoilValue,
      set: SetRecoilState,
      reset: ResetRecoilState,
    },
    newValue: T | DefaultValue,
  ) => void,

  dangerouslyAllowMutability?: boolean,
})

공식문서에 쓰여진 selector의 타입을 보면서 위의 코드에 대해 각각 설명 드리겠습니다.

key : selector를 구분할 수 있는 유일한 id, 즉 key 값을 의미합니다.
get : 에는 derived state 를 return 하는 곳 입니다. 예시 코드에서는 api call을 통해 받아온 data를 return 하게 됩니다. (해당 selector가 갖고 있습니다.)

const cookie = useRecoilValue(selector)

다음과 같이 값을 조회할 수 있습니다.
set : writeable 한 state 값을 변경할 수 있는 함수를 return 하는 곳 입니다. 여기서 주의하실 점은, 자기 자신 selector를 set 하려고 하면, 스스로를 해당 set function에서 set 하는 것이므로 무한루프가 돌게 되니 반드시 다른 selector와 atom을 set 하는 로직을 구성하여야 합니다. 또한 애초에 selector는 read-only 한 return 값(RecoilValue)만 가지기 때문에 set으로는 writeable 한 atom 의 RecoilState 만 설정할 수 있습니다.

set: ({set}, newValue) =>{ set(getCookieSelector, newValue) } // incorrect : cannot allign itself
set: ({set}, newValue) =>{ set(cookieState, newValue) } // correct : can allign another upstream atom that is writeable RecoilState
const [cookie, setCookie] = useRecoilState(cookieState);

해당 내용은 여기 에서 자세한 이유를 확인하실 수 있습니다.

Cookies.js (selector로 개선)

import { getCookieSeletor } from '../../reocil';

const Cookies = () =>{
  const [cookie, setCookie] = useRecoilState(getCookieSelector);
}

return (
  <>
   (<div>
        {cookie.map((cookie, index) => (
          <Card
            cookies={cookie}
            key={index}
            idx={cookie.id}
           />
       ))}
      </div>)
  </>
});

export default Cookies;

잠깐❗️ selector는 read-only한 return 값만 가진다고 배웠습니다. 근데 여기서 useRecoilState()setCookie() 라는 state를 변경할 수 있는 set 함수도 반환하고 있습니다. 위의 set 설명에서 언급했듯, 이는 writeable한 state 즉 atom의 값만 수정할 수 있습니다.

따라서, 여기서 setCookies() 함수는 selector의 값을 수정하는 함수가 아닌, 다른 atom의 writeable 한 state를 수정할 수 있는 함수를 정의한 경우에만 사용할 수 있습니다.

selector는 결국 다른 selector나 atom의 값을 get해올 수 있고, get해온 값을 바탕으로 다른 atom의 state를 설정할 수 있는 state를 modify 할 수 있는 역할을 합니다.

자, 다시 돌아와서 원래 보려고 했던 비동기 처리로 돌아가볼게요!
하지만.. selector에서 비동기 처리를 하고 실행하면, 다음과 같은 에러페이지가 나타나게 됩니다😢

이는 비동기를 처리하고 있을 때(= 렌더링 할 데이터가 아직 도착하기 이전, loading) 보여줄 fallback UI가 없다는 뜻의 에러메시지 입니다.

👍 React.Suspense의 지원, 비동기 상태 처리

App.js

import React,{ Suspense } from 'react';
import { Cookies } from '../components';

const App = () => {
  return(
    <RootRecoil>
      <Suspense fallback={<div>Loading...</div>}>
        <Cookies />
      </Suspense>
    </RootRecoil>
   );
}

export default App;

이와 같이 React의 Suspense 컴포넌트로 렌더링할 <Cookies /> 를 감싸주고, fallback 이란 props에 loading 상태에 보여줄 UI를 넣어주게 되면 정상적으로 동작하게 됩니다!

✌️ 두 번째 방법, Recoil의 Loadable

Suspense대신 Recoil의 Loadable을 사용할 수도 있습니다.

import { getCookieSeletor } from '../../reocil';
import { useRecoilState, useRecoilValueLoadable } from 'recoil';

const Cookies = () => {
  const cookieLoadable = useRecoilValueLoadable(getCookieSelector);

  switch(cookieLoadable.state){
    case 'hasValue':
      return (
        <>
          (<div>
    	    {cookieLoadable.contents.map((cookie, index) =>(
              <Card
                cookies={cookie}
                key={index}
                idx={cookie.id}
               />
            ))}
	     </div>)
	  </>
	});
     case 'loading':
  	return <Loading />;
     case 'hasError':
     	throw cookieLoadable.contents;
}


export default Cookies;

Loadable atom 이나 selector의 현재 상태를 나타내는 객체 입니다.

const cookieLoadable = useRecoilValueLoadable(getcookieSelector);

와 같이 접근할 수 있으며 Loadable은 statecontents 라는 프로퍼티를 가지고 있습니다.

📩 Loadable 객체
state : hasValue , hasError , loading atom 이나 selector의 상태를 말하며, 앞의 세 가지 상태를 가질 수 있습니다.
contents : atom이나 contents의 값을 나타내며, 상태에 따라 다른 값을 가지고 있습니다. hasValue 상태일 땐 value를, hasError 일 땐 Error 객체를, 그리고 loading 일 땐 Promise를 가지고 있습니다.

Redux 에서 많이 봤던 패턴입니다! store에 대한 상태를 reducer에 action으로 따로 정의해줘야 하는 불편함이 있었지만, Recoil 에서는 useRecoilValueLoadable() hook을 통해서 쉽게 접근할 수 있어 훨씬 편하게 사용할 수 있습니다.

🏃‍♀️ selector를 통한 성능 개선 - 캐싱

앞에서 잠깐 언급했듯, selector는 기본적으로 값을 캐싱합니다. 들어왔던 적이 있는 값을 기억하고 있기 때문에, 같은 응답을 보내는 api call 에 대해선 추가적으로 요청하지 않아 성능적으로 매우 유리합니다.

다음은 이번 포스팅을 작성하면서, 쿠키파킹에 직접 적용해 본 모습을 첨부한 것 입니다!
읽지 않은 쿠키 조회 그리고 Directory 탭과 AllCookies 탭을 클릭할 때 마다 api call 을 하는 구조로 구현하였는데, 기존의 atom을 이용한 방식은 매번 api call을 하고 있는 반면, selector로 바꿔서 구현했을 땐, 한번 call을 했던 api에 대한 캐싱이 이루어져 다시 호출하지 않는 모습을 보실 수 있습니다 〰️

이는 기존의 atom만 이용하여 구현한 모습입니다. 탭을 바꿀때 마다 새로 api call을 하고 있는 모습을 보여주는 반면,

selector를 통해 구현했을 땐, 한번 call 한 api에 대해서는 새로 call 하고 있지 않습니다❗️

동적인 url에 대한 api call - selectorFamily 👨‍👩‍👧‍👦

위의 소제목은 이해를 쉽게 돕기 위해서 적은 것이지만, 외부에서 파라미터로 값을 받아와서 selector에 적용해야 할 경우에 selectorFamily 를 사용한다고 이해하시면 될 것 같습니다 😊

`https://baseUrl.com/type=${apiTypes}`

api콜을 할 때 요청을 보내는 서버의 url은 보통 다음과 같이 params 를 받는 경우가 많은데, 이런 경우엔 이렇게 selectorFamily 를 이용하면 해결할 수 있습니다.

state.js

export const githubRepo = selectorFamily({
  key: "github/get",
  get: (githubId) => async () => {
    if (!githubId) return "";

    const { data } = await axios.get(
      `https://api.github.com/repos/${githubId}`
    );
    return data;
  },
});

위 코드는 github 레포지토리를 읽어오는 github가 제공하는 api입니다. githubId를 동적으로 받아와야 할 경우에 이렇게 selectorFamily를 사용하면 해결이 가능하고, 실제로 컴포넌트에선

Github.js

import { useRecoilValue } from 'recoil';
import { selectorFamily } from '../../state';
const Github = () => {
  const githubId = 'juno7803';
  const githubRepos = useRecoilValue(githubRepo(githubId));
  
  return(
    <>
      <div>Repos : {githubRepos}</div>
    </>
  )

}
export default Github;

다음과 같이 파라미터에 전달하고자 하는 값을 selector에 넣어서 api call을 할 수 있겠습니다.

5️⃣   아직 미흡한 DevTools, 그리고 Snapshot

저는 recoil을 써보기 전까진 처음 접해보고 유일하게 사용해본 상태관리 툴이 redux 였습니다.
그래서 가벼운 state를 관리하더라도 많은 양의 코드를 쓰는건 불편했지만, reduxDevtools 에선 정말 정말 디버깅이 쉽고 편했다는 큰 장점이 있었습니다.

이게 당연한 것이라 생각하고 recoil에 넘어와서도 비슷한 tool을 찾고자 했지만 아쉽게도 아직 recoil엔 존재하지 않습니다. 공식문서에서도 snapshot 이라는 개념이 존재하지만, property나 해당 유틸리티 함수들의 뒤에 _UNSTABLE 이라는 이름이 붙을 정도로 디버깅에 대한 부분은 완벽하게 출시되지 않았습니다. 공식문서의 설명, hook 이름 뒤에 _UNSTABLE 이 붙어있습니다.

A Snapshot object represents an immutable snapshot of the state of Recoil atoms.

이는 공식문서에서 snapshot을 설명하는 문장입니다.
상태(state)를 영상에 비유한다면, snapshot은 해당 영상의 한 프레임 즉 순간의 state를 포착한 것이라고 생각하시면 이해가 쉬울 것 같습니다.

본 포스팅에서 깊게 설명하기는 무리가 있을 것 같고, 앞으로 더 개선되거나 좋은 devtools가 나오게 되면 추가로 공부해서 새로 포스팅 하는걸로 하고, useRecoilCallback() 이라는 hook을 통해 snapshot을 콘솔창에 확인하는 걸 보여드리고 설명을 마치겠습니다.

Cookie.js

...
const logState = useRecoilCallback(({ snapshot }) => () => {
    console.log("현재 Snapshot에 포함된 states: ", snapshot.getLoadable(getCookieSelector).contents);
  });

return(
  <>
    ...
    <button onClick={logState}>현재 스냅샷 보관</button>
  </>
);

비동기적으로 업데이트 되는 state에 대한 snapshot을 찍기 위해 useReocilCallback() 이라는 hook을 사용하여 snapshot을 찍은 모습입니다.

6️⃣   마치며 👏

분명히 recoil은 매력적인 상태관리 툴 입니다.
hook을 사용해본 react 개발자들은 redux에 비해 매우 적은 러닝 커브로 익힐 수 있다는 가장 큰 장점과 더불어 왠만한 기능은 기본적으로 hook으로 구현되어 있기 때문에 가져다 쓰기도 편했습니다.

하지만, 정말 출시된 지 얼마 되지 않은 기술이기 때문에 생태계가 작고, 그만큼 atom과 selector에 대한 기본적인 예제 이외에는 소개된 곳이 적은 편입니다. (이 포스팅을 적게 된 이유이기도 합니다.)
따라서 공식문서의 예제에 의존해야 하고 어떤식으로 써야하는 정형화된 구조나 패턴이 없어 아직은 큰 규모의 프로젝트에 도입하기가 조금 꺼려질 수 있다고 생각합니다.

또한 devtools의 부재도 정말 크다고 생각합니다. redux의 가장 큰 장점 중 하나가 편리한 devtools 였기에 snapshot으로만 상태의 변화를 찍어보며 체크할 수 없는 현재의 recoil에서 가장 큰 단점이 아닐까 생각합니다.

에서 데하지만, States Of JS데이터 계층 - 기타 도구 부문에서 가장 큰 관심을 받고 있음을 알 수 있고, 출시된지 얼마 안됐기 때문에 앞으로의 행보가 더 기대됩니다. _UNSTABLE 딱지도 떼고, 좋은 devtools 만 나오더라도 실무에서 적극 활용가능하다고 생각합니다.

긴 글 읽어주셔서 감사합니다 😄

reference

⭐️ Recoil 200% 활용하기 Reference

해당 링크는 제가 참여한 쿠키파킹의 디렉토리 공유 기능을 사용하여 공유한 링크입니다! 레퍼런스들을 한데 모아 공유할 수 있어서 기능을 활용해 보았습니다 :)

profile
사실은 내가 보려고 기록한 것 😆

22개의 댓글

comment-user-thumbnail
2021년 4월 12일

Recoil을 사용한 로딩뷰 설정과 API 콜 최적화 정말 신기하네요 🥺 좋은 글 감사합니다 👍👍👍 쿠키파킹 최적화도 더욱 기대됩니다 ✨✨✨

1개의 답글
comment-user-thumbnail
2021년 4월 12일

유익한 포스팅 감사합니다🙂 리코일 궁금하고 써보고 싶었는데 이 레퍼런스만 봐도 왕왕 도움될거같아요 ✨✨ 설명도 잘하시고... 못하시는게 뭔지..👍🏻

2개의 답글
comment-user-thumbnail
2021년 4월 18일

selector가 api call을 캐싱해오는지는 몰랐네요. 혹시 selector로 api call을할때 캐싱 옵션을 부여할 수 있나요 예를들면 우선 캐시된 값을 리턴하고 백그라운드에서는 가져온다던지?? 무튼 굉장히 유용하게 사용할 수 있을 것 같습니다 좋은글 감사합니다 ㅎㅎ

1개의 답글
comment-user-thumbnail
2021년 4월 21일

진짜 유용한 글이네요!! 리액트 공부 시작하는 데 정말 큰 도움 될 것 같습니다~~ 북마크 해두고 궁금한 부분 여러번 읽어 볼게요~~😊

1개의 답글
comment-user-thumbnail
2021년 4월 23일

너무 유용하게 사용하겠습니다:)

1개의 답글
comment-user-thumbnail
2021년 4월 23일

좋은 글과 좋은 extension 제공에 감사합니다!
뜬금없는 벨린이의 질문입니다만, 코드 확장자명을 어떻게 입력해야 코드 블럭에서 JSX 문법에 하이라이트 표시가 되는지 궁금합니다ㅠㅠ

1개의 답글
comment-user-thumbnail
2021년 4월 26일

리코일에서 비동기 호출 방식이 너무 편리하고 깔끔하네요! 설명도 너무 깔끔해서 북마크해두고 또 보러 올게요! 잘 보고 갑니다 :)

1개의 답글
comment-user-thumbnail
2021년 4월 26일

너무멋있어요~ 쿠키파킹 잘 쓰고있어요~

2개의 답글
comment-user-thumbnail
4일 전

제 친구 준호는요, 마음이 여린 친구에요. 자기개발 하는 모습이 보기 좋네요...^_^

1개의 답글