본문 바로가기
React.js

Jotai - 7GUIS 튜토리얼 - 1

by Zih0 2021. 10. 11.

Jotai를 만든 다이시 카토상의 블로그에서 튜토리얼을 보고, 실습해볼 겸 작성하는 회고입니다.

다이시상은 Xstate(유한상태기계)의 7GUIS Tasks 튜토리얼을 보고 Jotai로도 7GUIS Task 튜토리얼을 만들어야겠다고 생각했다고 합니다.

Jotai를 배울 때 좋은 자료가 될 것이며 7가지 튜토리얼을 진행하면 기본은 물론 응용까지 가능 할 것이라고 생각됩니다.

 

7GUIS Tasks에 대한 설명 :

https://eugenkiss.github.io/7guis/tasks

 

 

이번 글에선 튜토리얼 1, 2, 3에 대한 글을 작성했습니다. 

Task1 : Counter

아마 상태관리 라이브러리를 접하신 분이라면 공식문서에서 봤을 카운터 입니다.

Atom 정의

count 정보를 담을 baseCountAtom과 수정하고 읽을 countAtom을 생성합니다.

// atoms.ts
import { atom } from "jotai";

const baseCountAtom = atom(0);

export const countAtom = atom(
  (get) => get(baseCountAtom),
  (_get, set) => {
    set(baseCountAtom, (c) => c + 1);
  }
);

Atom 적용

생성한 atom을 useAtom 을 통해 불러와 적용시켜줍니다.

// App.tsx
import { useAtom } from "jotai";
import { countAtom } from "./atoms";

const Counter = () => {
  const [count, inc] = useAtom(countAtom);
  return (
    <>
      <input value={count} readOnly /> <button onClick={inc}>Count</button>
    </>
  );
};

const App = () => (
  <div className="App">
    <Counter />
  </div>
);

export default App;

Task2 : Temperature Converter

섭씨온도를 화씨온도로, 화씨온도를 섭씨온도로 나타내는 튜토리얼입니다.

Atom 정의

섭씨와 화씨에 대해 각각 Atom을 만들고, 값을 수정하고 읽을 Atom을 추가적으로 만들어 줍니다.

수정 할 때, 섭씨일 경우엔 화씨 상태를 읽고, 화씨를 섭씨로 변경해줍니다.

반대로 화씨일 경우에는 섭씨 상태를 읽고, 섭씨를 화씨로 변경합니다.

import { atom } from "jotai";

const c2f = (x: number) => x * (9 / 5) + 32;
const f2c = (x: number) => (x - 32) * (5 / 9);

const INITIAL_CELSIUS = 5;
// Number.prototype.toFixed()는 소수점 자리수를 지정하는 함수입니다.
const baseCelsiusAtom = atom(INITIAL_CELSIUS.toFixed(0));
const baseFahrenheitAtom = atom(c2f(INITIAL_CELSIUS).toFixed(0));

export const celsiusAtom = atom(
  (get) => get(baseCelsiusAtom),
  (_get, set, value: string) => {
    set(baseCelsiusAtom, value);
    const temp = Number(value);
    if (value && Number.isFinite(temp)) {
      set(baseFahrenheitAtom, c2f(temp).toFixed(0));
    }
  }
);

export const fahrenheitAtom = atom(
  (get) => get(baseFahrenheitAtom),
  (_get, set, value: string) => {
    set(baseFahrenheitAtom, value);
    const temp = Number(value);
    if (value && Number.isFinite(temp)) {
      set(baseCelsiusAtom, f2c(temp).toFixed(0));
    }
  }
);

Atom 적용

화씨 컴포넌트와 섭씨 컴포넌트를 만들어서 App으로 불러옵니다.

Jotai의 Atom은 사용할 경우, 구독(subscribe)기능을 지원하므로 각각의 atom이 변경되면, 변경사항에 따라 리렌더링이 됩니다.

import { useAtom } from "jotai";
import { celsiusAtom, fahrenheitAtom } from "./atoms";

const Celsius = () => {
  const [value, setValue] = useAtom(celsiusAtom);
  return (
    <>
      <input value={value} onChange={(e) => setValue(e.target.value)} /> Celsius
    </>
  );
};

const Fahrenheit = () => {
  const [value, setValue] = useAtom(fahrenheitAtom);
  return (
    <>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      Fahrenheit
    </>
  );
};

const App = () => (
  <div className="App">
    <Celsius /> = <Fahrenheit />
  </div>
);

export default App;

Task 3: Flight Booker

Atom 정의

import { atom } from "jotai";

//편도, 왕복에 대한 상태 Atom
export const flightOptionAtom = atom<"one-way flight" | "return flight">(
  "one-way flight"
);

// 날짜 표기를 위한 함수
const formatDate = (d: Date) =>
  `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;

// 날짜 문자열을 파싱해서 Date로 반환하는 함수
const parseDate = (s: string) => {
  if (!/^(\d+)\/(\d+)\/(\d+)$/.test(s)) {
    return null; // invalid format
  }
  const d = new Date(s);
  if (Number.isNaN(+d)) {
    return null; // invalid date
  }
  return d;
};

// Date 타입의 Atom을 생성(초기화)하는 함수
const createDateAtom = () => {
  const INITIAL_DATE = new Date();
  const baseDateAtom = atom({
    date: INITIAL_DATE as ReturnType<typeof parseDate>,
    str: formatDate(INITIAL_DATE)
  });
  const dateAtom = atom(
    (get) => get(baseDateAtom),
    (_get, set, dateStr: string) => {
      const date = parseDate(dateStr);
      set(baseDateAtom, {
        date,
        str: date ? formatDate(date) : dateStr
      });
    }
  );
  return dateAtom;
};

// 위에서 만든 함수를 활용해 출국, 입국 날짜에 대한 Atom 생성
export const startDateAtom = createDateAtom();
export const returnDateAtom = createDateAtom();

// 예약과 관련된 Atom 생성
export const bookAtom = atom(
    // 예약 날짜가 제대로 입력(validation)되었는지 확인
  (get) => {
    const flightOption = get(flightOptionAtom);
    const startDate = get(startDateAtom).date;
    const returnDate = get(returnDateAtom).date;
    const isValid =
      startDate !== null &&
      (flightOption === "one-way flight" ||
        (returnDate !== null && startDate <= returnDate));
    return isValid;
  },
    // 편도, 왕복에 대한 각각의 결과 반환 
  (get, _set) => {
    const startDate = get(startDateAtom).date;
    if (startDate === null) {
      return;
    }
    if (get(flightOptionAtom) === "one-way flight") {
      window.alert(
        `You have booked a one-way flight on ${formatDate(startDate)}`
      );
      return;
    }
    const returnDate = get(returnDateAtom).date;
    if (returnDate !== null) {
      window.alert(
        `You have booked a return flight from ${formatDate(
          startDate
        )} to ${formatDate(returnDate)}`
      );
      return;
    }
  }
);

Atom 적용

import { useAtom } from "jotai";
import {
  flightOptionAtom,
  startDateAtom,
  returnDateAtom,
  bookAtom
} from "./atoms";

// 편도, 왕복을 선택하는 컴포넌트
const FlightOptionSelect = () => {
  const [flightOption, setFlightOption] = useAtom(flightOptionAtom);
  return (
    <div>
      <select
        value={flightOption}
        onChange={(e) =>
          setFlightOption(e.target.value as "one-way flight" | "return flight")
        }
      >
        <option value="one-way flight">one-way flight</option>
        <option value="return flight">return flight</option>
      </select>
    </div>
  );
};

// 출국 날짜를 입력받는 컴포넌트
const StartDateField = () => {
  const [startDate, setStartDate] = useAtom(startDateAtom);
  //문자열을 파싱 못할 경우 배경색이 red가 됨. 
    const style =
    startDate.date === null
      ? {
          backgroundColor: "red"
        }
      : {};
  return (
    <div>
      <input
        value={startDate.str}
        style={style}
        onChange={(e) => setStartDate(e.target.value)}
      />
    </div>
  );
};

// 입국 날짜를 입력받는 컴포넌트
const ReturnDateField = () => {
  const [returnDate, setReturnDate] = useAtom(returnDateAtom);
  const [flightOption] = useAtom(flightOptionAtom);
  const style =
    returnDate.date === null
      ? {
          backgroundColor: "red"
        }
      : {};
  return (
    <div>
      <input
        value={returnDate.str}
        style={style}
        disabled={flightOption !== "return flight"}
        onChange={(e) => setReturnDate(e.target.value)}
      />
    </div>
  );
};

// 예약 버튼에 대한 컴포넌트
const BookButton = () => {
  // 검증과 결과 
  const [isValid, book] = useAtom(bookAtom);
  return (
    <div>
      <button disabled={!isValid} onClick={book}>
        Book
      </button>
    </div>
  );
};

const App = () => (
  <div className="App">
    <FlightOptionSelect />
    <StartDateField />
    <ReturnDateField />
    <BookButton />
  </div>
);

export default App;

 

 

ref : https://blog.axlight.com/posts/learning-react-state-manager-jotai-with-7guis-tasks/

댓글