Make it to make it

いろいろ作ってアウトプットするブログ

React x TypeScriptの組み合わせミニマルサンプル

こちらのBen Awadのサンプルがよかったのでメモしておく。

github.com

ディレクトリ構造

.
├── 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>
  );
};