Front/React

React Recoil 로 todolist 제작하기

oodada 2024. 11. 2. 20:57

React Recoil

리액트의 하나의 컴포넌트에서 데이터를 생성하거나 업데이트하거나 다른 컴포넌트와 데이터를 공유해서 사용하는 여러 방법이 있습니다.

  • state 와 props를 사용해서 컴포넌트 간에 데이터를 전달

  • React Context API를 사용해서 컴포넌트 간에 데이터를 전달
  • Redux, MobX, Recoil 등의 상태 관리 라이브러리를 사용

Recoil이란

  • Recoil은 React 상태 관리를 위한 라이브러리로, 전역 상태를 관리하기 위한 간단한 방법입니다.
  • Recoil은 atom과 selector를 사용하여 상태를 관리합니다.

Recoil 문법

  1. atom을 사용하여 상태를 생성합니다.
  2. useRecoilState를 사용하여 상태를 사용합니다.
  3. selector를 사용하여 파생된 상태를 생성합니다.

Recoil 사용하기

  • atom을 사용하여 상태를 생성합니다.
import { atom } from 'recoil';

const myState = atom({
  key: 'myState', // unique ID (with respect to other atoms/selectors)
  default: defaultValue, // default value (aka initial value)
});
  • useRecoilState를 사용하여 상태를 사용합니다.
import { useRecoilState } from 'recoil';

const [state, setState] = useRecoilState(myState);

Recoil을 사용한 Theme 예제

import React from 'react';
import { atom, useRecoilState } from 'recoil';

const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};

const themeState = atom({
  key: 'themeState',
  default: themes.light,
});

function App() {
  return (
    <ThemeProvider>
      <Toolbar />
    </ThemeProvider>
  );
}

function ThemeProvider({ children }) {
  const [theme, setTheme] = useRecoilState(themeState);

  return <div style={{ background: theme.background, color: theme.foreground }}>{children}</div>;
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const [theme] = useRecoilState(themeState);
  return <button style={{ background: theme.background, color: theme.foreground }}>I am styled by theme context!</button>;
}

export default App;

Recoil을 이용한 앱 만들기

  • 주문 페이지

여러 컴포넌트의 가격과 옵션을 공유하여 총액을 계산하여 보여주는 페이지를 만들어보겠습니다.

import React from 'react';
import { atom, useRecoilState } from 'recoil';

const orderState = atom({
  key: 'orderState',
  default: {
    price: 0,
    option: 'none',
  },
});

function App() {
  return (
    <OrderProvider>
      <Order />
      <Total />
    </OrderProvider>
  );
}

function OrderProvider({ children }) {
  return <div>{children}</div>;
}

function Order() {
  const [order, setOrder] = useRecoilState(orderState);

  const handleChange = e => {
    let price = 0;
    switch (e.target.value) {
      case 'sugar':
        price = 1000;
        break;
      case 'milk':
        price = 1500;
        break;
      case 'none':
      default:
        price = 0;
        break;
    }
    setOrder({ ...order, option: e.target.value, price: price });
  };

  return (
    <div>
      <h1>Order</h1>
      <select
        value={order.option}
        onChange={handleChange}
      >
        <option value="none">None</option>
        <option value="sugar">Sugar</option>
        <option value="milk">Milk</option>
      </select>
    </div>
  );
}

function Total() {
  const [order] = useRecoilState(orderState);

  return (
    <div>
      <h1>Total</h1>
      <p>Price: {order.price}원</p>
      <p>Option: {order.option}</p>
    </div>
  );
}

export default App;

- 할 일 관리 앱 Recoil 사용하기

// src/context/TodoContext.js
import React from 'react';
import { atom, useRecoilState } from 'recoil';

const initialState = [
  {
    id: 1,
    isDone: false,
    task: '고양이 밥주기',
    createdDate: new Date().getTime(),
  },
  {
    id: 2,
    isDone: false,
    task: '감자 캐기',
    createdDate: new Date().getTime(),
  },
  {
    id: 3,
    isDone: false,
    task: '고양이 놀아주기',
    createdDate: new Date().getTime(),
  },
];

const todoState = atom({
  key: 'todoState',
  default: initialState,
});

export function useTodoState() {
  return useRecoilState(todoState);
}
// src/components/App.js
import React from 'react';
import TodoHd from './TodoHd';
import TodoEditor from './TodoEditor';
import TodoList from './TodoList';
import { RecoilRoot } from 'recoil';

function App() {
  return (
    <RecoilRoot>
      <div>
        <TodoHd />
        <TodoEditor />
        <TodoList />
      </div>
    </RecoilRoot>
  );
}

export default App;
// src/components/TodoList.js
import React, { useState } from 'react';
import TodoItem from './TodoItem';
import { useTodoState } from '../context/TodoContext';

export default function TodoList() {
  const [todo, setTodo] = useTodoState();
  const [search, setSearch] = useState('');

  const onChangeSearch = e => {
    setSearch(e.target.value);
  };

  const filteredTodo = () => {
    return todo.filter(item => item.task.toLowerCase().includes(search.toLowerCase()));
  };

  return (
    <div>
      <h2>할 일 목록 📃</h2>
      <input
        value={search}
        onChange={onChangeSearch}
        placeholder="검색어를 입력하세요"
      />
      <ul>
        {filteredTodo().map(item => (
          <TodoItem
            key={item.id}
            {...item}
          />
        ))}
      </ul>
    </div>
  );
}
// src/components/TodoEditor.js
import React, { useState } from 'react';
import { useTodoState } from '../context/TodoContext';

export default function TodoEditor() {
  const [todo, setTodo] = useTodoState();
  const [task, setTask] = useState('');

  const onChangeTask = e => {
    setTask(e.target.value);
  };

  const onSubmit = () => {
    if (!task) {
      return;
    }
    setTodo([{ id: Date.now(), isDone: false, task, createdDate: new Date().getTime() }, ...todo]);
    setTask('');
  };

  const onKeyDown = e => {
    if (e.key === 'Enter') {
      onSubmit();
    }
  };

  return (
    <div className="TodoEditor">
      <h2>새로운 Todo 작성하기 ✏ </h2>
      <div>
        <input
          value={task}
          onChange={onChangeTask}
          onKeyDown={onKeyDown}
          placeholder="할 일을 추가로 입력해주세요."
        />
        <button onClick={onSubmit}>추가</button>
      </div>
    </div>
  );
}
// src/components/TodoItem.js
import React from 'react';
import { useTodoState } from '../context/TodoContext';

export default function TodoItem({ id, isDone, task, createdDate }) {
  const [todo, setTodo] = useTodoState();

  const onUpdate = () => {
    setTodo(todo.map(it => (it.id === id ? { ...it, isDone: !it.isDone } : it)));
  };

  const onDelete = () => {
    setTodo(todo.filter(it => it.id !== id));
  };

  return (
    <div>
      <li key={id}>
        <input
          type="checkbox"
          checked={isDone}
          onChange={onUpdate}
        />
        <span style={{ textDecoration: isDone ? 'line-through' : 'none' }}>{task}</span>
        <span>{new Date(createdDate).toLocaleDateString()}</span>
        <button onClick={onDelete}>삭제</button>
      </li>
    </div>
  );
}

'Front > React' 카테고리의 다른 글

React Native 심화  (1) 2024.11.02
React Native 시작하기  (0) 2024.11.02
조건부 렌더링 (Conditional Rendering) - React 배우기  (0) 2024.11.02
React의 핵심 개념  (1) 2024.10.27
Rudux로 전역 상태 관리하기  (0) 2024.05.04
티스토리 친구하기