Recoil 사용하기(feat. 간단 예제)

이동환·2021년 4월 6일
28

TIL

목록 보기
69/74

이 글은 리코일을 공부하는 입장에서 공식문서에 시작하기를 번역하면서 이해한 글입니다.

Installation

CRA(npx create-react-app)로 리액트 파일을 생성한 후 아래 cli로 리코일을 설치한다.

npm i recoil
// or 
yarn add recoil

RecoilRoot

리코일 state를 사용하는 컴포넌트들은 <RecoilRoot>를 필요로 한다. <RecoilRoot>를 사용하는 가장 좋은곳은 root component이다.

// app.js
import React from 'react';
import {
  RecoilRoot,
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
} from 'recoil';    // import!!! import하는것은 important하닌까 ~

function App() {
  return (
    <RecoilRoot>    // 이렇게 감싸주어야한다.
      <CharacterCounter />
    </RecoilRoot>
  );
}

Atom

아톰은 상태를 말하며 어떠한 컴포넌트에서 씌여지고 읽혀질 수 있다. 아톰의 밸류를 읽는 컴포넌트들은 암묵적으로 그 아톰에게 참고(subscribe(구독이라고 해도 될거 같다.) )되어지고 있다. 그래서 아톰의 업데이트는 이 아톰을 참고하고 있는 컴포넌트를 리-렌더 시킨다.

const textState = atom({
  key: 'textState', // unique ID (다른 atoms/selectors을 구별하기 위해서)
  default: '', // default value (aka initial value)
});

function CharacterCounter() {
  return (
    <div>
      <TextInput />
      <CharacterCount />
    </div>
  );
}

function TextInput() {
  const [text, setText] = useRecoilState(textState);

  const onChange = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
}

Selector

셀렉터는 하나의 추출된 state다. '추출된(derived 또는 파생된)'이라는 의미는 state가 변경(transformation)된것을 의미한다. 다시 말해서 추출된 상태란, 주어진 상태를 변경(modify)시키는 순수 함수에 의해서 변경된 스테이트(the output)라고 말할 수 있다.(실제 상태를 변경시키지는 않는거 같다.)

(A selector represents a piece of derived state. Derived state is a transformation of state. You can think of derived state as the output of passing state to a pure function that modifies the given state in some way)

// usage
const charCountState = selector({
  key: 'charCountState', // unique ID (with respect to other atoms/selectors)
  get: ({get}) => {
    const text = get(textState);

    return text.length;
  },
});

function CharacterCount() {
  const count = useRecoilValue(charCountState);

  return <>Character Count: {count}</>;
}

(아래 헷갈리는것들을 정리했다. 공부한것을 바탕으로 아래와 같이 생각한다. 틀렸다면 죄송.....)

useRecoilState

: useState와 같은 역할이라고 봐도 될거 같다. 그러나 다른 파일에 있는 아톰을 읽을 수 있다는 점에서 useSelector와 비슷한거 같기도 하다.

다시 말해서, 아톰의 상태를 설정할 수 있고, 변경 시킬 수 도 있다.

useRecoilValue

: 아톰을 조회할 때만 사용한다.

useSetRecoilState

: setter 역할을 한다. useSetRecoilState를 사용하여 아톰을 변경 시킬 수 있다.

useResetRecoilState

: 아톰 값을 디폴트 값으로 변경시켜준다.

** 헷갈리는 점

useSetRecoilState와 useRecoilState 둘다 사용해서 setter함수를 구현 시킬 수 있다. 그럼 둘의 차이점과 언제 무엇을 써야하나라는 의문이 든다.

아직 정확하게는 모르겠지만, 공식문서를 참고해보면

We use useRecoilState() to read todoListState and to get a setter function that we use to update the item text, mark it as completed, and delete it:
useRecoilState는 아톰(todoListState)을 읽고, setter함수를 사용하기위해서(get a setter function)라고 적혀 있다.

추측건대, 둘중 무엇을 쓰든 상관은 없다. useSetRecoilState를 사용하면 읽기도 가능하고 상태 변경도 가능하다는것 같다.



간단 예제

위의 정보를 바탕으로 간단한 카운터 예제를 만들어봤다.

// app.js
import './App.css';
import { RecoilRoot } from 'recoil';
import Counter from './Counter';

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

export default App;
// atom.js
import { atom } from 'recoil';

let countState = atom({
  key: 'counter',
  default: 0, 
});

export default countState;
// Counter.js
import React from 'react';
import countState from './Atoms';
import { useRecoilState, 
  useRecoilValue, 
  useSetRecoilState, 
  useResetRecoilState 
} from 'recoil';

function Counter() {
  const [counter, setCounter] = useRecoilState(countState); 
  // useState와 같지만, useRecoilState을 사용하여 다른 파일에 있는 아톰을 읽을 수 있다.
  const currentCount = useRecoilValue(countState);  // 읽기 전용!
  const counterHandler = useSetRecoilState(countState); // 값만 변경 시키기 
  const resetCounter = useResetRecoilState(countState); // 디폴트값으로 값 변경
  
  const plusCount = () => {
    counterHandler((pre) => pre + 1);
  };
  const minusCount = () => {
    counterHandler((pre) => pre - 1);
  };


 
return (
    <div>
     <div>
      {/* <div>{counter}</div> */} // counter 또는 currentCount 둘 중 하나를 사용해도 상관없는거 같다.
      <div>{currentCount}</div>  // 그러나 읽기만 하려고 currentCount를 사용했다.
  
      {/* <button onClick={() => setCounter((num) => num + 1)}>+</button>
      <button onClick={() => setCounter((num) => num - 1)}>-</button> */}
      // 위의 코드도 작동한다.
      
      <button onClick={plusCount}>+</button>
      <button onClick={minusCount}>-</button>
      <button onClick={resetCounter}>reset</button>
    </div>
  );
}

export default Counter;

이번에는 셀렉터까지 추가해보겠습니다.

우선 atom.js를 아래와 같이 바꾸었습니다.

// atom.js
import { atom } from 'recoil';

let countState = atom({
  key: 'counter', // unique ID (with respect to other atoms/selectors)
  default: 0, // default value (aka initial value)
});

let inputState = atom({    // 기존에서 추가된 아톰.
  key: 'input',
  default: 0,
});

export { countState, inputState };

그리고 새로운 파일을 selector.js에 아래의 코드를 추가해보겠습니다.

// selector.js
import { selector } from 'recoil';
import { countState, inputState } from './Atoms';

const countStateSelector = selector({
  key: 'CountState',

  get: ({ get }) => {
    const inputVal = get(inputState);
    const count = get(countState);

    return `추가된 카운트는 ${inputVal}이고, 현재 카운트는 ${count}입니다.`;
  },
});

export default countStateSelector;

마지막으로 Counter.js를 아래와 같이 변경하였습니다.

// Counter.js
import React from 'react';
import countState from './Atoms';
import { countState, inputState } from './Atoms';  // 새로 변경된 코드
import countStateSelector from './selector';   // 새로 추가된 코드
import { 
  useRecoilState, 
  useRecoilValue, 
  useSetRecoilState, 
  useResetRecoilState 
} from 'recoil';

function Counter() {
  const [counter, setCounter] = useRecoilState(countState); 
  // useState와 같지만, useRecoilState을 사용하여 다른 파일에 있는 아톰을 읽을 수 있다.
  const currentCount = useRecoilValue(countState);  // 읽기 전용!
  const counterHandler = useSetRecoilState(countState); // 값만 변경 시키기 
  const resetCounter = useResetRecoilState(countState); // 디폴트값으로 값 변경
  
  // 새로 추가된 코드
  const currentInput = useRecoilValue(inputState);
  const inputHandlerState = useSetRecoilState(inputState);
  const resultValue = useRecoilValue(countStateSelector);
  
  
  const plusCount = () => {
    counterHandler((pre) => pre + 1);
  };
  const minusCount = () => {
    counterHandler((pre) => pre - 1);
  };

  // 새로 추가된 코드
  const inputHandler = (e) => {
    let target = e.target.value;
    inputHandlerState(target);
  };
  const submitCount = () => counterHandler((pre) => pre + Number(currentInput));


 
return (
    <div>
     <div>
      {/* <div>{counter}</div> */} // counter 또는 currentCount 둘 중 하나를 사용해도 상관없는거 같다.
      <div>{currentCount}</div>  // 그러나 읽기만 하려고 currentCount를 사용했다.
  
      {/* <button onClick={() => setCounter((num) => num + 1)}>+</button>
      <button onClick={() => setCounter((num) => num - 1)}>-</button> */}
      // 위의 코드도 작동한다.
      
      <button onClick={plusCount}>+</button>
      <button onClick={minusCount}>-</button>
      <button onClick={resetCounter}>reset</button>


      // 새로 추가된 코드
      <div>
        <input type='text' onChange={inputHandler}></input>
        <button onClick={submitCount}>입력값 더하기</button>
        <div>{resultValue}</div>
      </div>

    </div>
  );
}

export default Counter;

마치며

상태관리 라이브러리는 리덕스만 사용해봤었다. 몇개 안되는 state때문에 스토어, 액션, 리듀서들을 만들어야 했지만, 리코일은 조금 더 가볍게 상태를 관리할 수 있었다. 지금은 당장 기본만 해본것이라서 정확한 판단은 안서지만, 상태관리 라이브러리로 괜찮다는 생각이 든다.

profile
UX를 개선하는것을 즐기고 새로운것을 배우는것을 좋아하는 개발자입니다.

0개의 댓글