React 프로젝트 생성
React로 Frontend 작업
리액트 + spring boot로 프런트와 백엔드를 구성하고 배포까지 하는 과정입니다
방명록을 프로젝트 주제로 생성
리액트 시작하기
node js가 설치되어있다면 명령어로 리액트 프로젝트를 생성시킬 수 있다
npx create-react-app my-app 를 루트 폴더에서 실행시킨다
npm start로 리액트 프로젝트가 정상적으로 생성되었는 확인
localhost:3000으로 접속해서 프로젝트가 정상적으로 실행되는지 확인
프로젝트 초기 설정
react-router-dom을 이용할거라 프로젝트 위치에서 해당 모듈을 설치한다 npm install react-router-dom
그리고 index.js에 들어가 react-router-dom 모듈을 사용할 수 있도록 설정한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
reportWebVitals();
BrowserRouter를 추가했다
컴포넌트 생성
이제 프로젝트를 구성할 화면을 작업해야 한다 구성 목록은 4가지로 리스트를 보여줄 페이지, 글을 작성하는 페이지 , 글을 수정하는 페이지(삭제), 디테일 페이지로 구성된다
메인 페이지(리스트 뷰어)
메인 페이지에서는 자신의 소개와 사람들이 작성한 방명록을 볼 수 있어야한다
my-app/src 위치에 components 폴더를 만들고 안에 Main.jsx 파일을 생성한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import "./../App.css";
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
function Main() {
const [posts, setPosts] = useState([]);
// useState 나중에 비동기 작업을 위함
return (
<div className="App-header">
<h1>안녕하세요 저는 *** 입니다 만나서 반갑습니다</h1>
<h2>방명록</h2>
{posts.map((post) => (
<div key={post.id} className="card">
<Link to={`/post/${post.id}`}>
<div className="title">{post.title}</div>
</Link>
<p className="writer">작성자: {post.writer}</p>
</div>
))}
<div className="card">
<div className="title">예시 방명록 입니다1 </div>
<div className="writer">test1</div>
</div>
<div className="card">
<div className="title">예시 방명록 입니다2 </div>
<div className="writer">test2</div>
</div>
<Link className="button" to="/write">
방명록 작성
</Link>
</div>
);
}
export default Main;
아직 방명록 글이 없어서 테스트로 2개를 작성한다
글 작성 페이지
방명록을 작성하는 페이지로 메인페이지에서 버튼을 누르면 해당 페이지로 이동한다
해당 페이지에서는 글을 작성할 때마다 글이 저장될 변수에 업데이트가 항상 되야한다 따라서 useState를 사용
compenents에 Create.jsx 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import React, { useState } from "react";
import { Link } from "react-router-dom";
import "./create.css";
const PostCreate = () => {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [writer, setWriter] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
// 비동기로 생성 요청을 보내는 코드
};
return (
<div className="App-header">
<h2>방명록 작성하기</h2>
<form onSubmit={handleSubmit} className="card">
<div className="form-group">
<label className="title">제목</label>
<input
type="text"
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>
<div className="form-group">
<label className="content">내용</label>
<textarea
id="content"
value={content}
onChange={(e) => setContent(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="writer">작성자</label>
<input
type="text"
id="writer"
value={writer}
onChange={(e) => setWriter(e.target.value)}
required
/>
<button className="button" type="submit">
완료
</button>
</div>
</form>
<Link to={"/"}>돌아가기</Link>
</div>
);
};
export default PostCreate;
상세 페이지
작성한 방명록을 내용까지 볼 수 있는 페이지
Detail.jsx 파일 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import React, { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import "./../App.css";
function PostDetail() {
const { postId } = useParams();
const navigate = useNavigate();
const [post, setPost] = useState(null);
const handleHome = () => {
navigate("/");
};
const handleEdit = () => {
// 수정 페이지로 이동
navigate(`/edit/${postId}`);
};
//삭제 코드
const handleDelete = () => {};
// 특정 글을 가져오는 비동기 코드 필요
if (!post) {
return <div>Loading...</div>;
}
return (
<div className="App-header">
<h1>방명록 상세페이지</h1>
<div className="card">
<h2 className="title">{post.title}</h2>
<p>{post.content}</p>
<p className="writer">작성자: {post.writer}</p>
</div>
<div className="button-container">
<p className="button" onClick={handleEdit}>
수정
</p>
<p className="button" onClick={handleDelete}>
삭제
</p>
<p className="button" onClick={handleHome}>
돌아가기
</p>
</div>
</div>
);
}
export default PostDetail;
수정 삭제 버튼을 추가한다
수정 페이지
Edit.jsx 파일을 생성하고 수정 기능 컴포넌트를 생성한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import React, { useState, useEffect } from 'react';
import {useNavigate, useParams } from 'react-router-dom';
import './edit.css';
const PostEdit = () => {
const { postId } = useParams();
const navigate = useNavigate();
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [writer, setWriter] = useState('');
const [originalPost, setOriginalPost] = useState({});
// 기존 글 데이터를 불러오는 비동기 함수 호출
const handleTitleChange = e => {
setTitle(e.target.value);
};
const handleContentChange = e => {
setContent(e.target.value);
};
const handleWriterChange = e => {
setWriter(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
// 수정된 데이터를 서버에 보내는 비동기 함수 호출
};
const handleCancel = () => {
// 수정 취소 후 글 상세 페이지로 이동
navigate(`/post/${postId}`);
};
return (
<div className="App-header">
<h2>Edit Post</h2>
<form onSubmit={handleSubmit} className="card">
<div className="form-group">
<label>Title</label>
<input
type="text"
value={title}
onChange={handleTitleChange}
required
/>
</div>
<div className="form-group">
<label>Content</label>
<textarea
value={content}
onChange={handleContentChange}
required
></textarea>
</div>
<div className="form-group">
<label>Writer</label>
<input
type="text"
value={writer}
onChange={handleWriterChange}
required
/>
</div>
<div>
<div className="button-container">
<button type="submit">수정 완료</button>
<button type="button" onClick={handleCancel}>
돌아가기
</button>
</div>
</div>
</form>
</div>
);
};
export default PostEdit;
컴포넌트 연결하기
컴포넌트를 다 생성 후 이 컴포넌트를 연결해줘야한다 app.js에서 파일을 수정한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "react";
import { Routes, Route } from "react-router-dom";
import Main from "./components/Main";
import Detail from "./components/Detail";
import Create from "./components/Create";
import Edit from "./components/Edit";
function App() {
return (
<Routes>
<Route path="/" element={<Main />} />
<Route path="/post/:postId" element={<Detail />} />
<Route path="/write" element={<Create />} />
<Route path="/edit/:postId" element={<Edit />} />
</Routes>
);
}
export default App;
Route를 이용해서 컴포넌트를 보여줄 url을 연결한다
CSS 파일
App.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.card {
background-color: #3498db;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 16px;
color: #fff;
margin: 10px;
width: 50%;
}
.title {
background-color: #87ceeb;
padding: 8px;
margin-bottom: 12px;
font-size: 18px;
font-weight: bold;
text-decoration: none;
}
.content {
margin-bottom: 8px;
}
.writer {
font-style: italic;
}
.button-container {
display: flex;
justify-content: center;
gap: 10px;
}
.button {
background-color: #61dafb;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s ease;
text-decoration: none;
}
.button:hover {
background-color: #5ac8e0;
}
.post-create {
margin-top: 20px;
}
create.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
}
.card {
background-color: #3498db;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 16px;
margin: 10px;
width: 400px;
max-width: 100%;
}
.card-title {
color: #333333;
font-size: 20px;
font-weight: bold;
margin-bottom: 8px;
}
.card-content {
color: #555555;
margin-bottom: 8px;
}
label {
display: block;
font-weight: bold;
color: #666666;
}
.form-group {
margin-bottom: 16px;
}
input[type="text"],
textarea {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #61dafb;
color: white;
border: none;
margin: 5px;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #47c5f1;
}
edit.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
h2 {
text-align: center;
}
.card {
background-color: #3498db;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 16px;
color: #fff;
margin: 10px;
width: 50%;
}
.form-group {
margin-bottom: 10px;
}
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
input[type="text"],
textarea {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.button-container {
display: flex;
justify-content: center;
gap: 10px;
}
.button {
background-color: #61dafb;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s ease;
text-decoration: none;
}
.button:hover {
background-color: #5ac8e0;
}
button[type="submit"] {
background-color: #61dafb;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s ease;
text-decoration: none;
}
button[type="button"] {
background-color: #61dafb;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s ease;
text-decoration: none;
}