일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- uint8array
- androidId
- userevent_tracker
- jszip
- babel
- Three js
- Babel standalone
- FirebaseAnalytics
- typescript
- Prism.js
- swagger-typescript-api
- Flutter
- Image Resize typescript
- react
- webrtc
- uint16array
- Three-fiber
- Redux
- web track
- methodChannel
- identifierForVender
- Raycasting
- Excel
- code editor
- Completer
- node
- REST API
- Game js
- KakaoMap
- RouteObserver
- Today
- Total
Never give up
React(class, functional) and Node Rest API example 본문
먼저 필자는 문서를 간단하게 보기는 했지만 React를 어제 처음 시작했고
Node는 오늘 처음으로 REST API를 구현해봤으니
많은 부분이 미숙하다는걸 감안하고 봐주시면 감사드리겠습니다
React는 아래 유튜브 영상에서 나온대로 함수형으로 한번 구현을 해보고
공식문서에 나와있는 예제를 보면서 class형에 대한 감을 대충(?) 잡고 class형으로 변환을 해봤습니다
(Youtube 링크 : https://www.youtube.com/watch?v=w7ejDZ8SWv8)
(공식문서 링크 : https://ko.reactjs.org/tutorial/tutorial.html)
그리고 Node는.. 그냥 검색하면서 여기저기 나오는 자료들 찾아가면서
대충(?) server.js만 만들어서 아래처럼 구현했습니다
(필자는 backend쪽은 크게 관심이 없어서 대충..)
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
const cors = require('cors')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(cors())
const checkData = (data) => {
if (data == 'undefined' || data == null) {
return false
}
return true
}
let tasks = [
{
id: 1,
text: 'text1',
day: 'Feb 5th at 2:30pm',
reminder: true,
hide: false
},
{
id: 2,
text: 'text2',
day: 'Feb 6th at 1:30pm',
reminder: true,
hide: false
},
{
id: 3,
text: 'text3',
day: 'Feb 5th at 2:30pm',
reminder: false,
hide: false
}
]
app.get('/task', function (req, res) {
console.log('get all tasks')
res.send({ data: tasks.filter((element) => !element.hide), code: 1 })
})
app.get('/task/:no', function (req, res) {
const task = tasks.find((element) => element.id == req.params.no)
console.log('get task', task)
if (task) {
res.send({ data: task, code: 1 })
} else {
res.send({ data: "No data", code: -1 })
}
})
app.post('/task/add', function (req, res) {
const data = req.body
console.log('task add', data)
if (checkData(data.text) && checkData(data.day) && checkData(data.reminder)) {
const newTest = { id: tasks.length + 1, ...data }
tasks.push(newTest)
res.send({ data: newTest, code: 1 })
} else {
res.send({ data: `Request failed. (${JSON.stringify(data)})`, code: -1 })
}
})
app.post('/task/delete', function (req, res) {
const data = req.body
console.log('task delete', data)
const task = tasks.find((element) => element.id == data.id)
if (checkData(task)) {
task.hide = true
res.send({ data: task.id, code: 1 })
} else {
res.send({ data: `Request failed. (${JSON.stringify(data)})`, code: -1 })
}
})
app.post('/task/toggle', function (req, res) {
const data = req.body
console.log('task toggle', data)
const task = tasks.find((element) => element.id == data.id)
if (checkData(task)) {
task.reminder = !task.reminder
res.send({ data: task.id, code: 1 })
} else {
res.send({ data: `Request failed. (${JSON.stringify(data)})`, code: -1 })
}
})
// 8000 포트로 서버 오픈
app.listen(8080, function () {
console.log("start! express server on port 8080")
})
네.. 진짜 별거 없습니다
express에 있는거 가져다가 8080번으로 포트 열어주고
자료형은 간단하게 Array안에 Map {id, text, day, reminder, hide}형태로 들어가고
조회는 id로 삭제는 직접삭제가 아닌 hide형태로, 업데이트는 reminder부분만 하고 있습니다
전체조회 get : /task
특정 인덱스 조회 get : /task/:no (react쪽에 구현은 해놨지만 사용은 안하고 있습니다)
추가 post : /task/add (body는 task)
삭제(숨기기) post : /task/delete (index만 가져와서 처리)
업데이트(토글) post : /task/toggle (index만 가져와서 처리)
정도로 구현을 해놨고 응답으로 data와 code를 보내서
프론트 부분에서 code로 api 콜이 성공했는지, 실패했는지를 판단하고 data값을 받아서 처리를 해줍니다
그 다음으로는 React부분으로 가서..
api
const baseURL = "http://localhost:8080"
// 성공: code: 1, data : object
// 실패: code: -1, data : 실패 message
class API {
getTasks = async (id) => {
let url = `${baseURL}/task`
if (id != null) {
url += `/${id}`
}
const res = await fetch(url)
const result = await res.json()
return {
data: result.data,
code: result.code
}
}
// task
addTask = async (task) => {
const res = await fetch(`${baseURL}/task/add`, {
method: "POST",
headers: { "content-type": "application/json"},
body: JSON.stringify(task)
})
const result = await res.json()
return {
data: result.data,
code: result.code
}
}
deleteTask = async (id) => {
const res = await fetch(`${baseURL}/task/delete`,{
method: "POST",
headers: { "content-type": "application/json"},
body: JSON.stringify({id: id})
})
const result = await res.json()
return {
data: result.data,
code: result.code
}
}
toggleReminder = async (id) => {
const res = await fetch(`${baseURL}/task/toggle`,{
method: "POST",
headers: { "content-type": "application/json"},
body: JSON.stringify({id: id})
})
const result = await res.json()
return {
data: result.data,
code: result.code
}
}
}
export { API }
백 부분에서 정의 해놓은대로 url과 method, header, body를 셋팅해서 콜을 합니다
(간단하게 만들다보니 에러처리를 따로 못해줬는데 필요할거 같습니다..)
class형 메인
import { Component } from "react"
import Header from './header'
import Tasks from './tasks'
import AddTask from './add_task'
import { API } from '../../api/common'
class ClassApp extends Component {
constructor() {
super()
this.state = {
showAddTask: false,
tasks: []
}
this.api = new API()
this.addTask = this.addTask.bind(this)
this.deleteTask = this.deleteTask.bind(this)
this.toggleReminder = this.toggleReminder.bind(this)
}
async initTask() {
const result = await this.api.getTasks()
if (result.code == 1) {
this.setState({ tasks: result.data })
} else {
alert(result.data)
}
}
componentDidMount() {
this.initTask()
}
async addTask(task) {
const result = await this.api.addTask(task)
if (result.code == 1) {
this.setState({ tasks: [...this.state.tasks, result.data] })
} else {
alert(result.data)
}
}
async deleteTask(id) {
const result = await this.api.deleteTask(id)
if (result.code == 1) {
const task = this.state.tasks.find((element) => element.id == result.data)
task.hide = true
this.setState({ tasks: this.state.tasks.filter((element) => !element.hide) })
} else {
alert(result.data)
}
}
async toggleReminder(id) {
const result = await this.api.toggleReminder(id)
if (result.code == 1) {
const task = this.state.tasks.find((element) => element.id == result.data)
task.reminder = !task.reminder
this.setState({ tasks: this.state.tasks })
} else {
alert(result.data)
}
}
render() {
return (
<div className="container">
<Header
onAdd={() => this.setState({ showAddTask: !this.state.showAddTask })}
showAdd={this.state.showAddTask} />
{this.state.showAddTask && <AddTask onAdd={this.addTask} />}
{this.state.tasks.length > 0 ?
<Tasks tasks={this.state.tasks} onDelete={this.deleteTask} onToggle={this.toggleReminder} /> : 'No tasks to show'}
</div>
)
}
}
export default ClassApp;
함수형 main
import { useState, useEffect } from "react"
import Header from './header'
import Tasks from './tasks'
import AddTask from './add_task'
import { API } from '../../api/common'
function FunctionalApp() {
const [showAddTask, setShowAddTask] = useState(false)
const [tasks, setTasks] = useState([])
const api = new API()
const initTask = async () => {
const result = await api.getTasks()
if (result.code == 1) {
setTasks(result.data)
}
}
useEffect(async () => {
await initTask()
}, [])
const addTask = async (task) => {
const result = await api.addTask(task)
if (result.code == 1) {
setTasks([...tasks, result.data])
} else {
alert(result.data)
}
}
const deleteTask = async (id) => {
const result = await api.deleteTask(id)
if (result.code == 1) {
const task = tasks.find((element) => element.id == result.data)
task.hide = true
setTasks(tasks.filter((element) => !element.hide))
} else {
alert(result.data)
}
}
const toggleReminder = async (id) => {
const result = await api.toggleReminder(id)
if (result.code == 1) {
const task = tasks.find((element) => element.id == result.data)
task.reminder = !task.reminder
console.log(task)
setTasks([...tasks])
} else {
alert(result.data)
}
}
return (
<div className="container">
<Header onAdd={() => setShowAddTask(!showAddTask)} showAdd={showAddTask} />
{showAddTask && <AddTask onAdd={addTask} />}
{tasks.length > 0 ? <Tasks tasks={tasks} onDelete={deleteTask} onToggle={toggleReminder} /> : 'No tasks to show'}
</div>
);
}
export default FunctionalApp;
header
import Button from './button'
import { Component } from 'react'
class Header extends Component {
render() {
const {title, showAdd, onAdd} = this.props
return (
<header className="header">
<h1>{title}</h1>
<Button color={showAdd ? 'red' : 'green'} text={showAdd ? 'Close' : 'Add'} onClick={onAdd} />
</header>
)
}
}
export default Header
button
import { Component } from "react"
class Button extends Component {
render() {
const { color, text, onClick } = this.props
return (
<button onClick={onClick} style={{ backgroundColor: color }} className="btn">{text}</button>
)
}
}
export default Button
task
import { Component } from 'react'
import { FaTimes } from 'react-icons/fa'
class Task extends Component {
render() {
const { task, onDelete, onToggle } = this.props
return (
<div className={`task ${task.reminder ? 'reminder' : ''}`} onDoubleClick={() => onToggle(task.id)}>
<h3>
{task.text} <FaTimes style={{ color: 'red', cursor: 'pointer' }} onClick={() => onDelete(task.id)} />
</h3>
<p>{task.day}</p>
</div>
)
}
}
export default Task
tasks
import { Component } from 'react'
import Task from './task'
class Tasks extends Component {
render() {
const { tasks, onDelete, onToggle } = this.props
return (
<div>
{tasks.map((task) => (
<Task key={task.id} task={task} onDelete={onDelete} onToggle={onToggle} />
))}
</div>
)
}
}
export default Tasks
add_task
import { Component } from "react"
class AddTask extends Component {
constructor(props) {
super(props)
this.state = {
text: "",
day: "",
reminder: false
}
this.onSubmit = this.onSubmit.bind(this)
}
onSubmit(e) {
e.preventDefault()
if (this.state.text === "") {
alert('Please add a task')
return
}
this.props.onAdd({ text: this.state.text, day: this.state.day, reminder: this.state.reminder })
this.setState({
text: "",
day: "",
reminder: false
})
}
render() {
return (
<form className="add-form" onSubmit={(e) => this.onSubmit(e)}>
<div className="form-control">
<label>Task</label>
<input type="text" placeholder="Add Task" value={this.state.text} onChange={(e) => this.setState({text: e.target.value})} />
</div>
<div className="form-control">
<label>Day & Time</label>
<input type="text" placeholder="Add Day & Time" value={this.state.day} onChange={(e) => this.setState({day: e.target.value})} />
</div>
<div className="form-control form-control-check">
<label>Set Reminder</label>
<input type="checkbox" value={this.state.reminder} checked={this.state.reminder} onChange={(e) => this.setState({reminder: e.currentTarget.checked})} />
</div>
<input type="submit" value="Save Task" className="btn btn-block" />
</form>
)
}
}
export default AddTask
index js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// import App from './App';
import reportWebVitals from './reportWebVitals';
import FunctionalApp from './component/functional/functional_app'
import ClassApp from './component/class/class_app'
ReactDOM.render(
<React.StrictMode>
{/* <App /> */}
<FunctionalApp />
{/* <ClassApp /> */}
</React.StrictMode>,
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();
index css
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400&display=swap');
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Poppins', sans-serif;
}
.container {
max-width: 500px;
margin: 30px auto;
overflow: auto;
min-height: 300px;
border: 1px solid steelblue;
padding: 30px;
border-radius: 5px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.btn {
display: inline-block;
background: #000;
color: #fff;
border: none;
padding: 10px 20px;
margin: 5px;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
font-size: 15px;
font-family: inherit;
}
.btn:focus {
outline: none;
}
.btn:active {
transform: scale(0.98);
}
.btn-block {
display: block;
width: 100%;
}
.task {
background: #f4f4f4;
margin: 5px;
padding: 10px 20px;
cursor: pointer;
}
.task.reminder {
border-left: 5px solid green;
}
.task h3 {
display: flex;
align-items: center;
justify-content: space-between;
}
.add-form {
margin-bottom: 40px;
}
.form-control {
margin: 20px 0;
}
.form-control label {
display: block;
}
.form-control input {
width: 100%;
height: 40px;
margin: 5px;
padding: 3px 7px;
font-size: 17px;
}
.form-control-check {
display: flex;
align-items: center;
justify-content: space-between;
}
.form-control-check label {
flex: 1;
}
.form-control-check input {
flex: 2;
height: 20px;
}
footer {
margin-top: 30px;
text-align: center;
}
함수형과 class형은 구현 방법만 다르고 UI 및 기능은 동일합니다
그리고 UI와 이벤트 부분은 위에 동영상에서 그대로 가져와서 썼고
필자가 직접 구현한 부분은 REST API 연결부분 및 처리만 구현했습니다
그런데.. 함수형 class형 소스 두개를 붙였더니 생각보다 많이 길어진거 같아서
필자가 구현한 class형 부분만 넣었고 나머지는 링크로 대체합니다
(유튜브 예제 소스 : https://github.com/bradtraversy/react-crash-2021)
(react : GitHub - devmemory/react_example) - 현재 로직 일부 개선
직접 테스트 해보고 싶으신분은 npm i를 꼭 해주세요
(아무도 없겠지만..)
-- 이하 필자의 헛소리 타임이 시작될 예정이니 예제만 확인하실 분들은 스킵하시면 됩니다
먼저 함수형으로 따라서 할 때는 굳이 왜 hook을 사용하는거지? 라는 생각이 들었는데
직접 class형으로 한번 만들어놓고 보니 왜 만들었는지 느낌은 왔습니다만
그래도 계속 Flutter를 해와서 그런지 class형태가 조금 더 재밌었던것 같긴 한데..
아직은 "뭐가 났다"라고 판단하기에는 너무 투자한 시간이 적어서 모르겠습니다
그리고 상태관리로 setState만 사용해봤는데 redux 및 다른 방식도
한번 시도를 해보는게 좋을거 같다는 생각이 들었습니다
그리고 Svelte가 왜 쉬운 프레임워크인지 다시 한번 깨달았던 좋은 기회가 됐습니다만
한동안은 React를 조금 더 파볼 예정입니다
Flutter나 Svelte 포스팅은 아마도 정말 뜨문뜨문 혹은 그만..
아마도 재밌는 부분 혹은 삽질하게 만들어서 멋지게(?) 해결한 부분이 있다면
포스팅 할 예정입니다
'해왔던 삽질..' 카테고리의 다른 글
[Mac] commplex-main on port 5000 (0) | 2022.04.13 |
---|---|
Flutter - kakaomap webview 0.4.0 업데이트 후기(Feat. Webview, Intent scheme) (0) | 2022.03.29 |
Svelte - Simple test (0) | 2021.11.21 |
요즘 Flutter 포스팅을 안(못)하는 이유 (Feat. Web) (2) | 2021.11.13 |
Flutter - kakaomap webview 0.3.0 업데이트 후기 (2) | 2021.09.21 |