React와 Node.js를 활용한 고객 관리 시스템 개발

김성현·2021년 7월 11일
0

강의

목록 보기
7/9

React, Node

React JS 개요

리액트(React): UI를 효과적으로 구축하기 위해 사용하는 자바스크립트 기반 라이브러리

  • 선언적: 대화형 UI 작성에 유리, 데이터 변경시 효율적인 렌더링 수행
  • 컴포넌트 기반: 컴포넌트 상태 관리 및 효과적인 UI 구성
  • SSR지원을 통한 검색 엔진 최적화(SEO) 가능

검색 엔진 최적화 문제는 상당수 SPA 프로젝트가 가지는 문제이다.

리액트 컴포넌트

Props를 입력받아 리액트 요소(React Element)를 반환하는 형태로 동작한다.

Props default 값 설정

Show.defaultProps = {
  name: '홍길동'
}

개발간 무작위 이미지 가져오기 url

https://placeimg.com/가로크기/세로크기/any

state

변경될 수 있는 데이터를 처리할 때 사용
state값이 바뀌면 리액트는 리렌더링 실행

LifeCycle

초기 구성: constructor -> componentWillMount -> render -> componentDidMount

컴포넌트가 모두 구성된 직후인 componentDidMount에서 API호출

데이터 변경: shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

shouldComponentUpdate: 컴포넌트 업데이트 여부 결정(디폴트 true)

데이터 변경 후 UI변경 작업은 componentDidMount에서 수행

컴포넌트 해제
componentWillUnmount : 컴포넌트의 동작을 위해 사용되었던 메소드들의 리소스 제거(성능 향상에 사용)

LifeCycle 순서상 랜더링이 먼저 된 이후 API가 호출 됨으로 데이터 랜더링 시에 이부분을 고려해야한다. (ex, 조건부렌더링, 로딩컴포넌트 사용)

이벤트 처리

binding 처리 방법
1. 생성자함수 안에서 바인딩

constructor(props){
  super(props)
  this.state = {
    isToggleOn: true
  }
  this.handleClick = this.handleClick.bind(this)
}
  1. 화살표 함수 이용
handleClick = () => {
  this.setState(state => ({
    isToggleOn: !this.state.isToggleOn
  })
}

바인딩 처리는 state를 이용하는 경우에만 필요

시스템구현

Material UI 적용

npm i @material-ui/core

테이블 만들기

import Table from '@material-ui/core/Table';
import TableHead from '@material-ui/core/TableHead';
import TableBody from '@material-ui/core/TableBody';
import TableRow from '@material-ui/core/TableRow';
import TableCell from '@material-ui/core/TableCell';

const cellList=['번호','프로필 이미지','이름','생년월일','성별','직업','설정'];
return (
  <Table>
    <TableHead>
      <TableRow>
        {cellList.map(c=><TableCell>{c}</TableCell>)}
      </TableRow>
    </TableHead>
    <TableBody>
      {customers.map(c=><Customer />)}
    </TableBody>
  </Table>
)

Material css

import { withStyles } from '@material-ui/core/styles';

const styles = theme => ({
  root: {
    width: '100%',
    minWidth: 1080
  }
  /*
  클래스이름" {
    css속성
  }
  */
})

// 적용방법
const { classes } = this.props; // styles불러오기
<Table className={classes.root}>
//...
export default withStyles(styles)(App); //감싸기

모달 만들기

import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';

return (
  <Dialog open={this.state.open} onClose={this.handleClose}>
    <DialogTitle onClose={this.handleClose}>
    </DialogTitle>
    <DialogContent>
    </DialogContent>
    <DialogActions>
      <Button >확인</Button>
      <Button >취소</Button>
    </DialogActions>
  </Dialog>
)

폰트 적용

// index.css
@import url(http://fonts.googleapis.com/earlyaccess/notosanskr.css);
// index.js
import {MuiThemeProvider, createMuiTheme} from '@material-ui/core/styles';

const theme = createMuiTheme({
  typography:{
    fontFamily:'"Noto Sans KR",serif',
  }
})

ReactDOM.render(
  <React.StrictMode>
    <MuiThemeProvider theme={theme}>
    <App />
    </MuiThemeProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

고객 추가 기능 구현

파일 전송
1. file,fileName state 생성
2. input tag 만들기

handleFileChange = (e) =>{
  this.setState({
    file: e.target.files[0],
    fileName: e.target.value
  })
}

<input 
  type="file" 
  name="file" 
  accept='image/*' 
  file={this.state.file} 
  value={this.state.fileName} 
  onChange={this.handleFileChange} 
/>
  1. FormDate전송
addCustomer = () => {
  const url='/api/customers';
  const formData = new FormData();
  const config = {
    headers: { 'content-type':'multipart/form-data' }
    }
  return axios.post(url,formData,config);
}

'content-type':'multipart/form-data'
전달하는 데이터에 파일이 포함된 경우 설정

Node.js Express 서버

nodemon 설치

npm i -g nodemon

server.js

// 기본 코드
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = process.env.PORT || 5000;

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true}));

app.get('/api/hello', (req, res) => {
  res.send({message: 'Hello Express!'});
});

app.listen(port, ()=>console.log(`Listening on port ${port}`));

proxy 설정

/* client package.json */
"proxy": "http://localhost:5000/"

AWS RDS 서비스를 이용하여 MySQL DB 구축

  • RDS 데이터베이스 생성
  • 인코딩 설정(파라미터 그룹)
  • HeidiSQL 설치
  • 인바운드 규칙 편집(VPC 보안그룹)
  • HeidiSQL 세션 생성

DB와 Express 연동

const fs = require('fs');
const data = fs.readFileSync('./database.json');
const conf = JSON.parse(data);
const mysql = require('mysql');

const connection = mysql.createConnection({
    host: conf.host,
    user: conf.user,
    password: conf.password,
    port: conf.port,
    database: conf.database
});
connection.connect();

REAT API

데이터 조회

app.get('/api/customers',(req,res)=>{
    connection.query(
        "SELECT * FROM CUSTOMER WHERE isDeleted = 0",
        (err, rows, fields)=>{
            res.send(rows);
        }
    );
});

데이터 추가

const multer = require('multer');
const upload = multer({dest:'./upload'})
app.use('/image',express.static('./upload'));

app.post('/api/customers',upload.single('image'),(req,res)=>{
    const sql = 'INSERT INTO CUSTOMER VALUES (null,?,?,?,?,?,now(),0)';    
    const image = '/image/' + req.file.filename;
    const { name, birthday, gender, job} = req.body;
    const params = [image,name,birthday,gender,job];
    connection.query(sql,params,
        (err,rows,fields)=>{
            res.send(rows);
        });
});

데이터 삭제

app.delete('/api/customers/:id',(req,res)=>{
    const sql = 'UPDATE CUSTOMER SET isDeleted = 1 WHERE id = ?';
    const params=[req.params.id];
    connection.query(sql,params,
        (err,rows,fields)=>{
            res.send(rows);
        })
});

실제 DB에서 데이터를 삭제하는 것이 아니라 isDeleted컬럼을 이용하여 삭제 구현

고객 검색 기능 구현

  1. searchKeyword state를 만들고 빈문자열('')로 초기화
  2. filteredComponents함수 구현
const filteredComponents = (data)=>{
      data = data.filter((c)=> c.name.indexOf(this.state.searchKeyword)>-1);
      return data.map((c)=>{
        return <Customer info={c} /> 
      });
    }

초기에 모든 문자열은 빈문자열을 포함함으로 모든 데이터가 반환된다

  1. 컴포넌트 렌더링
<TableBody>
  {filteredComponents(this.state.customers)}
<TableRow>
profile
JS개발자

0개의 댓글