diff --git a/app/root.tsx b/app/root.tsx index 9fc6636..5bb7b76 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -25,7 +25,7 @@ export function Layout({ children }: { children: React.ReactNode }) { return ( - + diff --git a/app/routes.ts b/app/routes.ts index 102b402..a0ef982 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -1,3 +1,7 @@ -import { type RouteConfig, index } from "@react-router/dev/routes"; +import { type RouteConfig, index, route } from "@react-router/dev/routes"; -export default [index("routes/home.tsx")] satisfies RouteConfig; +export default [ + index("routes/home.tsx"), + route("state", "./work/01/index.tsx"), + +] satisfies RouteConfig; diff --git a/app/welcome/welcome.tsx b/app/welcome/welcome.tsx index 8ac6e1d..10a98ed 100644 --- a/app/welcome/welcome.tsx +++ b/app/welcome/welcome.tsx @@ -1,6 +1,8 @@ import logoDark from "./logo-dark.svg"; import logoLight from "./logo-light.svg"; +import { Link } from "react-router"; + export function Welcome() { return (
@@ -21,23 +23,11 @@
@@ -45,45 +35,3 @@
); } - -const resources = [ - { - href: "https://reactrouter.com/docs", - text: "React Router Docs", - icon: ( - - - - ), - }, - { - href: "https://rmx.as/discord", - text: "Join Discord", - icon: ( - - - - ), - }, -]; diff --git a/app/work/01/counter.ts b/app/work/01/counter.ts new file mode 100644 index 0000000..04a15f1 --- /dev/null +++ b/app/work/01/counter.ts @@ -0,0 +1,34 @@ +export interface Counter { + promise: Promise; + stop: () => void; +} + +export function createCounter( + seconds: number, + callback?: (passed: number, left: number) => void +) { + const handlerIdList: NodeJS.Timeout[] = []; + + const promiseList = [...Array(seconds + 1)].map((_, i, list) => { + const promise = new Promise((resolve) => { + const id = setTimeout(() => { + callback?.(i, list.length - i - 1); + resolve(); + }, i * 1000); + + handlerIdList.push(id); + }); + + return promise; + }); + + const promise = Promise.all(promiseList); + + const stop = () => { + handlerIdList.forEach((id) => { + clearTimeout(id); + }); + }; + + return { promise, stop } as Counter; +} diff --git a/app/work/01/index.tsx b/app/work/01/index.tsx new file mode 100644 index 0000000..d3cb1f6 --- /dev/null +++ b/app/work/01/index.tsx @@ -0,0 +1,137 @@ +import { useRef, useState } from "react"; +import { type Counter, createCounter } from "./counter"; +import "./style.css" + +const WAIT_SECONDS = 7; + +/** + * 7秒後に1億円もらえるボタン + * + * TODO: このプログラムには致命的なバグがある。それを修正し、正しく動作させること。 + */ +export default function App() { + const [wallet, setWallet] = useState(100000000); + const [passedCount, setPassedCount] = useState(0); + + const counterRef = useRef(null); + + const reset = () => { + setWallet(100000000); + setPassedCount(0); + }; + + const handleClickGet1Billion = () => { + const newCounter = createCounter(WAIT_SECONDS, (_passed: number, left: number) => + setPassedCount(left) + ); + + if (counterRef.current) { + counterRef.current.stop(); + } + counterRef.current = newCounter; + + const 財布に1億円追加する関数 = () => { + setWallet(wallet + 100000000); + }; + + const fn = async () => { + await newCounter.promise; // 7秒待つ + 財布に1億円追加する関数(); + } + + fn(); + }; + + return ( +
+ +
+ +

useStateを知る

+

{`${WAIT_SECONDS}秒後に1億円もらえるボタン`}

+ +

+ 財布の中身: + + ¥ + {wallet.toLocaleString()} + +

+ +

+ {passedCount > 0 && ( + + {passedCount}秒後に1億円ゲット!!! + + )} +

+ + +
+ +
+ この世の欲望 + + + + + + + +
+ +
+
+ + +
+

+ +  を押すと、{WAIT_SECONDS}秒後に1億円が財布に追加されます。
+ それまでに、豪遊の限りを尽くして、財布の中身を減らしてみましょう。
+ するとあなたは、このプログラムには致命的なバグがあることに気づくでしょう。 +

+

+ あなたはこのバグを修正し、このプログラムを正しく動作させることができますか? +

+

+ また、なにがバグの原因だったのか、言語化してみましょう。 +

+
+ +
+ ); +} diff --git a/app/work/01/style.css b/app/work/01/style.css new file mode 100644 index 0000000..b354a0f --- /dev/null +++ b/app/work/01/style.css @@ -0,0 +1,74 @@ +p { + line-height: 2; +} + +h1 { + font-size: 2em; + font-weight: bold; + padding-bottom: 0.5em; +} + +h2 { + font-size: 1.5em; + font-weight: bold; + padding-bottom: 0.5em; +} + +.App { + font-family: sans-serif; +} + +.box { + padding: 1em; +} + +.text-center { + text-align: center; +} + +.bordered { + border: 1px solid #808080; + border-radius: 0.4em; + padding: 1em; +} + +button:hover { + cursor: pointer; + opacity: 0.8; +} + +.button-rich { + border: none; + border-radius: 0.2em; + + padding-inline: .5em; + color: #222 +} + +.golden { + background: linear-gradient(45deg, + #b67b03 0%, + #daaf08 45%, + #fee9a0 70%, + #daaf08 85%, + #b67b03 90% 100%); + + border-right: 1px solid #b67b03; + border-bottom: 1px solid #b67b03; + + filter: drop-shadow(0.2em 0.2em 2px #b67b03); +} + +.silver { + background: linear-gradient(45deg, + #757575 0%, + #9e9e9e 45%, + #e8e8e8 70%, + #9e9e9e 85%, + #757575 90% 100%); + + border-right: 1px solid #757575; + border-bottom: 1px solid #757575; + + filter: drop-shadow(0.2em 0.2em 2px #757575); +}