1. 다른 주소로 이동시키기
: import {Route, Link} from 'react-router-dom';
<Link to="주소">content</Link>
[파라미터] /profile/apple
[쿼리] /profile?name=apple
* 쿼리를 문자열로 받아 처리 가능하지만 객체 형태로 변환하려면 qs 라이브러리를 사용
└ yarn add qs
* 서브라우트 : 라우트 내부의 라우트를 만드는 것을 의미한다.
** app.jsx
import React from 'react'
import {Route, Link} from 'react-router-dom';
import Home from './home';
import About from './about';
import Profiles from './profiles';
const App = () => {
return (
<div>
<ul>
<li>
<Link to='/'>홈</Link>
</li>
<li>
<Link to="/about">소개</Link>
</li>
<li>
<Link to="/profiles">프로필 목록</Link>
</li>
</ul>
<hr/>
<div>
<Route path="/" exact={true} component={Home}/>
<Route path="/about" component={About}/>
<Route path="/profiles" component={Profiles}/>
</div>
</div>
)
}
export default App;
** profile.jsx
import React from 'react';
const profileData = {
apple: {
name: '김사과',
description: '학생, 착해요'
},
banana: {
name: '반하나',
description: '회사원, 잘자요'
}
};
const Profile = ({match}) => {
const {username} = match.params;
const profile = profileData[username];
if(!profile) {
return <div>존재하지 않는 프로필입니다.</div>
}
return (
<div>
<h2>
{username}({profile.name})
</h2>
<p>{profile.description}</p>
</div>
);
}
export default Profile;
** about.jsx
import React from 'react';
import qs from 'qs';
const About = ({location}) => {
// props 전달되는 location 객체에 있는 search 값에서 데이터를 읽을 수 있음
// (location 안에는 현재 앱이 갖고 있는 주소에 대한 정보)
const query = qs.parse(location.search, {
ignoreQueryPrefix: true
});
const detail = query.detail === 'true';
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 공부하고 있어요.</p>
{detail && <p>추가적인 내용이 보일거예요</p>}
</div>
);
};
export default About;
** profiles.jsx
import React from 'react';
import {Link, Route} from 'react-router-dom';
import Profile from './profile';
const Profiles = () => {
return (
<div>
<h2>유저 목록</h2>
<ul>
<li>
<Link to="/profiles/apple">apple</Link>
</li>
<li>
<Link to="/profiles/banana">banana</Link>
</li>
</ul>
<Route path="/profiles" exact render={() => <div>유저를 선택하세요</div>}/>
<Route path="/profiles/:username" component={Profile}/>
</div>
)
}
export default Profiles;
2. history 객체
: 컴포넌트 내에 구현하는 메소드에서 라우터에 직접 접근을 할 수 있게 한다.
뒤로가기, 특정 경로로 이동, 이탈방지 등 구현할 수 있다.
** historySample.jsx
import React, {useEffect} from 'react';
function HistorySample({history}) {
const goBack = () => {
history.goBack();
}
const goHome = () => {
history.push('/');
}
useEffect(() => {
console.log(history);
const unblock = history.block('정말 떠나실건가요?');
return () => {
unblock();
}
}, [history]);
return (
<div>
<button onClick={goBack}>뒤로가기</button>
<button onClick={goHome}>홈으로</button>
</div>
)
}
export default HistorySample;
** app.jsx
import React from 'react'
import {Route, Link} from 'react-router-dom';
import Home from './home';
import About from './about';
import Profiles from './profiles';
import HistorySample from './historySample';
const App = () => {
return (
<div>
<ul>
<li>
<Link to='/'>홈</Link>
</li>
<li>
<Link to="/about">소개</Link>
</li>
<li>
<Link to="/profiles">프로필 목록</Link>
</li>
<li>
<Link to="/history">history 예제</Link>
</li>
</ul>
<hr/>
<div>
<Route path="/" exact={true} component={Home}/>
<Route path="/about" component={About}/>
<Route path="/profiles" component={Profiles}/>
<Route path="/history" component={HistorySample}/>
</div>
</div>
)
}
export default App;
3. Koa
app.use((ctx, next) => {
...
})
** ctx(Context) : 웹 요청과 응답에 대한 정보를 가지고 있는 객체
** next : 현재 처리 중인 미들웨어의 다음 미들웨어를 호출하는 함수
** /backend/index.js
const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');
const api = require('./api');
const app = new Koa();
const router = new Router();
router.use('/api', api.routes()); // api 라우트 적용
app.use((ctx, next) => {
console.log(ctx.url);
console.log(1);
next();
});
app.use((ctx, next) => {
console.log(2);
next();
});
app.use(ctx => {
ctx.body = 'hello Koa!';
});
app.listen(4000, () => {
console.log('4000번 포트로 서버 동작 중 ...');
});
** /backend/api/index.js
const Router = require('koa-router');
const posts = require('./posts');
const api = new Router();
api.use('/posts', posts.routes());
module.exports = api;
** /backend/api/posts/indexjs
const Router = require('koa-router');
const postsCtrl = require('./posts.ctrl');
const posts = new Router();
posts.get('/', postsCtrl.list);
posts.post('/', postsCtrl.write);
posts.get('/:id', postsCtrl.read);
posts.delete('/:id', postsCtrl.remove);
posts.put('/:id', postsCtrl.replace);
module.exports = posts;
** /backend/api/posts/posts.ctrl.js
let postId = 1; // id의 초기값이다.
// posts 배열 초기 데이터
const posts = [
{
id: 1,
title: '제목',
body: '내용'
}
];
/* 포스트 목록 조회
GET /api/posts
*/
exports.list = ctx => {
ctx.body = posts;
}
/* 포스트 작성
POST /api/posts
*/
exports.write = ctx => {
const {title, body} = ctx.request.body;
postId += 1;
const post = {id: postId, title, body};
posts.push(post);
ctx.body = post;
}
/* 특정 포스트 조회
GET /api/posts/:id
*/
exports.read = ctx => {
const {id} = ctx.params;
// 주어진 id 값으로 포스트를 찾는다.
// 파라미터로 받아온 값은 문자열 형식이므로 숫자로 변환하거나 비교할 id 값을 문자열로 변경해야 한다.
const post = posts.find(p => p.id.toString() === id);
if(!post) {
ctx.status = 404;
ctx.body = {
message: '포스트가 존재하지 않습니다.'
};
return;
}
ctx.body = post;
}
/* 특정 포스트 제거
DELETE /api/posts/:id
*/
exports.remove = ctx => {
const {id} = ctx.params;
const index = posts.findIndex(p => p.id.toString() === id);
if(index === -1) {
ctx.status = 404;
ctx.body = {
message: '포스트가 존재하지 않습니다.'
};
return;
}
posts.slice(index, 1);
ctx.status = 204; // 내용이 존재하지 않음
}
/* 특정 포스트 수정
PUT /api/posts/:id
*/
exports.replace = ctx => {
const {id} = ctx.params;
const index = posts.findIndex(p => p.id.toString() === id);
if(index === -1) {
ctx.status = 404;
ctx.body = {
message: '포스트가 존재하지 않습니다.'
};
return;
}
post[index] = {
id,
...ctx.request.body
};
ctx.body = posts[index];
}
4. .env 환경변수 파일 생성
yarn add mongoose dotenv
.env 파일
PORT=4000
MONGO_URI=mongodb://localhost:27017/blog
** /backend/.env
PORT=4000
MONGO_URI=mongodb://localhost:27017/blog
** /backend/index.js, 기존 index.js는 main.js로 이름 변경
require = require('esm')(module/*, options*/);
module.exports = require('./main.js');
** /backend/main.js
require('dotenv').config();
import Koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';
import mongoose from 'mongoose';
import api from './api';
const {PORT, MONGO_URI} = process.env;
mongoose.connect(MONGO_URI, {useNewUrlParser:true, useFindAndModify: false})
.then(() => {
console.log('mongodb 연결 성공!');
})
.catch(e => {
console.error(e);
});
const app = new Koa();
const router = new Router();
router.use('/api', api.routes()); // api 라우트 적용
app.use(bodyParser());
app.use(router.routes()).use(router.allowedMethods());
const port = PORT || 4000;
app.listen(port, () => {
console.log('4000번 포트로 서버 동작 중 ...');
});
** /backend/models/post.js
import mongoose from 'mongoose';
const {Schema} = mongoose;
const PostSchema = new Schema({
title: String,
body: String,
tags: [String],
publishedDate: {
type: Date,
default: Date.now
}
});
const Post = mongoose.model('Post', PostSchema);
export default Post;
** /backend/api/index.js
import Router from 'koa-router';
import posts from './posts';
const api = new Router();
api.use('/posts', posts.routes());
export default api;
** /backend/api/posts/index.js
import Router from 'koa-router';
import * as postsCtrl from './posts.ctrl';
const posts = new Router();
posts.get('/', postsCtrl.list);
posts.post('/', postsCtrl.write);
// /api/posts/:id
const post = new Router();
post.get('/', postsCtrl.read);
post.delete('/', postsCtrl.remove);
post.patch('/', postsCtrl.update);
posts.use('/:id', postsCtrl.checkObjectId, post.routes());
export default posts;
** /backend/api/posts/posts.ctrl.js
import Post from '../../models/post';
import mongoose from 'mongoose';
import Joi from '@hapi/Joi';
const {ObjectId} = mongoose.Types;
export const checkObjectId = (ctx, next) => {
const {id} = ctx.params;
if(!ObjectId.isValid(id)){
ctx.status = 400;
return;
}
return next();
}
let postId = 1; // id의 초기값이다.
// posts 배열 초기 데이터
const posts = [
{
id: 1,
title: '제목',
body: '내용'
}
];
/* 포스트 목록 조회
GET /api/posts
GET localhost:4000/api/posts
*/
export const list = async ctx => {
const page = parseInt(ctx.query.page || '1', 10);
if(page < 1) {
ctx.status = 400;
return;
}
try {
const posts = await Post.find().sort({_id:-1})
.limit(10)
.skip((page - 1) * 10)
.lean()
.exec();
const postCount = await Post.countDocuments().exec();
ctx.set('Last-Page', Math.ceil(postCount / 10));
ctx.body = posts.map(post => ({
...post,
body:
post.body.length < 200 ? post.body : `${post.body.slice(0, 200)}}...`
}))
} catch(e) {
ctx.throw(500, e);
}
}
/* 포스트 작성
POST /api/posts
POST localhost:4000/api/posts -> JSON
*/
export const write = async ctx => {
const schema = Joi.object().keys({
title: Joi.string().required(),
body: Joi.string().required(),
tags: Joi.array().items(Joi.string()).required()
})
const result = schema.validate(ctx.request.body);
if(result.error) {
ctx.status = 400;
ctx.body = result.error;
return;
}
const {title, body, tags} = ctx.request.body;
const post = new Post({
title,
body,
tags
})
try {
await post.save();
ctx.body = post;
} catch(e) {
ctx.throw(500, e);
}
}
/* 특정 포스트 조회
GET /api/posts/:id
GET localhost:4000/api/posts/60ac946c095365392c1bcd6f
*/
export const read = async ctx => {
const {id} = ctx.params;
// 주어진 id 값으로 포스트를 찾는다.
// 파라미터로 받아온 값은 문자열 형식이므로 숫자로 변환하거나 비교할 id 값을 문자열로 변경해야 한다.
try {
const post = await Post.findById(id).exec();
if(!post) {
ctx.status = 404;
return;
}
ctx.body = post;
} catch(e) {
ctx.throw(500, e);
}
}
/* 특정 포스트 제거
DELETE /api/posts/:id
DELETE localhost:4000/api/posts/60ac946c095365392c1bcd6f
*/
export const remove = async ctx => {
const {id} = ctx.params;
try {
await Post.findByIdAndRemove(id).exec();
ctx.status = 204; // 성공은 했지만 응답한 데이터는 없음
} catch(e) {
ctx.throw(500, e);
}
}
/* 특정 포스트 수정
PATCH /api/posts/:id
PATCH localhost:4000/api/posts/60ac946c095365392c1bcd6f
*/
export const update = async ctx => {
const {id} = ctx.params;
// write에서 사용한 schema, required()가 없음
const schema = Joi.object().keys({
title: Joi.string(),
body: Joi.string(),
tags: Joi.array().items(Joi.string())
})
const result = schema.validate(ctx.request.body);
if(result.error) {
ctx.status = 400;
ctx.body = result.error;
return;
}
try {
const post = await Post.findByIdAndUpdate(id, ctx.request.body, {
// new가 true일때, 업데이트 내용을 반환
// new가 false일때, 업데이트 전 데이터를 반환
new: true
}).exec();
if(!post) {
ctx.status = 404;
return;
}
ctx.body = post;
} catch(e) {
ctx.throw(500, e);
}
}
'웹_프론트_백엔드 > 프론트엔드' 카테고리의 다른 글
2021.04.07(보충) (0) | 2021.05.25 |
---|---|
2021.04.03 (0) | 2021.05.18 |
2021.03.28 (0) | 2021.05.16 |
2021.03.27 (0) | 2021.05.14 |
2021.03.21 (0) | 2021.05.12 |