이번엔 React를 이용해 로그인 과정을 구현해보겠다.

참조한 링크는 이 곳이다.

소스는 기존 포스팅, React-Node-Mysql 통합환경구축을 기반으로 시작한다.

전체 파일 트리와 package.json은 다음과 같다.

// File tree
(project_name)
	└ (app)
		└ App.js
		└ Hello.js
		└ Home.js
		└ LoginPanel.js
		└ UserInfo.js
	└ (public)
		└ index.html
		└ login.html
		└ style.css
	└ (server)
		└ (config)
			└ db-config.json
		└ main.js
	└ package.json
	└ webpack.config.js
// Package.json
{
  "name": "third_react_comp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "clean": "rm -rf build public/bundle.js",
    "build": "./node_modules/.bin/babel server --out-dir build && ./node_modules/.bin/webpack --progress",
    "start": "rm -rf build public/bundle.js && ./node_modules/.bin/babel server --out-dir build && ./node_modules/.bin/webpack --progress && node ./build/main.js"
  },
  "author": "refgjin",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "~6.7.*",
    "babel-cli": "~6.9.*",
    "babel-loader": "~6.2.*",
    "babel-plugin-react-transform": "^2.0.2",
    "babel-preset-es2015": "~6.6.*",
    "babel-preset-react": "~6.5.*",
    "webpack": "^1.12.*",
    "webpack-dev-server": "^1.10.*"
  },
  "dependencies": {
    "babel-polyfill": "~6.7.*",
    "body-parser": "^1.17.1",
    "express": "^4.14.1",
    "mysql": "^2.11.1",
    "react": "^0.13.*",
    "react-addons-css-transition-group": "^15.4.2",
    "react-alert": "^1.0.14",
    "react-cookie": "^1.0.5",
    "react-dom": "^15.0.0",
    "whatwg-fetch": "^0.11.0"
  }
}

변경된 App.js 파일이다.

//App.js
import React from 'react';
import ReactDOM from 'react-dom';
import Home from './Home';

const root = document.getElementById("root");

React.render(<Home />, root); 

기존 코드에서 Hello.js를 직접 참조했던 것과 다르게 Home.js를 먼저 참조한다.

이유는 다음 코드를 보며 설명하겠다.

// Home.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import cookie from 'react-cookie';

import Hello from './Hello';
import LoginPanel from './LoginPanel';

class Home extends Component {
	componentWillMount(){
		this.state = {
			userId: ''
		};
		//adminId: cookie.load('adminId'),
	}

	onLogin(adminId){
		this.setState({
			userId:userId
		});
		//cookie.save('adminId',adminId, { path: '/'});
	}

	onLogout(){
		this.setState({
			userId:''
		});
		//cookie.remove('adminId', { path: '/'});
	}

	render(){
		if(!this.state.userId){
			return <LoginPanel 
					onSuccess={this.onLogin.bind(this)} 
					/>;
		}

		return <Hello 
			userId={this.state.userId}
			onLogout={this.onLogout.bind(this)}
			/>;
	}
}
export default Home;

Home 컴포넌트는 userId가 존재한다면 Hello 컴포넌트를 불러오며,

존재하지 않을 경우 LoginPanel 컴포넌트를 부른다.

// LoginPanel.js
import React, { Component , PropTypes } from 'react';
import ReactDOM from 'react-dom';
import 'whatwg-fetch';

import AlertContainer from 'react-alert';

class LoginPanel extends Component {
	constructor(){
		super(...arguments);
		this.state ={
			requestID:'',
			requestPW:''
		};
		this.alertOptions = {
			offset: 14,
			position: 'top right',
			theme: 'dark',
			time: 5000,
			transition: 'scale'
		};

		this.requestIDChange = this.requestIDChange.bind(this);
		this.requestPWChange = this.requestPWChange.bind(this);
	}

	onSubmit(){
		let userInfo={
			'user_id':this.state.requestID,
			'user_pw':this.state.requestPW
		};

		fetch('/login',{
			method: 'POST',
			headers:{
				'Content-Type': 'application/json'
			},
			body: JSON.stringify(userInfo)
	    }).then((response)=> response.json())
	    .then((responseData)=>{
	    	if(responseData.loginresult){
	    		this.props.onSuccess(this.state.requestID);
	    	}
	    	else{
	    		msg.show(`일치하는 ID와 PW가 없습니다.`
					, {
					time: 2000,
					type: 'error'
				});
				this.setState({
					requestID:'',
					requestPW:''
				});
	    	}
	    });
	}

	requestIDChange(event){
		this.setState({requestID: event.target.value});
	}
	requestPWChange(event){
		this.setState({requestPW: event.target.value});
	}

	render(){
		return (
			<div className="loginpanel">
				<div className="loginwindow">
					<AlertContainer ref={ (a) => global.msg = a} {...this.alertOptions} />
					<ul>
						<li className="title">Login Template</li>
						<li><input type="text" name="requestID" placeholder="Admin Id" value={this.state.requestID} onChange={this.requestIDChange}/></li>
						<li><input type="password" name="requestPW" placeholder="Password" value={this.state.requestPW} onChange={this.requestPWChange}/></li>
						<li><button className="loginwindowbutton" onClick={this.onSubmit.bind(this)}>로그인</button></li>
					</ul>
				</div>
			</div>
		);
	}
}
LoginPanel.propTypes={
	onSuccess: PropTypes.function
};
export default LoginPanel;

LoginPanel 컴포넌트는 fetch를 이용해 API 서버에서 아이디-비밀번호 검사를 하고 존재할 경우,

Home 컴포넌트에서 받은 onSuccess함수로 userId를 전달하며,

Home 컴포넌트의 state.userId가 갱신되는 순간 바로 unmount 된다.

// Hello.js
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import 'whatwg-fetch';

import UserInfo from './UserInfo';

// Parent Component
class Hello extends Component {
	constructor(){
		super(...arguments);
		this.state = {
			mans:[]
		};
	}

	componentDidMount(){
		fetch('/man',{
			method: 'get',
			dataType: 'json',
			headers:{
				'Accept': 'application/json',
				'Content-Type': 'application/json'
			}
		})
		.then((response) => response.json())
		.then((responseData) => {
			this.setState({mans: responseData});
		})
		.catch((error)=>{
			console.log('Error fetching man',error);
		});
	}
	render() {
		let mans = this.state.mans.map( (man) => {
			return <Guy
					name={man.name}
					age={man.age}
					createDateTime={man.create_datetime}
					{...man}/>
		});

		let userPanel = {
			return <UserInfo
				userId={this.props.userId}
				onLogout={this.props.onLogout}
				{...UserInfo}/>
		};

		return (
			<div>
				{userPanel}
				<h1>CalyFactory Developers</h1>
				<ul>
				{mans}
				</ul>
			</div>
		);
	}
}
// Child Component
class Guy extends Component {
	render() {
		return (
			<li>
				{this.props.name}, age is {this.props.age}. create time : {this.props.createDateTime}
			</li>
		);
	}
}
export default Hello;

변경된 Hello 컴포넌트다. 부모 컴포넌트로부터 userId와 onLogout을 받아서 UserInfo 컴포넌트에서 전달하며,

기존 기능은 동일하다.

// UserInfo.js
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';

class UserInfo extends Component {
	render(){
		return (
			<div className="userinfo">
				<li className="title">CalyFactory</li>
				<li>{''}{this.props.userId}  안녕하세요 ! <input type="button" className="userinfologoutbutton" value="로그아웃" onClick={() => this.props.onLogout()} /></li>
			</div>
		);
	}
};
UserInfo.propTypes={
	userId: PropTypes.string,
	onLogout: PropTypes.function
};
export default UserInfo;

UserInfo 컴포넌트는 Home 컴포넌트로부터 받은 userId를 렌더링하고,

onLogout함수를 로그아웃 버튼에 binding한다.

// main.js
import express from 'express';
import bodyParser from 'body-parser';
import mysql from 'mysql';
import path from 'path';


let dbconfig = require(__dirname+'/../server/config/db-config.json');
let connection = mysql.createConnection(dbconfig);

const app = express();
const port = 3000;

app.use('/', express.static(__dirname + "/../public"));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.get('/man', (req, res) =>{
	connection.query("SELECT * FROM man", (err, rows) => {
		if(err) throw err;

		res.send(rows);
	});
});

app.get('/login', (req, res) =>{
	connection.query("SELECT * FROM user WHERE user_id=\'"+req.body.user_id+"\' and user_pw=\'"+req.body.user_pw+"\'", (err, rows) => {
		if(err) throw err;

		if(rows.length>0)
			return res.send({loginresult:true,name:rows[0].user_name});
		else
			return res.send({loginresult:false});
	});
});

const server = app.listen(port, () => {
	console.log('Express listening on port', port);
});

마지막으로 변경된 server/main.js다.

app/loginPanel.js에서 요청한 /login에 대해 아이디-비밀번호 체크를 제공한다.