[#2] react-recoil-todo

undefcat·2021년 4월 28일
1

react-recoil-todo

목록 보기
2/5
post-thumbnail

react-recoil-todo

지난 포스팅에서는 기본 컴포넌트를 구성해보았습니다. 이번 시간에는 recoil을 사용하여 기본 todo 아이템들을 뿌려주는 것까지 만들어보겠습니다.

App 컴포넌트 구성

Header Main Footer InfoApp에 포함하도록 합니다.

// src/App.js

import Header from './components/Header'
import Main from './components/Main'
import Footer from './components/Footer'
import Info from './components/Info'

function App() {
  return <>
    <section className="todoapp">
      <Header />
      <Main />
      <Footer />
    </section>
    <Info />
  </>
}

export default App

각 컴포넌트들을 index.js 파일로 생성했기 때문에 디렉터리 경로까지만 import를 하여도 기본값으로 index.js를 가져옵니다.

현재까지의 결과화면은 아래와 같습니다.

TODO 뿌려주기

지금까지 기본 화면을 구성하였습니다. 이제 todo 아이템들을 보여주는 것으로 구현을 시작해보도록 하겠습니다.

Todo 컴포넌트 분리

TODO 아이템들은 Main 컴포넌트에 존재하므로 Main 컴포넌트를 열어줍니다.

// src/components/Main/index.js

function Main() {
  return (
    <section className="main">
      <input id="toggle-all" className="toggle-all" type="checkbox" />
      <label htmlFor="toggle-all">Mark all as complete</label>
      <ul className="todo-list">
        <li className="completed">
          <div className="view">
            <input className="toggle" type="checkbox" checked />
            <label>Taste JavaScript</label>
            <button className="destroy" />
          </div>
          <input className="edit" value="Create a TodoMVC template" />
        </li>
      </ul>
    </section>
  )
}

export default Main

ul 안에 li가 존재합니다. 각 li가 하나의 TODO를 표현하기 때문에, 얘를 컴포넌트로 빼주도록 하겠습니다. src/components/Main/Todo.js를 하나 새로 만듭니다.

// src/components/Main/Todo.js

function Todo(props) {
  return (
    <li className="completed">
      <div className="view">
        <input className="toggle" type="checkbox" checked />
        <label>Taste JavaScript</label>
        <button className="destroy" />
      </div>
      <input className="edit" value="Create a TodoMVC template" />
    </li>
  )
}

export default Todo

Todo 컴포넌트의 데이터는 Main에서 뿌려주기 때문에, props로 받습니다.

참고로 recoilreact hook을 기반으로 동작하기 때문에, Class 컴포넌트가 아닌 Function 컴포넌트들로 구성하도록 합니다.

Todo 컴포넌트의 루트 태그인 li의 클래스에는 editingcompleted 두 개의 의미있는 클래스가 todomvc-app-css에 정의되어 있습니다. editing의 경우 Todo를 더블클릭했을 때 내용을 수정할 수 있는 input창이 보여지며 completed는 완료된 표시로 스타일이 나오도록
정의되어있습니다.

editing 기능은 마지막에 구현하도록 하고, 우선 Todo의 내용을 표시할 todo 데이터를 정의해보도록 하겠습니다.

atom

이제 recoil이 등장할 때입니다. recoil의 메인 페이지에서는 recoil을 아래와 같이 정의하고 있습니다.

그저 React의 state를 관리하는 라이브러리입니다. 따라서 어떤 상태, 즉 데이터들이 있고 이를 쉽게 관리해주는 라이브러리라고 생각하면 됩니다.

우리는 todos의 상태를 먼저 정의할 겁니다. src/state 디렉터리를 생성하고 하위에 todos.js 파일을 아래와 같이 생성합니다.

// src/state/todos.js

import { atom } from 'recoil'

let uniqId = 0

export const createTodo = text => ({
  id: ++uniqId,
  done: false,
  text,
})

export const todos = atom({
  key: 'todos',
  default: [
    createTodo('react 공부하기'),
    createTodo('recoil 공부하기'),
  ],
})

하나하나 살펴보도록 하겠습니다.

import { atom } from 'recoil'

// ...

export const todos = atom({
  key: 'todos',
  default: [
    createTodo('react 공부하기'),
    createTodo('recoil 공부하기'),
  ],
})

atom은 어떤 하나의 상태를 정의하는 recoil 함수입니다. key는 모든 recoil 상태들 중 유일한 값이어야 하며, default로 기본값을 정의할 수 있습니다.

let uniqId = 0

export const createTodo = text => ({
  id: ++uniqId,
  done: false,
  text,
})

React에서는 같은 컴포넌트를 여러개 뿌려줄 때 key 속성이 필요합니다. 이 key는 형제들중 유일한 값이어야 하는데, 따라서 새로운 Todo가 생성될 때마다 uniqId 값을 이용해 유일한 값을 만들어 줄겁니다.

이를 위해 새로운 todo를 생성하는 createTodo 함수를 만들고, 이 함수로 todo 객체를 생성하도록 합니다. 이 때 ++uniqIdid값이 유일하도록 보장하도록 합니다.

이제 정의된 todos state를 Main 컴포넌트에서 가져와서 사용하겠습니다.

useRecoilValue

useRecoilValue에서 use prefix는 react hook의 그 use와 같은 맥락입니다. 일반적으로 react hook을 살펴보면

import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  
  // ...
}

위와 같이 기본값 0을 정의하면서 useState로 컴포넌트에 훅을 걸어줍니다. 따라서 setCount가 호출될 때마다 해당 컴포넌트가 값의 변화를 인지하여 리렌더링 되도록 도와줍니다.

recoil 역시 마찬가지입니다. 우선 값인 state를 정의하고(위의 경우 atom으로 정의), 이를 useRecoilValue로 컴포넌트에 훅을 걸어줍니다.

따라서, react hook과 마찬가지로 useRecoilValue 역시 리액트 컴포넌트 안에서 호출되어야 합니다.

이를 적용하여 만든 Main 컴포넌트는 아래와 같습니다.

// src/components/Main/index.js

import { useRecoilValue } from 'recoil'
import * as state from '../../state/todos'

import Todo from './Todo'

function Main() {
  // state.todos는 atom으로 정의된 state입니다.
  // useRecoilValue는 이 state의 변화를 Main 컴포넌트가 감지할 수 있도록 hook을 겁니다.
  const todos = useRecoilValue(state.todos)
  const Todos = todos.map(todo => <Todo key={todo.id} todo={todo} />)

  return (
    <section className="main">
      <input id="toggle-all" className="toggle-all" type="checkbox" />
      <label htmlFor="toggle-all">Mark all as complete</label>
      <ul className="todo-list">
        { Todos }
      </ul>
    </section>
  )
}

export default Main

현재 todos에는 2개의 값이 들어있으므로 아래와 같은 화면이 나올 것입니다.

이제 다시 Todo 컴포넌트로 돌아가서, 전달받은 props를 뿌려주도록 수정하면 되겠습니다.

// src/components/Main/Todo.js

function Todo(props) {
  const { id, done, text } = props.todo

  return (
    <li className={ done ? 'completed' : '' }>
      <div className="view">
        <input className="toggle" type="checkbox" checked={ done } />
        <label>{ text }</label>
        <button className="destroy" />
      </div>
      <input className="edit" value="Create a TodoMVC template" />
    </li>
  )
}

export default Todo

그러면 이제 아래와 같이 나옵니다.

정리

todos를 뿌려주는 것까지 해봤습니다. recoil은 react hook을 바탕으로 만들어진 라이브러리이기 때문에 상당히 심플합니다. 지금까지 배운 recoil 내용을 정리해보면

  1. atom 으로 상태를 정의합니다.
  2. useRecoilValueatom을 구독합니다.

끝입니다. 참 쉽죠?

다음 시간에는 selectoruseRecoilState를 이용하여 All, Active, Completed 등 상태에 따른 todos 목록 보여주는 기능을 구현해보도록 하겠습니다.

profile
undefined cat

0개의 댓글