背景
社内でReactを使ったWEBアプリケーションを開発するにあたって、UIフレームワークの導入から認証までやってくれるようなサンプルが見当たらなかったので、今回作成したものをテンプレっぽくして共有します。
これさえあれば、とりあえず簡単なWEBアプリは作れるのでぜひ活用していただければと思います。
概要
起動方法
git clone https://github.com/tonio0720/React-App
cd React-App
# react 起動
cd frontend
npm i
npm start
# express 起動
cd backend
npm i
npm start
今回利用したもの
- React (Frontend Framework)
- Create React App
- Ant Design (UI Framework)
- Axios (HTTP client)
- echarts (Chart Library)
- Express (Backend Framework)
解説
Reactアプリ自体はcreate react app
を使って作成しました。
ただ色々と拡張する必要があったので、react-app-rewired
とcustomize-cra
を使いました。
UIフレームワークにはAnt Design
を使用しています。
バックエンドは認証処理をしたかったのでおまけ程度に書いています。
express-generator
を使って作りました。
認証にはexpress-jwt
を使用しています。
空の画面だけでさみしかったので、echartsでダッシュボードっぽくしてみました。
データは僕のQiitaのダッシュボードから持ってきました。(APIではなく直書きです。)
グラフにはecharts
を利用しています。
↓で詳細について説明してみます。
Ant Designの導入
Ant Design
はLESSで作られているので、Webpackで読み込める状態にする必要があります。
webpack
のバージョン次第では、localIdentName
が云々とエラーが出てしまうので、そちらの対応もしています。
const path = require('path');
const {
override,
disableEsLint,
fixBabelImports,
addLessLoader,
addWebpackAlias,
} = require('customize-cra');
const theme = require('./src/theme');
const modifyVars = {};
Object.keys(theme).forEach((key) => {
modifyVars[`@${key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}`] = theme[key];
});
const config = {
webpack: override(
disableEsLint(),
addWebpackAlias({
'@': path.resolve(__dirname, 'src')
}),
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
// ★ここから
addLessLoader({
javascriptEnabled: true,
modifyVars
}),
((config) => {
config.module.rules.forEach((rule) => {
if (!rule.oneOf) {
return;
}
rule.oneOf.forEach((rule) => {
if (!rule.use) {
return;
}
rule.use.forEach((loader) => {
if (loader.options && loader.options.localIdentName) {
const { localIdentName } = loader.options;
delete loader.options.localIdentName;
loader.options.modules = { localIdentName };
}
});
});
});
return config;
}),
// ★ここまで
),
devServer: (configFunction) => {
return (proxy, allowedHost) => {
const config = configFunction(proxy, allowedHost);
config.proxy = {
'/api': {
target: 'http://localhost:3030',
pathRewrite: { '^/api': '' }
}
};
return config;
};
},
};
module.exports = config;
JWT認証
Reactの認証処理はContextProvider
を使って実装しています。
ContextProvider
は下の階層にプロパティを引き渡すことができます。
ページ遷移の度に、バックエンドの/user/info
というところにリクエストを送り、検証をします。
成功の場合、userid
とトークンを保存します。
失敗の場合、ログインページに戻します。
import React, { useEffect, useState } from 'react';
import useReactRouter from 'use-react-router';
import { getToken, setToken, removeToken, gotoLogin } from '@/utils/auth';
import request from '@/utils/request';
export const AuthContext = React.createContext({});
export const AuthProvider = ({
children
}) => {
const { location: { pathname } } = useReactRouter();
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userid, setUserid] = useState(null);
const checkAuth = () => {
return request.post('/user/info', {});
};
useEffect(() => {
setIsLoggedIn(false);
if (pathname === '/login') {
return;
}
const token = getToken();
if (token) {
checkAuth().then(({
token,
userid
}) => {
setToken(token);
setIsLoggedIn(true);
setUserid(userid);
}).catch(() => {
removeToken();
gotoLogin();
});
} else {
gotoLogin();
}
}, [pathname]);
return (
<AuthContext.Provider
value={{
isLoggedIn,
userid
}}
>
{(isLoggedIn || pathname === '/login') && children}
</AuthContext.Provider>
);
};
ログインページからは/user/login
にリクエストを送り、usernameとpasswordを検証します。
成功した場合は、tokenが返ってくるのでCookieに保存します。
import React, { useState } from 'react';
import useReactRouter from 'use-react-router';
import {
Form,
Icon,
Input,
Button,
Checkbox,
Alert
} from 'antd';
import { setToken } from '@/utils/auth';
import request from '@/utils/request';
import styles from './index.module.less';
const LoginForm = ({
form
}) => {
const { history } = useReactRouter();
const [error, setError] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
form.validateFields((err, values) => {
setError(false);
if (!err) {
request.post('/user/login', values).then(({ token }) => {
setToken(token);
history.push('/');
}).catch(() => {
setError(true);
});
}
});
};
const { getFieldDecorator } = form;
return (
<Form onSubmit={handleSubmit}>
{error && (
<Alert
description="Password Incorrect."
type="error"
showIcon
style={{ marginBottom: 16 }}
/>
)}
<span>username: admin, password: admin</span>
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
autocomplete="off"
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true,
})(<Checkbox>Remember me</Checkbox>)}
<Button
type="primary"
htmlType="submit"
className={styles.loginFormButton}
>
Log in
</Button>
</Form.Item>
</Form>
);
};
export default Form.create({ name: 'login' })(LoginForm);
終わりに
Reactは便利ですが、部分的なサンプルが多くまとまったものが少ないので不便に感じている人も多いのではないでしょうか。
Reactを始めるきっかけにしてもらえればうれしいです!