Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React x Socket.ioで人狼サーバを作る 第一章 フロントエンド実装

React x Socket.ioで人狼サーバを作る 第一章 フロントエンド実装

Avatar for Ryosuke Uchiyama

Ryosuke Uchiyama

February 11, 2022
Tweet

More Decks by Ryosuke Uchiyama

Other Decks in Technology

Transcript

  1. パッケージをインストール $ npm install –g yarn Yarn $ npm install

    –g typescript TypeScript $ yarn global add create-react-app create-react-app
  2. プロジェクト初期化 $ cd ./open-jinro-docker/ プロジェクトのディレクトリに移動 $ mkdir ./client $ mkdir

    ./server フロントエンド用とバックエンド用のサブディレクトリを作成 フロントエンドの初期化 $ cd ./client/ $ npx create-react-app . --template typescript バックエンドの初期化 $ cd ./server/ $ npm init $ yarn add --dev typescript $ npx tsc --init
  3. Component ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); index.tsx function

    App() { return ( <div className="App"> <header className="App-header"> // ... </header> </div> ); } App.tsx 画面の各要素を定義するクラスまたはメソッド
  4. Props const props: Props = { message: 'Hello, React!!', };

    ReactDOM.render( <React.StrictMode> <App message={props.message} /> </React.StrictMode>, document.getElementById('root') ); index.tsx コンポーネントに渡す引数(設定値) export type Props = { message: string, }; function App(props: Props) { const handleOnClick = () => { console.log(props.message); }; return ( <button onClick={handleOnClick}>ボタン</button> ); } export default App; App.tsx
  5. 問題提起 function App() { let i = 0; const handleOnClick

    = () => { console.log(i++); }; return ( <> <button onClick={handleOnClick}>ボタン</button> <p>現在の i の値は {i} です。</p> </> ); } App.tsx この実装のどこに問題があるのでしょうか…?
  6. もう一度見てみましょう function App() { let i = 0; const handleOnClick

    = () => { console.log(i++); }; return ( <> <button onClick={handleOnClick}>ボタン</button> <p>現在の i の値は {i} です。</p> </> ); } App.tsx 「変数の値が変化した」は再レンダリング条件になりません
  7. useState コンポーネントの状態を表す値 function App() { const [val, setVal] = useState<number>(0);

    const handleOnClick = () => { setVal((prev) => prev + 1); console.log(val); }; return ( <> <button onClick={handleOnClick}>ボタン</button> <p>現在の i の値は {val} です。</p> </> ); } App.tsx
  8. Why? RFClarification: why is `setState` asynchronous? · Issue #11527 ·

    facebook/react (github.com) Reactではステートの変更が即時反映されない場合がある - Breath Note (shinki.net) ↓開発元(Facebook)の超長い説明(英文) …は読む気にならないので日本語による解説↓
  9. useEffect コンポーネントの副作用を制御する function App() { const [val, setVal] = useState<number>(0);

    useEffect(() => { console.log(val); }, [val]); const handleOnClick = () => { setVal((prev) => prev + 1); }; return ( <> <button onClick={handleOnClick}>ボタン</button> <p>現在の i の値は {val} です。</p> </> ); } App.tsx
  10. React Hooks 機能 使途 useState 状態を管理する useEffect 副作用を管理する useContext グローバルなデータ空間で状態を管理する

    useReducer 状態とそれに関連する状態を同時に取り扱う useCallback コールバック関数をメモ化する useMemo 変数の値をメモ化する useRef 要素の参照を取得する useImperativeHandle 参照が使われた時に親コンポーネントに渡されるインスタンス値を改造する useLayoutEffect 同期的に副作用を呼び出す useDebugValue React DevToolsでカスタムフックのラベルを表示する
  11. 配列のレンダリング JSX内でArray.prototype.map()を使う const array: string[] = ['国語', '数学', '英語', '理科',

    '社会']; function App() { return ( <> <ul> {array.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> </> ); } App.tsx
  12. 条件付きレンダリング 論理積 (&&) の短絡評価を活用する function App() { const [checked, setChecked]

    = useState<boolean>(false); const handleCheckboxChanged = (e: React.ChangeEvent<HTMLInputElement>) => { setChecked(e.target.checked); }; return ( <> <input type='checkbox' id='chk' checked={checked} onChange={handleCheckboxChanged} /> <label htmlFor='chk'>チェック</label> {checked && ( <p style={{ fontWeight: 'bold', color: 'red' }}>チェックされてるよ!!</p> )} </> ); } App.tsx
  13. React Routerでルーティング $ yarn add react-router-dom ReactDOM.render( <React.StrictMode> <BrowserRouter> <Routes>

    <Route path='/' element={<App />} /> <Route path='/page1' element={<Page1 />} /> <Route path='/page2' element={<Page2 />} /> </Routes> </BrowserRouter> </React.StrictMode>, document.getElementById('root') ); index.tsx function App() { const navigate = useNavigate(); const handleToPage1 = () => { navigate('/page1'); }; const handleToPage2 = () => { navigate('/page2'); }; return ( <> <button onClick={handleToPage1}>Page1へ</button> <button onClick={handleToPage2}>Page2へ</button> </> ); } App.tsx
  14. 人狼ゲームの流れ 0日目 夜 無限ループ n = n + 1 n日目

    昼 (議論) ゲーム終了判定 ・役職が決定される ・ルール次第では、人狼は襲撃を行う ・ルール次第では、占い師は占いを行う ・人狼の数 = 0 のとき、市民側の勝利 ・市民の数 <= 人狼の数 のとき、人狼側の勝利 ・それ以外のとき、ゲーム続行 ・話し合いにより、追放するプレイヤーを決める
  15. 人狼ゲームの流れ ゲーム終了判定 n日目 夜 投票結果判定 n日目 昼 (投票) ・匿名投票により、追放するプレイヤーの多数決を取る ・最多得票者が1人の場合、そのプレイヤーを追放する(対象は以降のゲームに干渉できな

    い) ・最多得票者が複数の場合、再度議論のうえ、それらのプレイヤーから決戦投票を行う(決 戦投票の結果、最多得票者がなお複数の場合は誰も追放しない) ・人狼は市民1人を襲撃する(対象は以降のゲームに干渉できない) ・占い師はプレイヤー1人を占う(対象が人狼か否かが分かる) ・狩人(騎士)はプレイヤー1人を守護する(この夜、対象への襲撃は失敗する) ・霊媒師はこの昼に追放したプレイヤーが人狼だったか否かが分かる
  16. ルーム作成ページ (/create) • ユーザー名を入力 • 各役職の人数を入力(合計人数は自動計算) • 「戻る」押下→ トップページへ遷移 •

    「作成」押下→ランダムなユーザID (16桁) とルームID (8桁) をサーバ から取得後、ルーム入室リクエストを送信し待機室ページへ遷移 • ユーザー名が入力されていない、またはプレイ人数が最低人数に達し ていない場合は「作成」ボタンを非活性にする
  17. ルーム参加ページ (/join) • ユーザー名を入力 • ルームIDを入力 • 「戻る」押下→ トップページへ遷移 •

    「参加」押下→ランダムなユーザID (16桁) をサーバから取得後、ルー ム入室リクエストを送信し待機室ページへ遷移 • ユーザー名とルームIDが入力されていない場合は「参加」ボタンを非 活性にする
  18. 待機室ページ (/waiting) • ルームIDを表示(ボタン押下でクリップボードにコピー) • プレイ人数を表示 • 現在待機中のユーザー名を表示 • 「退室する」押下→サーバにルーム退室リクエストを送信しトップ

    ページへ遷移 • 「ゲーム開始」押下→サーバに状態更新リクエストを送信しゲーム ページへ遷移 • 参加ユーザー数がプレイ人数と一致しない場合は「ゲーム開始」ボタ ンを非活性にする • 「ゲーム開始」ボタンはホストユーザーのみに表示
  19. ゲームページ(議論) (/game) • 日数と進行フェーズを表示(以降同様) • ユーザーの役職を表示(以降同様) • 役職が人狼かつ人狼が複数人の場合、自分以外の人狼のユーザー名を 表示(以降同様) •

    「投票フェーズへ進む」押下→サーバに状態更新リクエストを送信 • 生存中のユーザー名を表示(以降同様) • 追放・襲撃されたユーザー名を表示(以降同様) • 追放・襲撃後は行動選択なし(以降同様) • 追放・襲撃後はユーザー名に役職を付加(以降同様)
  20. ゲームページ(夜の行動) (/game) • 各役職に応じて必要な選択ボタンを表示 • 人狼:襲撃対象 • 占い師:占う対象 • 霊媒師:この日の昼に追放されたプレイヤーが人狼だったか否か

    を表示+「翌日へ進む」ボタン • 狩人(騎士):襲撃から守る対象 • それ以外:「翌日へ進む」ボタンのみ • 各リクエスト送信後は全員の行動完了を待機した後、サーバから状態 更新完了を受信
  21. 参考文献 44 • any型で諦めない React.EventCallback – Qiita • React hooksを基礎から理解する

    (useState編) - Qiita • じゃけぇ『モダンJavaScriptの基本から始める React実践の教科書』(SBクリエイティブ、2021年) • asakohattori『基礎から学ぶReact/React Hooks』(C&R研究所、2021年) • aaa