yasudacloudの日記

札幌に住むソフトウェアエンジニア

【後編】FastAPIとReactでダブルホットリロード

後編になります。

前編ではReactとFastAPIのセットアップを行い、FastAPIで生成されるOpenAPIから手動でReact側に取り込むまでを行いました。

 

後編ではFastAPIで更新されるOpenAPIのスキーマを自動的にReact側に反映させてみます。

 

FastAPI ホットリロードの修正

まず、FastAPI側のホットリロードの部分をいじります。厳密にはホットリロードのタイミングを取得する事自体はかなり難しそうな(ドキュメントに見当たらない)ので、サーバーの開始イベントを使用します。こちらもホットリロードの際に呼び出されるため問題なさそうです。

 

main.pyに以下のように記述します。

@app.on_event("startup")
def start_up():
p = Process(target=on_restart)
p.start()

 

上記はstartupというstartupというイベント(サーバーの起動時)にstart_up関数が呼び出され、on_restartという関数が別プロセスで呼び出されています。わざわざ別ブロセスにしている理由は後述。

 

次に、on_restartの関数の中身

 

def on_restart():
command_path = 'openapi-generator-cli generate'
output_path = '~/Projects/react_app/src/http'
command = "{} -i http://127.0.0.1:8000/openapi.json -g typescript-axios -o {}".format(command_path, output_path)

print("generate before")
subprocess.run(command, shell=True)
print("generate after")

 

こちらでは前編で使ったOpenAPIの生成をシェルで呼び出しています。command_pathやoutput_pathなどは見ての通りコマンドやパスを指定しているだけなので適宜置き換えてください。前編でOpenAPI周りはやってしまったのでこちらもう完了です。

※先ほど別プロセスにしていた理由ですが、別プロセスにしないとシェルでhttp://localhost:8000/openapi.jsonを参照した時に処理が止まってしまうためです。

 

次に、画面で分かりやすく見るために適当なデータを一覧取得するAPIを書いてみます。

 

class User(BaseModel):
id: int
name: str
created: datetime


@app.get("/api/users", response_model=List[User])
async def users():
return [
User(
id=1,
name='ユーザー1',
created=datetime.now()
),
User(
id=2,
name='ユーザー2',
created=datetime.now()
),
User(
id=3,
name='ユーザー3',
created=datetime.now()
),
]

こんな感じでどうでしょう。response_modelで型を指定する事でOpenAPIのレスポンスにも型がつきます。

 

 

そしてReact側では上記を取得して表示します

const [users, setUsers] = useState<api.User[]>([])

useEffect(() => {
const client = api.UserApiFactory(undefined, 'http://localhost:8000')
client.usersApiUsersGet().then(response => {
setUsers(response.data)
})
}, [setUsers])


return (
<div className="App">
<div style={{margin: 20}}>
<table>
{
users.map(user => (
<tr>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.created}</td>
</tr>
))
}
</table>
</div>
</div>
);

 

最小限ですが、これで準備が整いました。

動画貼り付け出来なかったのでURLになります。

 

テスト1

https://assets.yasuda.cloud/20220318_movie1.mp4

 

上記ではOpenAPIに影響するコードを変更し、それによってOpenAPIが更新されて、React側でホットリロードがかかっています。

スト2

https://assets.yasuda.cloud/20220318_movie2.mp4

 

こちらではまず、OpenAPIに関係ないコードを修正しています。同じく自動的にOpenAPIのクライアントコードが生成されていますがReactのホットリロードは更新されていません。

これはwebpack側(watch)がファイルの更新と認識していないためだと思われます。その後、テスト1と同様にOpenAPIに影響するパスを変更しています。

これによってReactのホットリロードが走って画面のデータが変更されています。

なんとかダブルホットリロード成功です。

 

 

まとめ

何とか両方の更新を行うことができましたが、OpenAPI以外のコードでもリロードが走ってしまうのでJSONハッシュ値か何かを取得して、必要以上にクライアント側にリロードさせない(クライアントコードを生成しない)ことが重要かと思います。

あとM1 Macを使ってますが、フロント&バックエンドを同時開発するとメモリ16GBだと心もとないです。

 

webpackのホットリロードはまた近いうちに記事を書きたいと思います。