導入理由
Reactで状態を維持したい場合、グローバルステート管理が必要になります。
もちろん、propsを渡しまくって状態を管理する方法もありますが、
コンポーネントの無駄なレンダリングが増えたり、コードが複雑になりがちです。
グローバルステート管理といえばReduxが有名ですが、
個人開発のような小規模なプロジェクトでは、Reduxは正直オーバースペックだと感じました。
他に良い方法はないかと探していたところ、Zustandというライブラリを知り、導入してみました。
使い方
まずはインストールしましょう。
reactをインストールした時点では必要なライブラリというのが全て揃っているわけではありませんので、作成するアプリケーションに必要なものは自分でインストールする必要があります。
npm install zustand
僕はパッケージ管理をnpmで行っているので、上記のコマンドを使用しています。
yarn add zustand
yarnを使って依存関係を管理している方は上記のコマンドでインストールできるはずです。
問題なくインストールできていれば、package.jsonに追加されているはずです。
次はディレクトリを用意しましょう。
ディレクトリのどこで作成しても問題なく使えると思いますが、できるだけディレクトリ構成は意識していた方が開発の効率は上がるでしょう。
/src
/store
- useAuthStore.ts
App.tsx
index.tsx
ステータス管理はstoreディレクトリを用意して使うのが良いかなと思います。
状態管理ではstoreという用語はよく使いますのでこの名前にしています。
プロジェクトによってはstateディレクトリを使ったりするそうですが、
僕はstoreというのをよく見かけるので、storeにしています。
実務で使う際はプロジェクトのルールに則って進めましょう。
個人開発であれば自分がわかる名前でいいと思います。
コードの書き方
認証周りを例にしますね。
import { create } from 'zustand'
type User = {
id: number,
name: string
}
type AuthState = {
user: User | null
login: (user: User) => void
logout: () => void
}
export const useAuthStore = create<AuthState>((set)=>({
user: null,
login: (user)=> set({ user }),
logout: () => set({ user: null})
}))
解説していきますね。
import { create } from 'zustand'
export付きの関数を使用する際は上記のような書き方をします。
今回の場合、 zustandというモジュールの中にあるcreate関数を使用するための宣言です。
reactに限らずjavaScriptではこういったモジュールの概念は非常に重要視されてます。
このcreateは何をするかというと、storeを作成するための関数になっています。
storeというのはアプリケーション全体で共有できる箱のようなものだと思ってくれればいいです。
やってることはuseStateと似ています。
createもユーザーのアクションによって状態を変更します。
useStateがコンポーネント単位であるのに対して、
create関数はアプリ全体で変更を行います。
これさえ使えばコンポーネントにプロップスを渡して状態を管理するなんてことはしなくて済みます。
type User = {
id: number
name: string
}
これはreactの書き方ではなく、typeScriptの書き方です。
typeScriptの特徴は型付けが厳格なため、大規模な開発には特に適した言語です。
僕はtypeScriptで書くのが好きなので、今回はtypeScriptを使用しています。
User型というものをここでは作ってます。
idは必ず数値で、nameは文字列というルールを決めています。
これを関数に設定すると、それ以外の値(booleanなど)が入った場合はエラーになります。
今回の場合だと引数がidしかない、nameしかないというのもルール違反です。
多くてもダメです。 必ずidとnameの二つを引数として受け取らなくてはなりません。
type AuthState = {
user: User | null
login: (user: User) => void
logout: () => void
}
作成したタイプはさらに作るタイプに適用させることもできます。
こんな感じで userはUser型 もしくはnullになるようにしています。
loginは関数、引数はuserを受け取り、その型は先ほど定義したUser型です。
つまり、idとnameしか受け取らないようになっています。
export const useAuthStore = create<AuthState>((set) => ({
user: null,
login: (user) => set({ user }),
logout: () => set({ user: null }),
}))
こちらがメインです。
まずexportをつけることで、どのファイルからも使える状態にします。
これを忘れると、使えないので注意です。
useAuthStoreという名前の変数に create関数を格納していますね。
ちなみにこういうuse~~みたいな処理はreactでよく見かけると思います。
useState useEffectなど。
これらは自分で作成することもでき、頻繁に使いまわすものをuse~~という名前にしています。
こういうものをカスタムフックと呼んでいます。
create<AuthState>
先ほども出てきましたね。
createでグローバルステートを管理する箱を作成しています。
内部構造はAuthStateで指定した型に基づいて記述することをここで宣言しています。
コード内部を見てみると、
user login logoutが使われています。
これら一つでも足りないとエラーになるので注意。
set関数
これはzustandが持っている更新用の関数です。
useStateでよく使う set~~と同じです。
userの初期値は当然null。
login関数が使用されると、実行元から渡されたuserをsetすることで、
user: nullが user: {渡されたuser}になります。
logoutは見たまんまですね。
userをnullにすることで、ログアウト機能を実現しています。
おまけで
一応こんな感じでグローバルステート管理を行うことができるようにはなりました。
ただ実際に上記の処理を使ってみるとわかるのですが、
リロードすると、userがnullになります。
これだとせっかくログインしても、ページをリロードするたびにログアウト状態になります。
こういうときは、
Zustandのpersistミドルウェアを使いましょう。
persistとは?
簡単に言うと、
zustandのstateをローカルストレージに保存して、リロードしてもデータが消えないようにする機能です。
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
こんな感じでpersistをインポートします。
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
login: (user) => set({ user }),
logout: () => set({ user: null }),
}),
{
name: 'auth-storage', // ローカルストレージに保存されるキー名
}
)
)
ポイントはここ:
persistでくるむ
第二引数にオプションを渡す(nameプロパティで保存名を決める)
これだけです!
これでページをリロードしても、ログイン状態は保持できます。
注意点
ローカルストレージに保存されるので、セキュリティに注意(本番環境なら暗号化など考えたほうがいい)
logoutするときは、ストレージの中身もクリアされます(ここは心配いりません)
学習用にまとめたものですので、何か足りない部分があったりすればぜひ教えてください!