리액트(React): UI를 효과적으로 구축하기 위해 사용하는 자바스크립트 기반 라이브러리
검색 엔진 최적화 문제는 상당수 SPA 프로젝트가 가지는 문제이다.
Props를 입력받아 리액트 요소(React Element)를 반환하는 형태로 동작한다.
Props default 값 설정
Show.defaultProps = { name: '홍길동' }
개발간 무작위 이미지 가져오기 url
https://placeimg.com/가로크기/세로크기/any
변경될 수 있는 데이터를 처리할 때 사용
state값이 바뀌면 리액트는 리렌더링 실행
초기 구성: 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)
}
handleClick = () => {
this.setState(state => ({
isToggleOn: !this.state.isToggleOn
})
}
바인딩 처리는 state를 이용하는 경우에만 필요
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}
/>
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'
전달하는 데이터에 파일이 포함된 경우 설정
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/"
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();
데이터 조회
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
컬럼을 이용하여 삭제 구현
searchKeyword
state를 만들고 빈문자열('')로 초기화const filteredComponents = (data)=>{
data = data.filter((c)=> c.name.indexOf(this.state.searchKeyword)>-1);
return data.map((c)=>{
return <Customer info={c} />
});
}
초기에 모든 문자열은 빈문자열을 포함함으로 모든 데이터가 반환된다
<TableBody>
{filteredComponents(this.state.customers)}
<TableRow>