概要
タイトルの構成でログイン画面もどきを作りたい。
一旦暗号化とかエラーハンドリングとかそういうのは後回し。
今回はフロントエンド側の説明がメインです。
自分のコードをチームメンバーに解説するための資料&備忘録になるので間違い等あるかもしれません。
ご親切な方、もしよろしかったらご指摘ください。
構成
バック : Express / Node.js
フロント : React + Redex + Material UI
コンソール2つ用意してどちらもサーバを起動してる。
ポートはよしなに。。。
##Redux
Reduxに関する解説はこちらの記事を参照させてください。
Redux/react-reduxチートシート
たぶんこれが一番分かりやすいと思います React + Redux のフロー図解
ソースの解説
###index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger'
import reducer from './reducers'
import App from './containers/App'
const middleware = [ thunk ]
if (process.env.NODE_ENV !== 'production') {
middleware.push(createLogger())
}
const store = createStore(
reducer,
applyMiddleware(...middleware)
)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
public/index.htmlの<div id="root"></div>
にrenderしている。
reducerをインポートし、createStoreを行い<Provider store={store}>
でReactとReduxを連携している。
createStoreの記述内で非同期処理を行うためのredux-thunkとログ吐き出すためのredux-loggerの2つのmiddleaareを結びつける。
###containers/App.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Login from '../components/Login'
class App extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
dispatch: PropTypes.func.isRequired
}
render() {
const { value } = this.props
return (
<div>
<div>
<Login value={value} />
</div>
</div>
)
}
}
const mapStateToProps = state => {
const { value } = state.value || {
value: "please input"
}
return {
value: value
}
}
export default connect(mapStateToProps)(App)
mapStateToPropsではstateをpropsとして利用できるように設定します。
connectの引数としてmapStateToPropsとAppを設定することでReactとReduxが連携されています。
今回はここでvalueのデフォルト値を設定しています。
mapStateToPropsで定義したvalueは上述した通り、Reduxで管理され、
const { value } = this.props
を介して、Login.jsのコンポーネントに渡すvalueを取り扱います。
###components/Login.js
import React from 'react'
import PropTypes from 'prop-types'
import { Button, TextField } from "@material-ui/core";
import { fetchLogin } from "../actions";
import { connect } from "react-redux";
import "../css/Login.css"
export class Login extends React.Component {
constructor(props) {
super(props);
this.state = { email: "", pasword: ""};
}
fetchLogin = (email, password) => {
this.props.fetchLogin(this.state.email, this.state.password);
};
emailChange = (e) => {
this.setState({
email: e.target.value
});
}
passwordChange = (e) => {
this.setState({
password: e.target.value
});
}
render() {
console.log(this.value)
return (
<div className="loginForm">
<p className="loginTitle">
{this.props.value}
</p>
<ul className="loginFormDetail">
<br />
<TextField
label="e-mail"
name="email"
value={this.state.email}
onChange={this.emailChange}
margin="normal"
/>
<br />
<TextField
label="password"
name="password"
onChange={this.passwordChange}
type="password"
autoComplete="current-password"
margin="normal"
/>
<br />
<Button
className="sendButton"
variant="contained"
color="primary"
type="submit"
onClick={this.fetchLogin}>LOGIN
</Button>
</ul>
</div>
)
}
}
const mapStateToProps = state => ({
});
Login.propTypes = {
value: PropTypes.string.isRequired
}
const mapDispatchToProps = {
fetchLogin
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);
ログイン画面を表示するためのコンポーネントです。
render内部では
・TextField/email
・TextField/password
・Button/Login
の3つのパーツが用意されています。(気持ち程度のMaterial UI)
emailとpasswordについてはそれぞれonChangeで変更を検知してstateに反映する関数を用意しています。
LoginボタンのonClickで呼び出されるのはactions/index.jsのfetchLogin関数で、ReduxのActionが呼び出されることでAPIの結果がvalueに反映され、画面上の表示が更新されます。
emailとpasswordをReduxで管理しないのはログイン処理の後に持ち回る必要のない情報のためで、純粋にstateで管理しています。
mapDispatchToPropsに関しては上述したmapStateToPropsと同じように、ここで記述したDispatchをpropで利用することができます。
###actions/index.js
export const REQUEST_LOGIN = 'REQUEST_LOGIN'
export const receiveLogin = (json) => ({
type: REQUEST_LOGIN,
value: json.name
})
export const fetchLogin = (email, password) => dispatch => {
return fetch("http://略/login", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
email: email,
password: password
})
})
.then(response => response.json())
.then(function (json) {
console.log(json);
dispatch(receiveLogin(json));
})
}
Login.jsのLoginボタンを押すと呼び出されるコンポーネントでReduxにおけるAction部分を担っています。
emailとpasswordをPOSTするとAPI側から{"name":"onigiri"}
のようなデータが返却されます。
このデータをreceiveLoginで整形しdispatchを行うことでReducerが返却値を元にvalueを書き換えます。
###reducers/index.js
import { REQUEST_LOGIN } from '../actions'
const value = (state = { value: "" }, action) => {
switch (action.type) {
case REQUEST_LOGIN:
return {
...state,
value: action.value
}
default:
return state
}
}
const getLoginResult = (state = {}, action) => {
switch (action.type) {
case REQUEST_LOGIN:
return {
...state,
value: value(state.value, action)
}
default:
return state
}
}
export default getLoginResult
Reducerです。action/index.jsのfetchLoginでdispatchされるとgetLoginResultが呼び出され、valueの値を変更します。
##その他
Material UIを利用する上で、makeStylesを利用しようと思いましたがfunctionの中でしか呼び出せないらしく奮闘の末挫折しました。色々とfunction化したり頑張ったのですが一旦諦めさんです。