연습용으로 만든 Dummy데이터는 고정되었기에 사용자의 액션에 따라서 데이터를 읽고 쓰고 업데이트, 삭제해보자!
그러기 위해서는 DB를 구축하고 API를 만들어야하는데, JSON서버를 이용해서 RESTful API를 만들어 보자!
[REST API란?]
Representational State Transfe
uri주소와 메서드로 CRUD요청을 하는 것! 즉 웹 데이터 전송 방식을 말한다.
Create : POST
Read : GET
Update : PUT
Delete : DELETE
URI는 식별하고, URL은 위치를 가르킨다.
URI: 웹 기술에서 사용하는 논리적 또는 물리적 리소스를 식별하는 고유한 문자열 시퀀스
URL: 흔히 웹 주소라고도 하며, 네트워크 상에 리소스가 어디에 있는지 알려주기 위한 규약
- HTTP URI(Uniform Resource Identifier)을 통해 자원(Resource)를 명시한다.
- HTTP Method(POST, GET, DELETE, PATCH)를 통해 표현한다.
- 해당 자원(URI)에 대한 CRUE Operation을 적용하는 것을 의미한다.
웹 서비스를 만드는데 사용되는 제약의 모음으로
API 디자인에 있어 HTTP 프로토콜을 의도에 맞게 사용하도록 정의된 아키텍쳐 스타일이다.
이 제약들을 모두 만족해서 만들면 'RESTful 하다' 라고 한다.
RESTful API방식으로 웹 데이터가 전송된다.
API URI로 통신을 하는 것을 'path parameter를 사용하여 통신한다'고 한다.
JSON서버는 빠르고 쉽게 REST API를 구축해준다.
공부 목적, 작은 프로젝트로 사용 가능하다.
[셋팅하기]
1. npm 설치
npm install -g json-server
- 서버띄우기
json-server --watch ./src/db/data.json --port 3001
json-server --watch (경로) --띄울 포트넘버
Read
[JSON 데이터 처리]
1. dummyList를 API통신해서 올바른 응답받기
useEffect()
첫번째 매개변수: 함수
두번째 매개변수: 의존성배열
어떤 상태값이 바뀌었을 때 동작하는 함수를 작성할 수 있다.
함수가 호출된 시기는 렌더링 결과가 실제 dom에 반영됐을때이다. 그리고 컴포넌트가 사라지기 직전에 마지막으로 호출된다.
상태값 변경 -> useEffect 함수 호출
렌더링이 끝나고 작업을 하고 싶으면 함수를 전달해주면 되는데, 매번 변경이 될때마다 불필요하게 함수가 호출 될 수 있다. 그럴 때 두번째 매개변수를 넣어준다.
//DayList.jsx
//리스트 출력
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
// import dummy from '../db/data.json'
function DayList() {
const [days, setDays] = useState([])
useEffect(() => {
console.log('111')
fetch('http://localhost:3002/days')//response는 http응답 실제 json이아님
.then(res => {
return res.json() //json파일로 변환됨
})
.then(data => {
setDays(data)
})
}, [])
return (
<div>
<ul className="list_day">
{
days.map(dum => (
<li
key={dum.id}
>
<Link to={`/day/${dum.day}`}>Day {dum.day}</Link>
</li>
))
}
</ul>
</div >
)
}
export default DayList
- 새로운 state를 생성한다. api를 이용해 list를 가져오고 setState를 통해 자동으로 렌더링하도록 만든다.
- fetch에는 연결된 http에서 실제 응답해줄 server를 연결시킨다.
- json data를 setState값으로 넣어준다.
JSON.stringify() → 자바스크립트를 제이슨으로 변환
.json() → 제이슨 바디를 자바스크립트로 변환
//Day.jsx
// 특정 날짜를 클릭했을 때 단어가 나오게 만드는 페이지
import React, { useEffect, useState } from 'react'
import dummy from '../db/data.json'
import { useParams } from "react-router-dom"
import Word from './Word'
function Day() {
// day가 1인것만 나올 수 있도록 만들기
const { day } = useParams()
// json서버에서 데이터 통신
const [words, setWords] = useState([])
useEffect(() => {
fetch(`http://localhost:3002/words?day=${day}`)
.then(res => { return res.json() })
.then(data => setWords(data))
}, [day])
return (
<div>
<h2>Day {day}</h2>
{/* 단어를 클릭했을 때 실행되는 함수, day.id와 list.id 값을 비교해서 맞다면 출력 */}
<table>
<tbody>
{
words.map(word =>
<Word word={word} key={word.id} />
)
}
</tbody>
</table>
</div>
)
}
export default Day
- json서버 fetch후에 useParams를 통해 가져온 day를 이용해 filter시킬까?
.then(data => setWords(data.filter(db => db.day === Number(day))))
▼
fetch(http://localhost:3002/words?day=${day}`)`
이렇게해도 동작은 된다.
더 간편한 방법은 서버주소를 백틱으로 감싸서 직접 쿼리스트링과 함께 추가한다.
- useEffect에서 특정값을 사용하게 되면 의존성되는 배열값을 넣어줘야하는데 위 코드에서 day를 넣게되면 최신값이라고 보장받을 수 있다.
[겹치는 부분]
동일한 로직은 custom Hooks를 사용해서 반복되는 로직을 사용할 수 있다.
//useFetch.jsx
import React, { useEffect, useState } from 'react'
function useFetch(url) {
const [data, setData] = useState([])
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => setData(data))
}, [url])
return data
}
export default useFetch
- 서버주소를 parameter로 받아온다. 결국 필요로 하는것은 data return해주는 것이다.
- data라는 상태값이 있고 URL이란 주소를 넘겨받아서 fetch시킴 -> 응답받은 데이터를 setState해주고 data를 리턴한다.
[api로 데이터를 받아오는 부분은 한줄로 처리가 가능해졌다.]
//DayList.js
import { Link } from 'react-router-dom'
import useFetch from '../hooks/useFetch'
function DayList() {
const days = useFetch("http://localhost:3002/days")
return (
<div>
<ul className="list_day">
{
days.map(dum => (
<li
key={dum.id}
>
<Link to={`/day/${dum.day}`}>Day {dum.day}</Link>
</li>
))
}
</ul>
</div >
)
}
export default DayList
- 컴포넌트가 렌더링되면 useFetch안에 있는 useEffect가 실행된다.
- return된 값을 변수에 넣어 리스트를 만들어준다.
update
PUT method를 이용해서 isDone을 수정해보자.
현재는 isDone을 단순히 true / false가 되어있어서 새로고침시에 모습이 유지되지 않는데, put을 이용해서 새로고침시에도 작동한 그대로 남아있을 수 있게 데이터를 보내준다.
//Word.jsx
const toggleDone = () => {
// setIsDone(!isDone)
fetch(`http://localhost:3001/words/${word.id}`, {
method: "PUT", //2. 요청의 옵션을 입력한다.
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
...word,
isDone: !isDone,
}),
}).then(res => {
if (res.ok) {
setIsDone(!isDone)
}
})
}
두번째 인자로 객체를 받아온 후 해당 객체안에 요청의 옵션들을 입력한다.
- method는 "PUT"
- Content-type은 보내는 리소스 타입을 말한다. 문자열, 이미지, html, json등 다양하게 보낼 수 있다.
- body에는 단순히 가져오는 get과는 다르게 put은 수정을 위한 정보들을 실어서 보내줘야한다.
body안에는 기존data(...word)에 isDone을 바꿔서 입력시키는데, JSON.stringify를 이용해서 json문자열로 변환시킨다.
delete
- 기능구현: 삭제버튼을 누르면 comfirm창을 띄우고 삭제를 진행시킨다.
import React, { useState } from 'react'
function Word({ word }) {
...
// delete
function del() {
if (window.confirm('삭제 하시겠습니까?')) {
fetch(`http://localhost:3001/words/${word.id}`, {
method: 'DELETE',
})
}
}
return (
<div>
<tr className={isDone ? 'off' : ''}>
<td>
<input
type="checkbox"
// checked={word.isDone}
checked={isDone}
onChange={toggleDone}
/>
</td>
<td>{word.eng}</td>
<td>
{isShow && word.kor}
</td>
<td>
<button onClick={toggleShow}>
뜻 {isShow ? '숨기기' : '보기'}
</button>
<button onClick={del} className='btn_del'>삭제</button>
</td>
</tr>
</div>
)
}
export default Word
- del이라는 함수를 하나 만들고 method에 'DELETE'를 시켜준다.
- 해당 함수는 '삭제'버튼을 클릭 시 동작할 수 있게 한다.
동작하는 화면을 확인했더니..
confirm창이 뜨지만 아무 변화가 없는상태였고, 새로고침하니 삭제가 된 화면이 출력되는것을 확인할 수 있다.
실제단어는 지워지지만, 페이지에는 변화가 없는데 삭제된 이후의 단어리스트를 다시 그려주지 않아서 그렇다.
삭제요청 -> 확인 -> 컴포넌트 리렌더링 과정이 필요하다.
이때 null을 리턴해주면 아무것도 표현해주지 않는다.
그러면 data의 모습을 null로 바꾸면 클릭시 브라우저에서 삭제된 상태로 표시가 된다.
function Word({ word: w }) {
const [word, setWord] = useState(w) //w라는 새로운 변수명으로 할당
...
...
// delete
function del() {
if (window.confirm('삭제 하시겠습니까?')) {
fetch(`http://localhost:3001/words/${word.id}`, {
method: 'DELETE',
}).then(res => {
if (res.ok) {
setWord({ id: 0 })
}
})
}
}
if (word.id === 0) {
return null
}
word를 state로 만들고 props로 받아오는데, 새로운 변수명으로 받아올 수 있다.
삭제가 되면, word의 id를 0으로 바꿔준다.
word의 id가 0일경우 null을 리턴해준다.
( id가 0인상태이면 해당 데이터는 기존 데이터를 날려버리고 null이 차지하게 됨 )
알고 넘어갈 부분 state를 props로 받아 왔는데, 이름이 겹쳤던 상황이였다.
function Word(props) {
const [word, setWord] = useState(props.word)
...
또는
function Word({ word: w }) {
const [word, setWord] = useState(w)
...
새로운 변수명으로 받아옴 => 구조 분해 할당을 이용했음
POST
새로운 페이지에서 단어 추가와 day 추가 기능을 만들어 보자.
CreateWord.jsx를 하나 만들어주고 App.js에서는 route를 해주고 header에서 Link걸어주기.
import React from 'react'
import useFetch from '../hooks/useFetch'
export default function CreateWord() {
const addWord = useFetch("http://localhost:3003/days")
const handleSubmi = (e) => {
e.preventDefault()
}
return (
<div>
<form action="submit">
<div>
<label for="eng">영어 단어</label>
<input type="text" id="eng" />
</div>
<div>
<label for="kor">단어 뜻</label>
<input type="text" id="kor" />
</div>
<div>
<label for="day">추가</label>
<select name="days" id="day">
{
addWord.map(word => (
<option key={word.id} value={word.day}>day {word.day}</option>
))
}
</select>
</div>
</form>
<button onClick={handleSubmi}>
저장
</button>
</div >
)
}
useFetch로 만든 useEffect를 가져와서 map을 돌려 days를 select리스트로 만든다.
useRef()
- useRef 로 특정 DOM 선택하기
아래 코드에서는 input창에 적은 값을 얻기위해서 useRef를 사용한다.
dom에 접근할때 사용하는데, 스크롤 위치나 포커스를 줄 때 사용할 수 있다.
//createWord.jsx
import React, { useRef } from 'react'
import useFetch from '../hooks/useFetch'
export default function CreateWord() {
const addWord = useFetch("http://localhost:3003/days")
const handleSubmi = (e) => {
e.preventDefault()
console.log(engRef.current.value)
console.log(korRef.current.value)
console.log(dayRef.current.value)
}
const engRef = useRef(null)
const korRef = useRef(null)
const dayRef = useRef(null)
return (
<div>
<form action="submit">
<div>
<label for="eng">영어 단어</label>
<input type="text" id="eng" ref={engRef} />
</div>
<div>
<label for="kor">단어 뜻</label>
<input type="text" id="kor" ref={korRef} />
</div>
<div>
<label for="day">추가</label>
<select name="days" id="day" ref={dayRef}>
{
addWord.map(word => (
<option key={word.id} value={word.day}>day {word.day}</option>
))
}
</select>
</div>
</form>
<button onClick={handleSubmi}>
저장
</button>
</div >
)
}
- 먼저 useRef() 를 사용하여 Ref 객체를 만든다.
const engRef = useRef(null) const korRef = useRef(null) const dayRef = useRef(null)
- 선택하고 싶은 DOM에 ref를 연결해준다.
<input type="text" id="kor" ref={korRef} />
이렇게 연결해주면 DOM요소가 생성된 후 접근할 수 있다.
위 코드에서 저장 버튼을 클릭하는 시점은 렌더링 결과가 돔에 반영된 후이다.
- current속성을 이용하면 원하는 dom요소에 접근할 수 있다.
engRef.current.value
해당 코드에서는 current.value를 했는데, 이렇게 하면 input에 입력된 value값을 얻을 수 있다.
post를 이용해서 단어 생성하기
- fetch() 사용
fetch(`http://localhost:3001/words/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
day: dayRef.current.value,
eng: engRef.current.value,
kor: korRef.current.value,
isDone: false
}),
}).then(res => {
if (res.ok) {
// 생성완료 후 alert생성
alert('생성이 완료됐습니다.')
}
})
2. axios 사용
axios.post('http://localhost:3003/words', {
day: dayRef.current.value,
kor: korRef.current.value,
eng: engRef.current.value,
isDone: false
}).then((res) => console.log(res))
}
useNavigate()
- Link 는 특정 주소로 이동해주는 태그였다면,
Navigate 는 특정 행동을 했을 때 해당 주소로 이동해줄 수 있게 만들어준다.
양식이 제출되었거나 특정 event발생시 url을 조작해줄 수 있다.
const history = useNavigate()
...
axios.post('http://localhost:3003/words', {
day: dayRef.current.value,
kor: korRef.current.value,
eng: engRef.current.value,
isDone: false
}).then(() =>
alert('등록이 완료됐습니다.'),
history(`/day/${dayRef.current.value}`)
)
[완성된 createWord.jsx]
import { useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import axios from 'axios'
import useFetch from '../hooks/useFetch'
export default function CreateWord() {
const addWord = useFetch("http://localhost:3003/days")
const history = useNavigate()
const engRef = useRef(null)
const korRef = useRef(null)
const dayRef = useRef(null)
const handleSubmi = (e) => {
e.preventDefault()
// console.log(engRef.current.value)
// console.log(korRef.current.value)
// console.log(dayRef.current.value)
axios.post('http://localhost:3003/words', {
day: dayRef.current.value,
kor: korRef.current.value,
eng: engRef.current.value,
isDone: false
}).then(() =>
alert('등록이 완료됐습니다.'),
history(`/day/${dayRef.current.value}`)
)
}
return (
<div>
<form action="submit">
<div>
<label for="eng">영어 단어</label>
<input type="text" id="eng" ref={engRef} />
</div>
<div>
<label for="kor">단어 뜻</label>
<input type="text" id="kor" ref={korRef} />
</div>
<div>
<label for="day">추가</label>
<select name="days" id="day" ref={dayRef}>
{
addWord.map(word => (
<option key={word.id} value={word.day}>day {word.day}</option>
))
}
</select>
</div>
</form>
<button onClick={handleSubmi}>
저장
</button>
</div >
)
}
[createDay도 만들어 보자]
import axios from 'axios'
import React from 'react'
import { useNavigate } from 'react-router-dom'
import useFetch from '../hooks/useFetch'
export default function CreateDay() {
const newPage = useNavigate('')
const days = useFetch('http://localhost:3003/days')
let daysNum = days.length
const addDay = () => {
axios.post('http://localhost:3003/days', {
day: daysNum + 1
}).then(
alert('등록되었습니다.'),
newPage(`/`)
)
}
return (
<div>
<h2>현재 일수: {days.length}</h2>
<div>
<button onClick={addDay}>day 추가</button>
</div>
</div>
)
}
'Framework > React' 카테고리의 다른 글
React.memo, useCallback(), useMemo() (0) | 2022.10.26 |
---|---|
React Hook Form 회원가입 유효성 체크 (0) | 2022.10.24 |
React Context API (0) | 2022.10.12 |
conditional rendering (0) | 2022.10.11 |
리액트에서 setState는 비동기로 동작 ?? (1) | 2022.10.03 |
댓글