【React】TypeScriptを使用してReact Hooksを書く
📅 May 21, 2020
•⏱️7 min read
こんにちは、カツヒロです。
この頃は、もっぱらJS系、中でもReact関連の勉強にハマっています。今回は、Reactと相性が良いと言われているTypeScriptとReact Hooksの書き方について記事を書きたいと思います。
TypeScriptで型概念を追加することで、エラーが起こりにくく安全性の高いコードを書くことが可能になります。TypeScriptは、JSの後継として今後のデフォルト言語になる可能性が高いので、この機会ぜひ覚えることをオススメします。
Reactで型を使うのが初めてだったり、Hooksの機能にまだ自信がなくても理解できると思いますので、ぜひ読んでみてください。
useStateをTSを使用して書く
useStateはHooksのもっとも基本的な機能です。かつてはクラス型コンポーネントに限定されていたstateでの状態管理を、関数型コンポーネントでも使用可能にしました。
そんなuseStateフックをTypeScriptで書くと下記のようなものになります。(下記の例では、かんたんなリストコンポーネントを作成しています。)
import React, { useState } from 'react';
interface IState {
id: number;
name: string;
price: number;
}
const App: React.FC = (): JSX.Element => {
// useState<型>(初期値)とすることで、型を指定する
const [name, setName] = useState<string>('');
const [price, setPrice] = useState<number>(1000);
const [items, setItems] = useState<IState[]>([]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setItems([...items, {id: items.length + 1, name: name, price: price}]);
}
return (
<div>
<form onSubmit={handleSubmit}>
名前:<input value={name} onChange={ e => setName(e.target.value)}/>
価格:<input value={price} onChange={ e => setPrice(parseInt(e.target.value))}/>
<button>Add</button>
<table>
<tr>
<th>ID</th>
<th>名前</th>
<th>値段</th>
</tr>
{items.map((item: IState) => {
return(
<tr>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.price}</td>
</tr>
);
})}
</table>
</form>
</div>
);
}
export default App;
基本的な型の宣言は、Interface型で書きます。下記のコードは、itemsの初期値を設定するタイミングとリスト表示する際のアイテムの描画時に使用しています。
interface IState {
id: number;
name: string;
price: number;
}
「useState<型>(初期値)」とすることで、useStateの初期値を設定します。
関数型コンポーネントの宣言時には「React.FC」という型が使用可能です。これは「Functional Component」の略で、短縮して「FC」と宣言できます。引数の型には「JSX.Element」を指定し、その名の通り「JSX要素」にアノテーションする型です。
const App: React.FC = (): JSX.Element => {
// useState<型>(初期値)とすることで、型を指定する
const [name, setName] = useState<string>('');
const [price, setPrice] = useState<number>(1000);
const [items, setItems] = useState<IState[]>([]);
...
宣言していない型を使用した場合は、エラーとなるので気をつけましょう。
const App: React.FC = (): JSX.Element => {
// useState<型>(初期値)とすることで、型を指定する
const [name, setName] = useState<string>('');
const [price, setPrice] = useState<number>(1000);
setName('太郎'); // -> OK!
setPrice('二郎'); // -> 型と異なるのでエラー
useContextをTSを使用して書く
useContextは、Providerから渡された値を使用するフックです。これを利用することで、Props Drilling問題(Propsのバケツリレー)を解決することができる画期的なものです。
そんなuseContextをTSで記述すると以下のようになります。(下記のコードでは、かんたんなボタンの表示を行っています。)
import React, { useContext, createContext } from 'react';
interface Context {
theme: string;
color: string;
font: string;
}
// JSX記法では、下記のように記述
// const ThemeContext = createContext();
const ThemeContext = createContext<Partial<Context>>({});
const ThemeButton: React.FC = ({children}): JSX.Element => {
// Providerで指定した値をuseContext(Context名)で取得
const { color } = useContext(ThemeContext);
return <button style={{ backgroundColor: color }}>{children}</button>
}
const Toolbar: React.FC = (): JSX.Element => (
<ThemeButton>buttonA</ThemeButton>
);
// valueに設定されているプロパティは、呼び出すことでどこでも使用可能になる
const App: React.FC = (): JSX.Element => {
return (
<ThemeContext.Provider value={{ color: "orange" }}>
<Toolbar/>
</ThemeContext.Provider>
)
}
export default App;
コンテキストを使用するために、まずは「createContext()」を行います。
// JSX記法では、下記のように記述
// const ThemeContext = createContext();
const ThemeContext = createContext<Partial<Context>>({});
<Partial<T>>
は、オプショナルな値を設定する型宣言です。つまり、<T>
の値がすべて揃っていなくてもエラーが起こりません。今回のコードでは、<IContext>
に初期値が入っていない場合にはエラーが起こってしまうようです。
// valueに設定されているプロパティは、呼び出すことでどこでも使用可能になる
const App: React.FC = (): JSX.Element => {
return (
<ThemeContext.Provider value={{ color: "orange" }}>
<Toolbar/>
</ThemeContext.Provider>
)
}
createContextを設定したら、Providerにvalueを設定します。コンテキストに設定されたvalueの値は、useContextを使用することで、子コンポーネントのどこでも利用可能です。
useReducerをTSを使用して書く
reducerの機能は、reduxやJSのreduce関数に詳しい方なら理解しやすいものかと思います。
import React, { useReducer } from 'react';
Interface IState {
count: number;
}
interface IAction {
type: "INCREMENT" | "DECREMENT";
}
const initialState: IState = { count: 0 };
const reducer = (state: IState, action: IAction) => {
switch(action.type){
case "INCREMENT":
return { count: state.count + 1};
case "DECREMENT":
return { count: state.count - 1};
}
}
const App: React.FC = (): JSX.Element => {
// useReducerは、stateとdispatchという変数を取得する
// 引数には、stateとactionを設定し初期値を設定する
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
</div>
)
}
export default App;
useReducerの初期設定は下記です。
const App: React.FC = (): JSX.Element => {
// useReducerは、stateとdispatchという変数を取得する
// 引数には、stateとactionを設定し初期値を設定する
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
</div>
)
}
「useReducer(reducer, initialState)」のように、useReducerに作成したreducer関数と初期値を引数として渡します。すると、返し値として「[state, dispatch]
」が返ってきます。
dispatch関数に、reducer関数のtypeを渡すと、設定していた機能が実行されます。
const reducer = (state: IState, action: IAction) => {
switch(action.type){
case "INCREMENT":
return { count: state.count + 1};
case "DECREMENT":
return { count: state.count - 1};
}
}
TypeScript + React Hooksのまとめ
上記で紹介したフック以外にも、React Hooksには便利なフックがたくさんありますが、基本的な機能を抑えるにはuseState、useContext、useReducerで十分だと思います。残りのフックは、実際にコードを書いていく中で覚えていきましょう。
TypeScriptで型概念を追加することで、エラーが起こりにくく安全性の高いコードを書くことが可能になります。TypeScriptは、JSの後継として今後のデフォルト言語になる可能性が高いので、この機会ぜひ覚えておきませんか?
TSの勉強には、下記のUdemy講座をオススメしています。型の概念とTypeScriptに付いて詳しく説明されているので、初心者の方やこれからTSにチャレンジしたい方にもオススメです。