React x TypeScriptの組み合わせミニマルサンプル
こちらのBen Awadのサンプルがよかったのでメモしておく。
ディレクトリ構造
. ├── package.json ├── public │ └── index.html ├── src │ ├── App.tsx │ ├── Counter.tsx │ ├── ReducerExample.tsx │ ├── TextField.tsx │ ├── index.tsx │ └── react-app-env.d.ts ├── tsconfig.json └── yarn.lock
コード
Render propsと型推論の活用
index.tsx
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; ReactDOM.render(<App />, document.getElementById("root"));
App.tsx
import React from "react"; import { Counter } from "./Counter"; // props // hooks // render props const App: React.FC = () => { return ( <div> <Counter> {({ count, setCount }) => ( <div> {count} <button onClick={() => setCount(count + 1)}>+</button> </div> )} </Counter> </div> ); }; export default App;
Render propsでcounterの実装をしている。
Counter.tsx
import React, { useState } from "react"; interface Props { children: (data: { count: number; setCount: React.Dispatch<React.SetStateAction<number>>; }) => JSX.Element | null; } export const Counter: React.FC<Props> = ({ children }) => { const [count, setCount] = useState(0); return <div>{children({ count, setCount })}</div>; };
children
に関数と引数を渡すというもの。
setCount
のようなセッターの型をマニュアルで記述するのは大変だが、VSCodeのホバー時に型推論をサジェストしてくれるので、それをペーストする。
useReducer
の使用例
ReducerExample.tsx
import React, { useReducer } from "react"; type Actions = | { type: "add"; text: string } | { type: "remove"; idx: number; }; interface Todo { text: string; complete: boolean; } type State = Todo[]; const TodoReducer = (state: State, action: Actions) => { switch (action.type) { case "add": return [...state, { text: action.text, complete: false }]; case "remove": return state.filter((_, i) => action.idx !== i); default: return state; } }; export const ReducerExample: React.FC = () => { const [todos, dispatch] = useReducer(TodoReducer, []); return ( <div> {JSON.stringify(todos)} <button onClick={() => { dispatch({ type: "add", text: "..." }); }} > + </button> </div> ); };
useState
, useRef
の使用例
TextField.tsx
import React, { useState, useRef } from "react"; interface Person { firstName: string; lastName: string; } interface Props { text: string; ok?: boolean; i?: number; fn?: (bob: string) => string; person: Person; handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void; } interface TextNode { text: string; } export const TextField: React.FC<Props> = ({ handleChange }) => { const [count, setCount] = useState<TextNode>({ text: "hello" }); const inputRef = useRef<HTMLInputElement>(null); const divRef = useRef<HTMLDivElement>(null); return ( <div ref={divRef}> <input ref={inputRef} onChange={handleChange} /> </div> ); };