본문 바로가기
ICIA 수업일지

2021.09.14 수업일지(React.js)

by 주성씨 2021. 9. 14.

- 프로젝트

프론트 서버(React.js) - Client Server - 3000
백엔드 서버(Node.js) - Developer Server - 5000

 

- 디자인 프레임워크의 종류

부트스트랩(BootStrap)
머티리얼유아이(MatrialUI)
Ant

 

- Material-UI를 이용해 디자인해보겠다.

C:\data\react>cd ex04

C:\data\react\ex04>cd client

C:\data\react\ex04\client>yarn add @material-ui/core

 

- 완료 후

C:\data\react\ex04\client>cd..

C:\data\react\ex04>yarn dev

 

- 백엔드 서버 확인

[0] [nodemon] 2.0.12
[0] [nodemon] to restart at any time, enter `rs`
[0] [nodemon] watching path(s): *.*
[0] [nodemon] watching extensions: js,mjs,json
[0] [nodemon] starting `node server.js`
[0] server is listening at localhost:5000 // <-

 

- 이제 core를 이용해 디자인을 변경해보겠다.

- C:\data\react\ex04\client\src\components\CustomerItem.js

import React from 'react';
import { TableCell, TableRow } from '@material-ui/core';

const CustomerItem = ({customer}) => {
    const {id, image, name, birthday, gender, job} = customer
    return (
        <TableRow className="item">
            <TableCell>{id}</TableCell>
            <TableCell><img src={image} alt="profile"/></TableCell>
            <TableCell>{name}</TableCell>
            <TableCell>{birthday}</TableCell>
            <TableCell>{gender}</TableCell>
            <TableCell>{job}</TableCell>
        </TableRow>
    );
};

export default CustomerItem;

 

확인

 

- C:\data\react\ex04\client\src\components\Customer.js

import React, { useEffect, useState } from 'react';

import {Table, TableBody, TableRow, TableHead, TableCell, Paper} from '@material-ui/core';
import { withStyles } from '@material-ui/core/styles';
import CustomerItem from './CustomerItem';

const styles = (theme) => ({
    table: { minWidth: '800px' }
   });

const Customer = (props) => {

    const {classes} = props;

    const [customers,setCustomers] = useState([]);

    const callAPI=()=>{
        fetch('/api/customers')
        .then(res=>res.json())
        .then(json=>{
            setCustomers(json);
        })
    }

    // ,[] 한번만 호출하려면 빈배열을 추가한다.
    useEffect(()=>{
        callAPI();
    },[])

    return (
        <Paper className={classes.paper}>
            <h1>고객관리시스템</h1>
            <Table className={classes.table}>
                <TableHead>
                    <TableRow>
                        <TableCell>번호</TableCell>
                        <TableCell>이미지</TableCell>
                        <TableCell>이름</TableCell>
                        <TableCell>생년월일</TableCell>
                        <TableCell>성별</TableCell>
                        <TableCell>직업</TableCell>
                    </TableRow>
                </TableHead>
                <TableBody>
                    { customers.map(c => (
                    <CustomerItem key={c.id} customer={c}/>
                    ))}
                </TableBody>
            </Table>
        </Paper>
    );
};

export default withStyles(styles)(Customer);

 

- C:\data\react\ex04\client\src\components\CustomerItem.js

import React from 'react';
import { TableCell, TableRow } from '@material-ui/core';

const CustomerItem = ({customer}) => {
    const {id, image, name, birthday, gender, job} = customer
    return (
        <TableRow className="item">
            <TableCell>{id}</TableCell>
            <TableCell><img src={image} alt="profile"/></TableCell>
            <TableCell>{name}</TableCell>
            <TableCell>{birthday}</TableCell>
            <TableCell>{gender}</TableCell>
            <TableCell>{job}</TableCell>
        </TableRow>
    );
};

export default CustomerItem;

 

확인

 

- 프로그래바 UI를 사용하기 위해 아래 주소로 이동하여 예제를 참조한다. 

- C:\data\react\ex04\client\src\components\Customer.js

import React, { useEffect, useState } from 'react';

import {Table, TableBody, TableRow, TableHead, TableCell, Paper, CircularProgress} from '@material-ui/core';
import { withStyles } from '@material-ui/core/styles';
import CustomerItem from './CustomerItem';

const styles = (theme) => ({
    table: { minWidth: '800px', maxWidth:'1000px' }
   });

const Customer = (props) => {

    const {classes} = props;

    const [customers,setCustomers] = useState(null);
    const [loading, setLoading] = useState(false);

    // async await 비동기 처리 문법
    const callAPI=async()=>{
        const response = await fetch('/api/customers');
        const body = await response.json();
        return body;
    }

    useEffect(()=>{
        // 로딩중이니깐 true
        setLoading(true);
        // 성공시 then 실패시 catch
        callAPI().then(res=>{
            setCustomers(res);
            // 로딩 끝
            setLoading(false);
        })
        .catch(err => {
            console.log(err);
            setLoading(false);
        });
    },[])

    return (
        <Paper className={classes.paper}>
            <h1>고객관리시스템</h1>
            <Table className={classes.table}>
                <TableHead>
                    <TableRow>
                        <TableCell>번호</TableCell>
                        <TableCell>이미지</TableCell>
                        <TableCell>이름</TableCell>
                        <TableCell>생년월일</TableCell>
                        <TableCell>성별</TableCell>
                        <TableCell>직업</TableCell>
                    </TableRow>
                </TableHead>
                <TableBody>
                    { customers ? customers.map(c => (
                    <CustomerItem key={c.id} customer={c}/>
                    )) : ''}

                    { loading ? 
                        <TableRow>
                            <TableCell colSpan="6" align="center">
                                {/* progress 바 보이게 하기 */}
                                <CircularProgress className={classes.progress}/>
                            </TableCell>
                        </TableRow> : ''
                    }
                </TableBody>
            </Table>
        </Paper>
    );
};

export default withStyles(styles)(Customer);

 

확인

 

- 고객을 추가할 CustomerAdd.js를 만든다.

- C:\data\react\ex04\client\src\components\(new)CustomerAdd.js

import React from 'react';

const CustomerAdd = () => {
    return (
        <form>
            프로필이미지 : <input type="file" name="file"/><br/>
            이름 : <input type="text" name="userName"/><br/>
            생년월일 : <input type="text" name="birthday"/><br/>
            성별 : <input type="text" name="gender"/><br/>
            직업 : <input type="text" name="job"/><br/>
            <button type="submit">추가하기</button>
        </form>
    );
};

export default CustomerAdd;

 

....
import CustomerAdd from './CustomerAdd';

....

const Customer = (props) => {

....

    useEffect(()=>{
....
    },[])

    return (
        <Paper className={classes.paper}>
            <h1>고객관리시스템</h1>
            <Table className={classes.table}>
....
            </Table>
            <CustomerAdd/>
        </Paper>
    );
};

export default withStyles(styles)(Customer);

 

확인

 

- C:\data\react\ex04\client\src\components\CustomerAdd.js

import React, { useState } from 'react';

const CustomerAdd = () => {

    const [form, setForm] = useState({
        file:null,
        userName:'이순신',
        birthday:'450428',
        gender:'남자',
        job:'장군'
    });

    const {file, fileName, userName, birthday, gender, job} = form;

    return (
        <form>
            프로필이미지 : <input type="file" name="file"/><br/>
            이름 : <input type="text" name="userName" value={userName}/><br/>
            생년월일 : <input type="text" name="birthday" value={birthday}/><br/>
            성별 : <input type="text" name="gender" value={gender}/><br/>
            직업 : <input type="text" name="job" value={job}/><br/>
            <button type="submit">추가하기</button>
        </form>
    );
};

export default CustomerAdd;

확인

 

import React, { useState } from 'react';

const CustomerAdd = () => {

    const [form, setForm] = useState({
        file:null,
        userName:'이순신',
        birthday:'450428',
        gender:'남자',
        job:'장군',
        fileName:''
    });

    const {file, fileName, userName, birthday, gender, job} = form;

    const onChange = (e) =>{
        const newForm ={
            ...form,
            [e.target.name]:e.target.value
        }
        setForm(newForm);
    }

    const onFileChange = (e) => {
        const newForm = {
            ...form,
            file:e.target.files[0],
            fileName:e.target.value
        }
        setForm(newForm);
    }
    return (
        <form>
            프로필이미지 : <input type="file" name="file" value={fileName}/><br/>
            이름 : <input type="text" name="userName" value={userName} onChange={onChange}/><br/>
            생년월일 : <input type="text" name="birthday" value={birthday} onChange={onChange}/><br/>
            성별 : <input type="text" name="gender" value={gender} onChange={onChange}/><br/>
            직업 : <input type="text" name="job" value={job} onChange={onChange}/><br/>
            <button type="submit">추가하기</button>
        </form>
    );
};

export default CustomerAdd;

 

확인

 

- 고객 추가 양식(Form) 구현 및 이벤트 핸들링

1) 서버와의 비동기 통신을 위한 axios 라이브러리를 설치한다.

yarn add axios

 

2) 파일 업로드 처리 작업을 위해 multer 라이브러리를 설치한다. 

yarn add multer

 

- C:\data\react\ex04\package.json

{
    "name": "management",
    "version": "1.0.0",
    "private": true,
    "scripts": {
        "client": "cd client && yarn start",
        "server": "nodemon server.js",
        "dev": "concurrently --kill-others-on-fail \"yarn server\" \"yarn client\""
    },
    "dependencies": {
        "axios": "^0.21.4", <- 확인
        "concurrently": "^6.2.1",
        "express": "^4.17.1",
        "multer": "^1.4.3", <- 확인
        "mysql": "^2.18.1"
    }
}

 

- 서버 재구동

ctrl + `
yarn dev

 

3) submit을 위한 작업을 해보겠다.

- C:\data\react\ex04\client\src\components\CustomerAdd.js

...
import {post} from 'axios';

const CustomerAdd = () => {

....

    const onSubmit = (e) => {
        e.preventDefault();
        if(!window.confirm('고객을 등록하시겠습니까?')) return;
        const url = "/api/customers";
        const formData = new FormData();
        formData.append("image",file);
        formData.append("name",userName);
        formData.append("birthday",birthday);
        formData.append("gender",gender);
        formData.append("job",job);
        const config = {
            Headers : {'content=type':'multipart/form-data'}
        };
        // url에서 formData를 config에서 컨타입을 multiform으로
        post(url,formData,config);

    }

    return (
        <form onSubmit={onSubmit}>
....
        </form>
    );
};

export default CustomerAdd;

 

4) data가 잘 들어가는지 확인해보도록 하겠다.

확인

 

5) 고객 데이터를 테이블에 추가하고 파일업로드를 위한 서버프로그램을 추가한다.

- C:\data\react\ex04\(new)upload

- C:\data\react\ex04\server.js

const express = require('express');
const app = express();
const port = process.env.PORT || 5000;
const mysql = require('mysql');

....

// 이미지 업로드
const multer = require('multer');
const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, './upload'); //업로드 패스를 지정
    },
    filename: (req, file, cb) => {
        cb(null, `${Date.now()}_${file.originalname}`);
        //새로운 파일이름을 업로드날짜_오리지널이름으로 지정
    }
});
const upload = multer({
    storage: storage
});
app.use('/image', express.static('./upload'));
//upload 폴더를 접근할 때 image 폴더로 접근

// 고객등록
app.post('/api/customers',upload.single('image'),(req,res)=>{
    var sql = "insert into customers(image, name, birthday, gender, job) values(?,?,?,?,?)";
    var image ='/image/' + req.file.filename;
    var name = req.body.name;
    var birthday = req.body.birthday;
    var gender = req.body.gender;
    var job = req.body.job;
    connection.query(sql, [image,name,birthday,gender,job], (err,rows)=>{
        res.send(rows);
    })
});

 

6) 이제 이미지가 경로에 맞게 저장되고 데이터가 테이블에 입력되는지 확인해보자.

확인

 

확인

 

확인

 

7) 입력되고 페이지가 refresh되게 하겠다.

- C:\data\react\ex04\client\src\components\Customer.js

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import {Table, TableBody, TableRow, TableHead, TableCell, Paper, CircularProgress} from '@material-ui/core';
....

....

const Customer = (props) => {

    const {classes} = props;

    const [customers,setCustomers] = useState(null);
    const [loading, setLoading] = useState(false);

    // async await 비동기 처리 문법
    const callAPI=async()=>{
        setLoading(true);
        try {
            const response = await axios.get('/api/customers');
            setCustomers(response.data);
        } catch (e) {
            console.log(e)
        }
        setLoading(false);
    }

    useEffect(()=>{
        callAPI();
    },[])

    return (
        <Paper className={classes.paper}>
            <h1>고객관리시스템</h1>
....
            <CustomerAdd callAPI={callAPI}/>
        </Paper>
    );
};

export default withStyles(styles)(Customer);

 

- C:\data\react\ex04\client\src\components\CustomerAdd.js

....

const CustomerAdd = ({callAPI}) => {

....

    const onSubmit = (e) => {
....
        callAPI();

    }

    return (
        <form onSubmit={onSubmit}>
....
        </form>
    );
};

export default CustomerAdd;

 

확인

 

 

- 고객 삭제 기능을 만들도록 하겠다.

- C:\data\react\ex04\client\src\components\(new)CustomerDelete.js

import React from 'react';

const CustomerDelete = () => {
    return (
    <button>삭제</button>
    );
};

export default CustomerDelete;

 

- C:\data\react\ex04\client\src\components\CustomerItem.js

 

....

const CustomerItem = ({customer}) => {
    const {id, image, name, birthday, gender, job} = customer
    return (
        <TableRow className="item">
....
            <TableCell>
                <CustomerDelete/>
            </TableCell>
        </TableRow>
    );
};

export default CustomerItem;

 

- C:\data\react\ex04\client\src\components\Customer.js

...

    return (
        <Paper className={classes.paper}>
            <h1>고객관리시스템</h1>
            <Table className={classes.table}>
                <TableHead>
                    <TableRow>
...
                        <TableCell>삭제</TableCell>
                    </TableRow>
                </TableHead>
                <TableBody>
...
                </TableBody>
            </Table>
            <CustomerAdd callAPI={callAPI}/>
        </Paper>
    );
};

export default withStyles(styles)(Customer);

 

확인

 

- 테이블에서 데이터를 삭제하는게 아니라 컬럼을 추가하여 삭제했는지 안했는지 여부를 추가하겠다.

- MySQL

alter table customers add isDelete int;
update customers set isDelete = 0 where id > 0;

확인

 

- 아이디 삭제버튼으르 누르면 컨펌을 받을 수 있도록 하겠다.

- C:\data\react\ex04\client\src\components\CustomerDelete.js

import React from 'react';

const CustomerDelete = ({id}) => {
    const deleteCustomer = (id) =>{
        if(!window.confirm(`${id}을(를) 삭제하시겠습니까?`)) return
    }
    return (
    <button onClick={()=>deleteCustomer(id)}>삭제</button>
    );
};

export default CustomerDelete;

 

- C:\data\react\ex04\client\src\components\CustomerItem.js

....

const CustomerItem = ({customer}) => {
    const {id, image, name, birthday, gender, job} = customer
    return (
        <TableRow className="item">
....
            <TableCell>
                <CustomerDelete id={id}/>
            </TableCell>
        </TableRow>
    );
};

export default CustomerItem;

 

확인

 

- 이제 테이블 삭제컬럼에 데이터를 넣는 작업을 하도록 하겠다.

- C:\data\react\ex04\server.js

// 4. 고객삭제
//         아래와 같이 파라미터로 받겠다.(/:id)
app.delete('/api/customers/:id',(req,res)=>{
    var sql = "update customers set isDelete=1 where id=?";
    // 파라미터로 받기 위해서는 아래와 같이 해야한다.
    var id = req.params.id;
    connection.query(sql,[id],(err,rows)=>{
        res.send(rows);
    })
});

 

- C:\data\react\ex04\client\src\components\CustomerDelete.js

import React from 'react';

const CustomerDelete = ({id}) => {
    const deleteCustomer = (id) =>{
        if(!window.confirm(`${id}을(를) 삭제하시겠습니까?`)) return
        fetch(`/api/customers/${id}`,{method:'delete'})
    }
    return (
    <button onClick={()=>deleteCustomer(id)}>삭제</button>
    );
};

export default CustomerDelete;

 

확인

 

- 이제 고객목록출력 메서드를 다음과 같이 수정한다.

- C:\data\react\ex04\server.js

// 1. 고객목록출력
app.get('/api/customers', (req, res) => {
    var sql = "select * from customers where isDelete=0 order by id desc limit 0,3";
    connection.query(sql, (err, rows) => {
        res.send(rows);
    })
});

 

- 삭제 후 화면을 refresh 하기 위해 callAPI를 가지고 오자.

- C:\data\react\ex04\client\src\components\Customer.js

                <TableBody>
                    { customers ? customers.map(c => (
                    <CustomerItem key={c.id} customer={c} callAPI={callAPI}/>
                    )) : ''}

 

- C:\data\react\ex04\client\src\components\CustomerItem.js

  .....
  			<TableCell>
                <CustomerDelete id={id} callAPI={callAPI}/>
            </TableCell>
  .....

 

- C:\data\react\ex04\client\src\components\CustomerDelete.js

import React from 'react';

const CustomerDelete = ({id, callAPI}) => {
    const deleteCustomer = (id) =>{
        if(!window.confirm(`${id}을(를) 삭제하시겠습니까?`)) return
        fetch(`/api/customers/${id}`,{method:'delete'})
        callAPI();
    }
    return (
    <button onClick={()=>deleteCustomer(id)}>삭제</button>
    );
};

export default CustomerDelete;

 

확인

 

- insert할 경우 isDelete를 0으로 하겠다.

- C:\data\react\ex04\server.js

// 3. 고객등록
app.post('/api/customers',upload.single('image'),(req,res)=>{
    var sql = "insert into customers(image, name, birthday, gender, job,isDelete) values(?,?,?,?,?,0)";
    var image ='/image/' + req.file.filename;
    var name = req.body.name;
    var birthday = req.body.birthday;
    var gender = req.body.gender;
    var job = req.body.job;
    connection.query(sql, [image,name,birthday,gender,job], (err,rows)=>{
        res.send(rows);
    })
});

 

- Material UI 모달(Modal) 디자인 구현하기

- 아래 url 사이트로 이동하여 모달(Model) 디자인을 참조한다. 

https://material-ui.com/components/modal/

 

React Modal component - Material-UI

The modal component provides a solid foundation for creating dialogs, popovers, lightboxes, or whatever else.

material-ui.com

 

- CustomerAdd.js 컴포넌트를 아래와 같이 수정한다.

- C:\data\react\ex04\client\src\components\CustomerAdd.js

import React, { useState } from 'react';
import {post} from 'axios';
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField } from '@material-ui/core';

const CustomerAdd = ({callAPI}) => {

    const [open, setOpen] = useState(false);
    const onClickOpen = () => {
        setOpen(true);
    }
    const onClickClose = () => {
        setOpen(false);
    }
    const [form, setForm] = useState({
        file:null,
        userName:'이순신',
        birthday:'450428',
        gender:'남자',
        job:'장군',
        fileName:''
    });

    const {file, fileName, userName, birthday, gender, job} = form;

    const onChange = (e) =>{
        const newForm ={
            ...form,
            [e.target.name]:e.target.value
        }
        setForm(newForm);
    }

    const onFileChange = (e) => {
        const newForm = {
            ...form,
            file:e.target.files[0],
            fileName:e.target.value
        }
        setForm(newForm);
    }

    const onSubmit = (e) => {
        e.preventDefault();
        if(!window.confirm('고객을 등록하시겠습니까?')) return;
        const url = "/api/customers";
        const formData = new FormData();
        formData.append("image",file);
        formData.append("name",userName);
        formData.append("birthday",birthday);
        formData.append("gender",gender);
        formData.append("job",job);
        const config = {
            Headers : {'content=type':'multipart/form-data'}
        };
        // url에서 formData를 config에서 컨타입을 multiform으로
        post(url,formData,config);
        callAPI();

    }

    return (
        <div>
            <Button variant="contained" color="primary" onClick={onClickOpen}>고객 추가하기</Button>
            <Dialog open={open}>
                <DialogTitle>고객추가</DialogTitle>
                <DialogContent>
                        <input style={{display:"none"}} accept="image/*" id="raised-button-file"
                        type="file" file={file} value={fileName} onChange={onFileChange}/><br/>
                        <label htmlFor="raised-button-file">
                            <Button variant="contained" color="primary" component="span" name="file">
                            {fileName === '' ? '프로필 이미지 선택' : fileName}
                            </Button>
                        </label><br/>
                        <TextField label="이름" type="text" name="userName" value={userName} onChange={onChange}/><br/>
                        <TextField label="생년월일" type="text" name="birthday" value={birthday} onChange={onChange}/><br/>
                        <TextField label="성별" type="text" name="gender" value={gender} onChange={onChange}/><br/>
                        <TextField label="직업" type="text" name="job" value={job} onChange={onChange}/><br/>
                        <button type="submit">추가하기</button>
                </DialogContent>
                <DialogActions>
                    <Button variant="contained" color="primary" onClick={onSubmit}>추가</Button>
                    <Button variant="outlined" color="primary" onClick={onClickClose}>닫기</Button>
                </DialogActions>
            </Dialog>
        </div>
    );
};

export default CustomerAdd;

 

 

확인

 

- 리액트 라우터로 SPA 개발하기

- SPA : SPA는 Single Page Application의 약어이다. 말 그대로 한 개의 페이지로 이루어진 애플리케이션이라는 의미이다. 리액트 같은 라이브러리 혹은 프레임워크를 사용하여 뷰 렌더링을 사용자의 브라우저가 담당하도록 하고 우선 애플리케이션을 브라우저에 불러와서 실행시킨 후에 사용자와의 인터랙션이 발생하면 필요한 부분만 자바스크립트를 사용하여 업데이트해 준다. 만약 새로운 데이터가 필요하면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용한다.

기존 웹 서빗느느 요청시마다 서버로부터 리소스드과 데이터를 해석하고 화면에 렌더링하는 방식이다. SPA형태는 브라우저에 최초에 한번 페이지 전체를 로드하고, 이후부터는 특정 부분만 Ajax을 통해 데이터를 바인딩하는 방식이다.

기존 페이지와 SPA 비교

 

// 서버 종료
ctrl + `

// 새로운 프로젝트 생성
C:\data\react\>yarn create react-app ex05

// Router 라이브러리 설치
C:\data\react\ex05>yarn add react-router-dom

// 프로젝트 스타트
yarn start

 

- 프로젝트에 라우터 적용

프로젝트에 리액트 라우터를 적용할 때는 src/index.js 파일에서 react-router-dom에 내장되어 있는 BrowserRouter라는 컴포넌트를 사 용하여 감싸준다. 이 컴포넌트는 웹 애플리케이션에 HTML5의 History API를 사용하여 페이지를 새로고침하지 않고도 주소를 변경하고 현재 주 소에 관련된 정보를 props로 쉽게 조회하거나 사용할 수 있도록 해준다.

- C:\data\react\ex05\src\index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

- C:\data\react\ex05\src\(new)component

- C:\data\react\ex05\src\(new)component\Home.js

import React from 'react';

const Home = () => {
    return (
        <div>
            <h1>홈</h1>
            <p>가장 먼저 보여줄 페이지</p>
        </div>
    );
};

export default Home;

 

- C:\data\react\ex05\src\(new)component\About.js

import React from 'react';

const About = () => {
    return (
        <div>
            <h1>소개</h1>
            <p>이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트이다.</p>
        </div>
    );
};

export default About;

 

- C:\data\react\ex05\src\App.js

import { Route } from 'react-router-dom';
import './App.css';
import Home from './component/Home'
import About from './component/About'

const App = () => {
  return (
    <div className="App">
      {/* exact가 있고 없고가 page를 구별해준다. */}
      <Route path="/" component={Home} exact={true}/>
      <Route path="/about" component={About} exact={true}/>
    </div>
  );
}

export default App;

 

확인

 

확인

 

- link component를 만들어서 다른 주소로 이동하도록 해보겠다.

- C:\data\react\ex05\src\App.js

import { Link, Route } from 'react-router-dom';
import './App.css';
import Home from './component/Home'
import About from './component/About'

const App = () => {
  return (
    <div className="App">
      <ul>
        <li><Link to="/">홈</Link></li>
        <li><Link to="/about">소개</Link></li>
      </ul>
      <hr/>
      {/* exact가 있고 없고가 page를 구별해준다. */}
      <Route path="/" component={Home} exact={true}/>
      <Route path="/about" component={About}/>
    </div>
  );
}

export default App;

 

확인

 

- Route 하나에 여러 개의 path 설정하기

- C:\data\react\ex05\src\App.js

...

const App = () => {
  return (
    <div className="App">
      <ul>
...
        <li><Link to="/info">information</Link></li>
      </ul>
      <hr/>
...
      <Route path={["/about","/info"]} component={About}/>
    </div>
  );
}

export default App;

 

확인

 

- URL 파라미터와 쿼리

- 일반적으로 파라미터아이디나 이름을 사용하여 조회할 때 사용하고 쿼리키워드를 검색하거나 페이지에 필요한 옵션을 전달할 때 사용한다.

  • 파라미터 예시: /profile/velopert
  • 쿼리 예시: /about?details=true

 

- 파라미터 받는 방법

- C:\data\react\ex05\src\component\(new)Profile.js

import React from 'react';

// 데이터 생성
const data = {
    shim:{
        name : '심청이',
        description:'리액트에 흥미가 있는 개발자'
    },
    park:{
        name : '박영희',
        description:'프론트앤드에 소질이 있는 개발자'
    },
    kim:{
        name : '김철수',
        description:'DB설계에 재능이 있는 개발자'
    }
}

// 위의 파라미터 이름을 받아서 출력을 하는
const Profile = ({match}) => {
    //               <-
    const {username} = match.params;
    //            <-
    const profile = data[username];
    if(!profile){
        return <div>존재하지 않는 사용자입니다.</div>
    }
    return (
        <div>
            <h3>{username}({profile.name})</h3>
            <p>{profile.description}</p>
        </div>
    );
};

export default Profile;

 

- C:\data\react\ex05\src\App.js

import { Link, Route } from 'react-router-dom';
import './App.css';
import Home from './component/Home'
import About from './component/About'
import Profile from './component/Profile';

const App = () => {
  return (
    <div className="App">
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/info">Information</Link></li>
        <li><Link to="/profile/shim">Shim's profile</Link></li>
        <li><Link to="/profile/park">Park's profile</Link></li>
        <li><Link to="/profile/kim">kim's profile</Link></li>
      </ul>
      <hr/>
      {/* exact가 있고 없고가 page를 구별해준다. */}
      <Route path="/" component={Home} exact={true}/>
      <Route path={["/about","/info"]} component={About}/>
      <Route path="/profile/:username" component={Profile}/>
    </div>
  );
}

export default App;

 

확인

 

- 서브 라우트

서브 라우트는 라우트 내부에 또 라우트를 정의하는 것을 의미한다. 아래는 프로필 링크를 보여 주는 Profiles라는 라우트 컴포넌트를 따로 만들고 그 안에서 Profile 컴포넌트를 서브 라우트로 사용하도록 작성한다.

- C:\data\react\ex05\src\component\(new)Profiles.js

import React from 'react';
import { Link, Route } from 'react-router-dom';
import Profile from './Profile';

const Profiles = () => {
    return (
        <div>
            <h1>사용자 목록</h1>
            <ul>
                <li><Link to="/profiles/shim">Shim's profile</Link></li>
                <li><Link to="/profiles/park">Park's profile</Link></li>
                <li><Link to="/profiles/kim">kim's profile</Link></li>
            </ul>
            <Route path="/profiles/:username" component={Profile}/>
        </div>
    );
};

export default Profiles;

 

- C:\data\react\ex05\src\App.js

...
import Profiles from './component/Profiles';

const App = () => {
  return (
    <div className="App">
      <ul>
...
        <li><Link to="/profiles">Profile</Link></li>
      </ul>
      <hr/>
...
      <Route path="/profiles" component={Profiles}/>
    </div>
  );
}

export default App;

 

확인

 

- switch

switch 컴포넌트는 여러 Route를 감싸서 그 중 일치하는 단 하나의 라우트만을 렌더링시켜 준다. Switch를 사용하면 모든 규칙과 일치하지 않 을 때 보여 줄 Not Found 페이지도 구현할 수 있다. 작성 후 존재하지 않는 페이지인 http://localhost:3000/nowhere로 접속해 보자.

- C:\data\react\ex05\src\App.js

import { Link, Route, Switch } from 'react-router-dom';
import './App.css';
import Home from './component/Home'
import About from './component/About'
import Profiles from './component/Profiles';

const App = () => {
  return (
    <div className="App">
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/info">Information</Link></li>
        <li><Link to="/profiles">Profile</Link></li>
      </ul>
      <hr/>
      <Switch>
      <Route path="/" component={Home} exact={true}/>
      <Route path={['/about','/info']} component={About}/>
      <Route path="/profiles" component={Profiles}/>
      {/* path를 따로 정의하지 않으면 모든 상황에 렌더링됨 */}
      <Route render={ ({location}) => ( 
        <div>
          <h2>이 페이지는 존재하지 않습니다.</h2>
          <p>{location.pathname}</p>
        </div>
      )}/>
      </Switch>
    </div>
  );
}

export default App;

 

확인

 

- 외부 API를 연동하여 뉴스 뷰어 만들기

axios는 현재 가장 많이 사용되고 있는 자바스크립트 HTTP 클라이언트이다. 이 라이브러리의 특징은 HTTP 요청을 promise 기반으로 처리한 다는 점이다. 이 라이브러리를 사용하기 위해 아래와 같이 설치한다. 

yan add axios;
yan start

 

- 데이터 불러오기 버튼을 누르면 데이터를 출력하도록 하겠다.

- C:\data\react\ex05\src\App.js

import './App.css';
import axios from 'axios';
import {useState} from "react"

const App = () => {
  const [data,setData] = useState(null);
  const onClick = async() => {
    const res = await axios.get('https://newsapi.org/v2/top-headlines?country=kr&apiKey=0d2cfc3452514a4f93903b35e762e40d')
    setData(res.data);
  }
  return (
    <div className="App">
      <button onClick={onClick}>데이터 불러오기</button>
      {data && <textarea rows={7} value={JSON.stringify(data, null, 2)} readOnly/>}
    </div>
  );
}

export default App;

 

확인

 

- 뉴스 뷰어 UI 만들기

- C:\data\react\ex05\src\component\(new)NewsItem.js

import React from 'react';
import './NewsItem.css'

const NewsItem = ({article}) => {
    const {title, description, url, urlToImage} = article;
    return (
        <div className="newsItemBlock">
            <div className="thumbnail">
                <a href={url}>
                    <img src={urlToImage} alt="thumbnail"/>
                </a>
            </div>
            <div className="contents">
                <a href={url}>
                    <h2>{title}</h2>
                    <p>{description}</p>
                </a>
            </div>
        </div>
    );
};

export default NewsItem;

 

- C:\data\react\ex05\src\component\(new)NewsList.js

import React from 'react';
import NewsItem from './NewsItem';

const sampleArticle ={
    title: '제목', 
    description: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. ...', 
    url: 'https://google.com', 
    urlToImage: 'http://placehold.it/160x160'
   };

const NewsList = () => {
    return (
        <div className="newsListBlock">
            <NewsItem article={sampleArticle}/>
            <NewsItem article={sampleArticle}/>
            <NewsItem article={sampleArticle}/>
        </div>
    );
};

export default NewsList;

 

- C:\data\react\ex05\src\App.js

import './App.css';
import axios from 'axios';
import {useState} from "react"
import NewsList from './component/NewsList';

const App = () => {
  const [data,setData] = useState(null);
  const onClick = async() => {
    const res = await axios.get('https://newsapi.org/v2/top-headlines?country=kr&apiKey=0d2cfc3452514a4f93903b35e762e40d')
    setData(res.data);
  }
  return (
    <div className="App">
      <NewsList/>
    </div>
  );
}

export default App;

 

확인

 

- 뉴스 기사를 가지고와 출력해보도록 하겠다.

- C:\data\react\ex05\src\component\NewsList.js

import axios from 'axios';
import React, { useEffect, useState } from 'react';
import NewsItem from './NewsItem';
import './NewsList.css'

const NewsList = () => {
    const [articles, setArticles] = useState(null);
    const [loading,setLoading] = useState(false);

    const callAPI = async() => {
        setLoading(true);
        try {
            const res = await axios.get('https://newsapi.org/v2/top-headlines?country=kr&apiKey=0d2cfc3452514a4f93903b35e762e40d')
            setArticles(res.data.articles);
            setLoading(false);
        } catch (e) {
            console.log(e);
            setLoading(false);
        }
    }

    useEffect(()=>{
        callAPI();
    },[]);

    if(loading){
        return <div className="newsListBlock">불러오는중...</div>
    }
    return (
        <div className="newsListBlock">
            {articles ? articles.map(acticle => <NewsItem key={acticle.id} article={acticle}/>) : ''}
        </div>
    );
};

export default NewsList;

 

- C:\data\react\ex05\src\component\NewsItem.js

import React from 'react';
import './NewsItem.css'

const NewsItem = ({article}) => {
    const {title, description, url, urlToImage} = article;
    return (
        <div className="newsItemBlock">
            <div className="thumbnail">
                <a href={url}>
                    <img src={urlToImage} alt="thumbnail"/>
                </a>
            </div>
            <div className="contents">
                <a href={url}>
                    <h2>{title}</h2>
                    <p>{description}</p>
                </a>
            </div>
        </div>
    );
};

export default NewsItem;

 

확인

 

 

내일 오전 page45 카테고리 뉴스 검색