ReactにTypeScriptを使おうとすると、TypeScriptの基礎知識だけでは足りず、Reactの型について知らなければなりません。それで、戸惑うことも多いでしょう。そういう人たちのためにReactとTypeScriptの使い方をまとめたのが「React+TypeScript Cheatsheets」です。本稿では「useReducer」の項を参考に、useReducer
フックで簡単なカウンターをつくってみます。
TypeScriptが加わったReactのひな型アプリケーションをつくる
TypeScriptを加えたReactのひな型アプリケーションは、Create React Appでつくりましょう。コマンドラインツールでnpx create-react-app アプリケーション名
に、オプション--template typescript
を添えてください。
npx create-react-app react-typescript-usereducer --template typescript
アプリケーションのディレクトリ(react-typescript-usereducer
)に切り替えて、npm start
とコマンド入力すれば、ローカルサーバでひな型アプリケーションが開きます。
カウンターをつくる
まずは、リデューサのモジュール(src/reducer.ts
)です。リデューサはJSX要素を返しませんので、拡張子は.tsx
でなく.ts
としました(「【TypeScript】.ts拡張子と.tsx拡張子」参照)。リデューサ(reducer
)に渡すふたつの引数(state
とaction
)に型づけが要ります。型エイリアス(type aliases)で定めました。
type STORE = { count: number };
type ACTIONTYPE =
| { type: "increment" }
| { type: "decrement" };
const reducer = (state: STORE, action: ACTIONTYPE) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
};
export default reducer;
つぎに、カウンターの表示と操作のモジュール(src/Counter.tsx
)です。ここで、useReducer
フックを使いました。TypeScriptの型推論が働きますので、型づけしなくても動きます。
import React from 'react';
import reducer from './reducer';
const initialState = { count: 0 };
const Counter = () => {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<div>
<button onClick={() => dispatch({ type: "decrement" })}>
-
</button>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: "increment" })}>
+
</button>
</div>
);
};
export default Counter;
ルートのモジュール(src/App.tsx
)は、カウンターのコンポーネント(Counter
)をimport
してJSX要素に差し込むように書き替えましょう。これでカウンターのアプリケーションは、とりあえず動きます(図001)。
// import React from 'react';
// import logo from './logo.svg';
import Counter from './Counter';
import './App.css';
function App() {
return (
<div className="App">
{/* <header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header> */}
<Counter />
</div>
);
}
export default App;
図001■ useReducerフックを使ったカウンター
型づけを加える
アロー関数式で変数(const
)に定めたリデューサをReactの関数として型づけするのが、React.Reducer
です。引数に状態とアクションを受け取って、状態を返します。引数の型はジェネリックで与えてください(「Typing a useReducer React hook in TypeScript」の「The type of useReducer」参照)。
import React from 'react';
// const reducer = (state: STORE, action: ACTIONTYPE) => {
const reducer: React.Reducer<STORE, ACTIONTYPE> = (state, action) => {
};
関数コンポーネントを型づけるのは、React.FunctionComponent
あるいはReact.FC
です(「React+TypeScript Cheatsheets」の「Function Components」参照)。引数があれば、やはりジェネリックで型を与えられます。ただし、これらの型はTypeScriptの基本テンプレートから除かれますので、これからは使うことをお勧めしません。
型とは別に、コールバック関数はuseCallbackでメモ化しておきましょう(「Create React App 入門 09: useCallbackフックで無駄な処理を省く」参照)。
// import React from 'react';
import React, { useCallback } from 'react';
const Counter = () => {
const decrement = useCallback(() => dispatch({ type: "decrement" }), []);
const increment = useCallback(() => dispatch({ type: "increment" }), []);
return (
<div>
{/* <button onClick={() => dispatch({ type: "decrement" })}> */}
<button onClick={decrement}>
-
</button>
{/* <button onClick={() => dispatch({ type: "increment" })}> */}
<button onClick={increment}>
+
</button>
</div>
);
};
アクションのtype
を変数に入れる
リデューサのモジュール(src/reducer.ts
)でアクションのtype
(ACTIONTYPE
)は、文字列のリテラル型(literal types)で定めました。ですから、型チェックはもちろん働きます。けれど、エディタのコード補完が利きません。そこで、変数に入れることにしましょう。ここでは、つぎのようにSymbol()
を用いました。
export const incrementType = Symbol('increment');
export const decrementType = Symbol('decrement');
type ACTIONTYPE =
// | { type: "increment" }
| { type: typeof incrementType }
// | { type: "decrement" };
| { type: typeof decrementType };
const reducer: React.Reducer<STORE, ACTIONTYPE> = (state, action) => {
switch (action.type) {
// case "increment":
case incrementType:
// case "decrement":
case decrementType:
}
};
型を定めた変数(decrementType
とincrementType
)はexport
しましたので、他のモジュールもimport
さえすればコード補完が利くのです。これで、タイプミスも減らせるでしょう。
// import reducer from './reducer';
import reducer, { decrementType, incrementType} from './reducer';
const initialState = { count: 0 };
const Counter = () => {
const [state, dispatch] = React.useReducer(reducer, initialState);
// const decrement = useCallback(() => dispatch({ type: "decrement" }), []);
const decrement = useCallback(() => dispatch({ type: decrementType }), []);
// const increment = useCallback(() => dispatch({ type: "increment" }), []);
const increment = useCallback(() => dispatch({ type: incrementType }), []);
};
でき上がった作例は、サンプル001としてCodeSandboxに公開しました。それぞれのモジュールのコードや、具体的な動きについてはこちらをご覧ください。