🐥
APIをリクエストするCustom HooksをTypeScriptで書いてみよう
書くこと
- APIをリクエストする処理をCustom Hooksとして書く(axiosを使用してGetリクエスト)。
- TypeScriptで記述して型定義をする。
まずはJavaScriptでCustom Hooksを作成する
作成するCustom Hooksの概要
- 同じ会社に所属するユーザー全てを取得するAPIにリクエスト。
- 引数として
companyId
を受け取る。 - 返り値として
data
,error
,loading
をkeyとしたオブジェクトを返す。 -
data
はkeyをusers
, valueは配列の中にオブジェクトの形で返す。
dataサンプル
{ users: [
{ family_name: 'Endou', first_name: 'Shiki', company_id: 3 },
{ family_name: 'Tanaka', first_name: 'Tarou', company_id: 3 },
...
]
}
hooks/useFetchUsers.js
import axios from 'axios'
import { useEffect, useState } from "react";
export const useFetchUsers = (companyId) => {
// useStateでレスポンスの状態を管理する。
const [res, setRes] = useState({ data: null, error: null, loading: false })
// useFetchUsersを呼び出したときにfetchRequest関数を実行。
useEffect(() => {
fetchRequest(companyId)
}, [])
// '/api/v1/users'にリクエストする関数。
const fetchRequest = (companyId) => {
// リクエストが返ってくるまでは loading を true。
setRes(prevState => ({ ...prevState, loading: true }))
axios.get('/api/v1/users', {
params: {
companyId
}
}).then((response) => {
// 成功したら data に users を格納。
setRes({ data: response.data, error: null, loading: false })
}).catch(error => {
console.log(error);
// 失敗したら error に エラー情報を格納。
setRes({ data: null, error: error, loading: false })
}
return res
}
Custom Hooks を使用する
components/userList.jsx
import React, { useState } from 'react';
import { useFetchUsers } from 'common/hooks/users/use_fetch_users'
const userList = () => {
// オブジェクトの分割代入, Custom Hooksを実行。
const { data, error, loading } = useFetchUsers(3)
if (loading) return <div>...loading</div>
if (error) return <div>{error.message}</div>
return (
// dataの初期値はnullなので && を使用する。
<>
{ data && data.users.map(user => {
return (
<>
<div>{user.family_name}</div>
<div>{user.first_name}</div>
<div>{user.company_id}</div>
</>
)
}) }
</>
)
}
JavaScriptからTypeScriptに変更する
hooks/useFetchUsers.ts
// axiosにerrorの型定義があるのでimport。
+ import axios, { AxiosError } from 'axios'
import { useEffect, useState } from "react";
// dataの型定義。
+ interface IUser {
+ users: Array<{
+ family_name: string;
+ first_name: string;
+ company_id: number;
+ }>
+ }
// レスポンスの型定義。
+ interface IResponse {
+ data: IUser;
+ error: AxiosError;
+ loading: boolean;
+ }
- export const useFetchUsers = (companyId) => {
+ export const useFetchUsers = (companyId: number) => {
// ① 明示的な型指定が必要。
- const [res, setRes] = useState({ data: null, error: null, loading: false })
+ const [res, setRes] = useState<IResponse>({ data: null, error: null, loading: false })
useEffect(() => {
fetchRequest()
}, [])
const fetchRequest = () => {
setRes(prevState => ({ ...prevState, loading: true }))
// ② レスポンスの型を指定できるのでIUserを指定する。
- axios.get('/api/v1/users', {
+ axios.get<IUser>('/api/v1/users', {
params: {
companyId,
},
}).then((response) => {
setRes({ data: response.data, error: null, loading: false })
- }).catch(error => {
+ }).catch((error: AxiosError) => {
console.log(error);
setRes({ data: null, error, loading: false })
})
}
return res
}
data
, error
の初期値がnull
なので型推論に任せるとany
になってしまう
① 型推論に任せたケース
const [res, setRes] = useState({ data: null, error: null, loading: false })
// resの型定義 { data: any, error: any, loading: boolean }
axios.get
はGenerics
が使用されているのでレスポンスの型を指定できる
② axios.getの型定義
get<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
TypeScriptで書いたCustom Hooksを使用してみよう
-
ts
ファイルをimport
するので拡張子をtsx
に変更。
components/userList.tsx
import React, { useState } from 'react';
import { useFetchUsers } from 'common/hooks/users/use_fetch_users'
const userList = () => {
// ① data, error, loading にhoverをすると型情報を参照できる。
const { data, error, loading } = useFetchUsers(3)
if (loading) return <div>...loading</div>
if (error) return <div>{error.message}</div>
return (
// ② 'user.age'のような型定義されていないプロパティを参照するとエラーが表示される。
<>
{ data && data.users.map(user => {
return (
<>
<div>{user.family_name}</div>
<div>{user.first_name}</div>
<div>{user.company_id}</div>
<div>{user.age}</div>
</>
)
}) }
</>
)
}
data
, error
, loading
, data.users
にカーソルをhoverすると型情報が表示される
① 表示される型情報
data: IUser
error: AxiosError
loading: boolean
data.users: IUser.users: {
family_name: string;
first_name: string;
company_id: number;
}[]
data.users
に定義されていないプロパティを参照するとエディタ上でエラーが表示される
② 型安全
data.users[0].age
// エラーメッセージ
`Property 'age' does not exist on type '{ family_name: string; first_name: string; company_id: number; }'.`
間違っている箇所や他にもっと良い書き方があればコメントして頂けると嬉しいです。
Discussion