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/
댓글