こちらはパート2です。前回までの実装はこちらから
実装手順
ReactやFirebaseが導入されている前提で話を進めていきます。
長くなってしまったので2つに分けて投稿します。
以下の順番で話を進めていきます。
- ReactとReduxを連携
- StoreをReduxで管理
- Firebase Authenticationの実装
- ログイン画面の実装
- Reducksパターンを導入【ここから!】
- Operationの実装(Firebase記述)
- ActionとReducerの実装
- Selectorの実装
- ログイン状態によって遷移先を変える
使用したバージョン
macOS Big Sur Version 11.5.2 npm 7.20.3 node 15.11.0 react 17.0.2 react-redux 7.2.4 redux-thunk 2.3.0 reselect 4.0.0 connected-react-router 6.9.1
Reducksパターンを導入
いよいよRedux部分の状態遷移関連のコードを書いていきます。
まずReducksパターンなんですが、
ActionやReducerをどう綺麗に書いていくかの一つの指針というものと思ってください。
User関連のActionやReducerをまとめて一つのフォルダに記述することで
みやすくなるようにしています。
Reducksパターンは今回4つのデザインパターンが出てきます。
- Operation
- Action
- Reducer
- Selector
Operationの実装(Firebase記述)
Action前の複雑な初期を記述するエリア。
主には値検証や外部APIとの通信。
operation.js
const usersRef = db.collection('users')
export const signInWithEmailAndPassword = (email, password) => {
return async (dispatch) => {
// firebase authentication
return auth.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
var user = userCredential.user;
// Firestore
usersRef.doc(user.uid).get()
.then((doc) => {
if (!doc.exists) {
usersRef.doc(user.uid).set({
counter: 1
updateYMD: firebase.firestore.Timestamp.now()
})
dispatch(signInAction({isSignedIn: true}))
dispatch(push('/'))
}
else {
usersRef.doc(user.uid).set({
counter: doc.data().counter + 1
updateYMD: firebase.firestore.Timestamp.now()
})
dispatch(signInAction({isSignedIn: true}))
dispatch(push('/'))
}
})
.catch((error) => {
alert(error)
})
})
.catch((error) => {
alert(error.message)
});
}
}
今回ログインされた回数をカウントするため、
- 初めてのログイン → 初期値を定義する
- 2回目以降のログイン → 元の値に+1する
これらをすることでログイン回数をカウントしています。
ActionとReducerの実装
operationにて外部APIより認証が完了したら、その旨をReduxで状態遷移で管理していきます。
Storeのサインインしているかどうかという変数を変えたいのですが、
Storeを触れるのはreducerだけです。
そこでactionがreducerにサインインしましたよー、という情報を渡します。
そのコードが以下のようになります。
action.js
export const SIGN_IN = "SIGN_IN";
export const signInAction = (userState) => {
return {
type: SIGN_IN,
payload: userState
}
}
いよいよStoreの値を更新します。
SIGN_INという状態の時にstoreを更新します。
returnの中身に「…」という文字が使われていますが、
これは前のstateの状態とactionからのpayloadをUNIONしているイメージです。
defaultではstateを返しておきましょう。
でないと、Storeの初期値がありません!というエラーが出力されてしまいます。
reducer.js
import * as Actions from './actions';
export const UserReducer = (
state = {
isSignedIn: false
},
action
) => {
switch(action.type) {
case Actions.SIGN_IN:
return {
...state,
...action.payload
};
default:
return state
}
}
export default UserReducer;
Selectorの実装
Storeの値は更新することができました。
次はメイン画面にアクセスしてきた時、ログインされているかどうかを判定するための
メソッドをSelectorというデザインパターンに記述していきます。
selector.js
import { createSelector } from "reselect";
const userSelector = (state) => state.user;
export const getSignedIn = createSelector(
[userSelector],
state => state.isSignedIn
)
自作のgetSignedInメソッドへアクセスすることで
Store内のisSignedInの値を読み込むことができます。
いよいよ終わりが見えてきました!
ログイン状態によって遷移先を変える
App.jsにてSelectorでログイン状態を確認し、それにより画面遷移を変える処理を追加します。
ログインされた時の遷移先画面であるMain.jsのコードも一様置いておきます。
App.js
import React from 'react';
import {Route, Switch} from 'react-router';
import SignIn from './components/SignIn';
import Main from './components/Main';
const checkAuth = () => {
const userSelector = useSelector(state => state.users)
const loginedUser = getLoginedUser(userSelector)
if (!loginedUser) {
return false
}
else {
return true
}
}
const App = () => {
return (
<div className="App">
<Switch>
<Route exact path="/" render={() => (
checkAuth() ? (
<Route exact path="/" component={Main} />
) : (
<Route exact path="/SignIn" component={SignIn}/>
)
)}/>
</Switch>
</div>
);
}
export default App;
Main.js
import React from 'react';
const Main = () => {
return (
<div>
Hello Main!
</div>
);
}
export default Main;
selectorによりログイン状態を把握し、
JSX記法にて3項演算子を使用しています。ログインされたいたらMain画面にアクセスし、それ以外ならログイン画面に遷移するようになっています。
まとめ
あとはnpm startを実行し、signInディレクトリにアクセスするとログイン状態によって遷移する画面が変わると思います。
なんかエラーで動かない!ってなったら一報ください。
余談ですが、Reactはファイル管理が煩雑になるなぁ、と思っていたのですが、Reducksパターンを導入して少しみやすくなったと思います。
コメント