
코딩앙마
강의 목록 : https://www.youtube.com/playlist?list=PLZKTXPmaJk8J_fHAzPLH8CJ_HO_M33e7-
vscode 확장 패키지 추가
1. - Auto Import - ES6, TS, JSX, TSX
2. - Reactjs code snippets
3. - ESLint
4. - Prettier - Code formatter
Visual Studio Code 폴더/파일 아이콘 변경하기
리액트 프로젝트 생성
npx create-react-app 경로
예) npx create-react-app E:\react-app2
11. json-server, REST API

json-server 설치
$ npm i -g json-server
$ yarn global add json-server
실행
json-server --watch ./src/db/data.json --port 3001
Resources
http://localhost:3001/days
http://localhost:3001/words
REST API
Create : POST
Read : GET
Update : PUT
Delete : DELETE
Day.js
import dummy from '../db/data.json';
import { useLocation, useParams } from 'react-router-dom';
import Word from './Word';
const Day = () => {
// const location = useLocation();
// const params = useParams();
// console.log("params ", params, "location ", location);
const { day } = useParams();
const wordList = dummy.words.filter(word => {
return Number(word.day) === Number(day)
});
return (
<>
<h2>Day {day}</h2>
<table>
<tbody>
{wordList.map(word => (
<Word key={word.id} word={word} />
))}
</tbody>
</table>
</>
);
};
export default Day;
Word.js
import React from 'react';
import { useState } from 'react';
const Word = ({ word }) => {
const [isShow, setIsShow] = useState(false);
const [isDone, setIsDone] = useState(word.isDone);
function toggleShow() {
setIsShow(!isShow);
}
function toggleDone() {
setIsDone(!isDone);
}
return (
<tr key={word.id} className={isDone ? 'off' : ''}>
<td>
<input type="checkbox" checked={isDone}
onChange={toggleDone}
/>
</td>
<td>{word.eng}</td>
<td>{isShow && word.kor}</td>
<td>
<button onClick={toggleShow}>뜻 {isShow ? '숨기기' : '보기'}</button>
<button className='btn_del'>삭제</button>
</td>
</tr>
);
};
export default Word;
12. useEffect, fetch()로 API 호출
DayList.js
import { Link } from 'react-router-dom';
import { useEffect, useState } from 'react';
const DayList = () => {
const [days, setDays] = useState([]);
/*
const [count, setCount] = useState(0);
function onClick() {
setCount(count + 1);
}
function onClick2() {
setDays([...days, {
id: new Date(),
day: (Math.floor(Math.random() * 31)) + 1,
}]);
}
useEffect(() => {
console.log("Counter Chnage");
}, [count])
*/
useEffect(() => {
fetch("http://localhost:3001/days")
.then((res) => res.json())
.then(data => {
// console.log("data :", data);
setDays(data);
}).catch(err => {
console.error("에러 :", err);
});
}, []);
return (
<>
<ul className="list_day">
{days.map(day => (
<li key={day.id} id={day.id}>
<Link to={`/day/${day.day}`}>Day {day.day}</Link>
</li>
))
}
</ul >
{/* <button onClick={onClick}>{count}</button>
<button onClick={onClick2}>Day Change</button> */}
</>
);
};
export default DayList;
Day.js
import { useParams } from 'react-router-dom';
import Word from './Word';
import { useState, useEffect } from 'react';
const Day = () => {
const { day } = useParams();
// const wordList = dummy.words.filter(word => {
// return Number(word.day) === Number(day)
// });
const [words, setWords] = useState([]);
useEffect(() => {
fetch(`http://localhost:3001/words?day=${day}`)
.then((res) => res.json())
.then(data => {
//console.log(data);
setWords(data);
}).catch(err => {
console.error("에러:", err);
});
}, [day]);
return (
<>
<h2>Day {day}</h2>
<table>
<tbody>
{words.map(word => (
<Word key={word.id} word={word} />
))}
</tbody>
</table>
</>
);
};
export default Day;
13. Custom Hooks
훅 생성
useFetch.js
import { Link } from 'react-router-dom';
import useFetch from './../hooks/useFetch';
const DayList = () => {
const days = useFetch("http://localhost:3001/days");
return (
<>
<ul className="list_day">
{days.map(day => (
<li key={day.id} id={day.id}>
<Link to={`/day/${day.day}`}>Day {day.day}</Link>
</li>
))
}
</ul >
</>
);
};
export default DayList;
DayList.js
import { Link } from 'react-router-dom';
import useFetch from './../hooks/useFetch';
const DayList = () => {
const days = useFetch("http://localhost:3001/days");
return (
<>
<ul className="list_day">
{days.map(day => (
<li key={day.id} id={day.id}>
<Link to={`/day/${day.day}`}>Day {day.day}</Link>
</li>
))
}
</ul >
</>
);
};
export default DayList;
Day.js
import { useParams } from 'react-router-dom';
import Word from './Word';
import useFetch from './../hooks/useFetch';
const Day = () => {
const { day } = useParams();
const words = useFetch(`http://localhost:3001/words?day=${day}`);
return (
<>
<h2>Day {day}</h2>
<table>
<tbody>
{words.map(word => (
<Word key={word.id} word={word} />
))}
</tbody>
</table>
</>
);
};
export default Day;
14. PUT(수정), DELETE(삭제)
Word.js
import React from 'react';
import { useState } from 'react';
const Word = ({ word: w }) => {
const [word, setWord] = useState(w);
const [isShow, setIsShow] = useState(false);
const [isDone, setIsDone] = useState(word.isDone);
function toggleShow() {
setIsShow(!isShow);
}
function toggleDone() {
fetch(`http://localhost:3001/words/${word.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json; charset=utf-8"
},
body: JSON.stringify({
...word,
isDone: !isDone
}),
}).then((res) => {
if (res.ok) {
setIsDone(!isDone);
}
})
}
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;
}
return (
<tr key={word.id} className={isDone ? 'off' : ''}>
<td>
<input type="checkbox" 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>
);
};
export default Word;
15.POST(생성), useNavigate()


useHistory =>eact-router-dom@6 이상 사용안함 = > useNavigate 으로 변경할것
App.js
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Day from './component/Day';
import DayList from './component/DayList';
import EmptyPage from './component/EmptyPage';
import Header from './component/Header';
import CreateWord from './component/CreateWord';
import CreateDay from './component/CreateDay';
function App() {
return (
<BrowserRouter>
<div className="App">
<Header />
<Routes>
<Route path="/" element={<DayList />} />
<Route path="/day/:day" element={<Day />} />
<Route path="/create_word" element={<CreateWord />} />
<Route path="/create_day" element={<CreateDay />} />
<Route path={"*"} element={<EmptyPage />} />
</Routes>
</div>
</BrowserRouter >
);
}
export default App;
CreateWord.js
import { useRef } from 'react';
import useFetch from './../hooks/useFetch';
import { useNavigate } from 'react-router-dom';
const CreateWord = () => {
const engRef = useRef(null);
const korRef = useRef(null);
const dayRef = useRef(null);
const days = useFetch("http://localhost:3001/days");
const navigate = useNavigate();
//useHistory =>eact-router-dom@6 이상 사용안함 = > useNavigate 으로 변경할것
//const history = useHistory();
function onSubmit(e) {
e.preventDefault();
// console.log("dayRef :", dayRef.current.value);
// console.log("engRef :", engRef.current.value);
// console.log("kor :", e.target.kor.value);
const eng = engRef.current;
const kor = e.target.kor;
if (!eng.value) {
alert("eng 를 입력해 주세요.");
eng.focus();
return;
}
if (!kor.value) {
alert("kor 를 입력해 주세요.");
kor.focus();
return;
}
fetch(`http://localhost:3001/words`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
day: dayRef.current.value,
eng: eng.value,
kor: kor.value,
isDone: false
}),
})
.then(res => {
if (res.ok) {
alert("생성이 완료 되었습니다.");
// engRef.current.value = "";
// e.target.kor.value = "";
// dayRef.current.value = 1;
navigate(`/day/${dayRef.current.value}`);
}
});
}
return (
<form onSubmit={onSubmit}>
<div className="input_area">
<label>Eng</label>
<input type="text" name="eng" placeholder="cumputer" ref={engRef} />
</div>
<div className="input_area">
<label>Kor</label>
<input type="text" name="kor" placeholder="컴퓨터" ref={korRef} />
</div>
<div className="input_area">
<label>Day</label>
<select name="day" ref={dayRef} >
{days.map(day => (
<option key={day.id} value={day.day}>{day.day}</option>
))}
</select>
</div>
<button>저장</button>
</form>
);
};
export default CreateWord;
CreateDay.js
import { useNavigate } from 'react-router-dom';
import useFetch from './../hooks/useFetch';
const CreateDay = () => {
const days = useFetch("http://localhost:3001/days");
const navigate = useNavigate();
function addDay(e) {
e.preventDefault();
fetch(`http://localhost:3001/days/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
day: days.length + 1
}),
})
.then(res => {
if (res.ok) {
//alert("생성이 완료 되었습니다.");
navigate(`/`);
}
});
}
return (
<div>
<h3>현재 일수 : {days.length}일</h3>
<button onClick={addDay}>Day 추가</button>
</div >
);
};
export default CreateDay;
16. 마치며
src/component/CreateWord.js
import { useRef } from 'react';
import useFetch from './../hooks/useFetch';
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';
const CreateWord = () => {
const engRef = useRef(null);
const korRef = useRef(null);
const dayRef = useRef(null);
const [isLoading, setIsLoading] = useState(false);
const days = useFetch("http://localhost:3001/days");
const navigate = useNavigate();
//useHistory =>eact-router-dom@6 이상 사용안함 = > useNavigate 으로 변경할것
//const history = useHistory();
function onSubmit(e) {
e.preventDefault();
if (!isLoading) {
const eng = engRef.current;
const kor = e.target.kor;
if (!eng.value) {
alert("eng 를 입력해 주세요.");
eng.focus();
return;
}
if (!kor.value) {
alert("kor 를 입력해 주세요.");
kor.focus();
return;
}
setIsLoading(true);
fetch(`http://localhost:3001/words`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
day: dayRef.current.value,
eng: eng.value,
kor: kor.value,
isDone: false
}),
})
.then(res => {
if (res.ok) {
alert("생성이 완료 되었습니다.");
engRef.current.value = "";
e.target.kor.value = "";
//dayRef.current.value = 1;
// navigate(`/day/${dayRef.current.value}`);
setIsLoading(false);
}
});
}
}
return (
<form onSubmit={onSubmit}>
<div className="input_area">
<label>Eng</label>
<input type="text" name="eng" placeholder="cumputer" ref={engRef} />
</div>
<div className="input_area">
<label>Kor</label>
<input type="text" name="kor" placeholder="컴퓨터" ref={korRef} />
</div>
<div className="input_area">
<label>Day</label>
<select name="day" ref={dayRef} >
{days.map(day => (
<option key={day.id} value={day.day}>{day.day}</option>
))}
</select>
</div>
<button
style={
{
opacity: isLoading ? 0.3 : 1
}
}
> {isLoading ? "Saving" : "저장"}</button>
</form>
);
};
export default CreateWord;
src/component/Day.js
import { useParams, useNavigate } from 'react-router-dom';
import Word from './Word';
import useFetch from './../hooks/useFetch';
const Day = () => {
const { day } = useParams();
const days = useFetch(`http://localhost:3001/days`);
const words = useFetch(`http://localhost:3001/words?day=${day}`);
const navigate = useNavigate();
return (
<>
<h2>Day {day}</h2>
{words.length === 0 && <span>등록된 단어가 없습니다.</span>}
<table>
<tbody>
{words.map(word => (
<Word key={word.id} word={word} />
))}
</tbody>
</table>
<div style={{
marginTop: "30px",
display: "flex",
justifyContent: "space-evenly"
}}>
<button
onClick={() => {
if (Number(day) > 1) {
navigate(`/day/${Number(day) - 1}`)
}
}}
style={{
opacity: Number(day) === 1 ? 0.3 : 1
}}
>이전</button>
<button onClick={() => {
navigate(`/day/${Number(day) + 1}`)
}}
style={{
opacity: Number(day) === (days.length) ? 0.3 : 1
}}
>다음</button>
</div>
</>
);
};
src/component/DayList.js
if (days.length === 0) {
return <div className='d-flex justify-content-center loading-spinner'
style={{
marginTop: "50px",
textAlign: "center"
}}
>
<div className="spinner-border text-danger"
style={{
width: '3rem',
height: '3rem'
}}
role="status" >
<span className="sr-only">Loading...</span>
</div>
</div >
}
src/component/Word.js
17. 부록 : 타입스크립트를 적용해보자!
리액트 타입스크립트 설치
#npm npm install typescript @types/node @types/react @types/react-dom @types/jest @types/react-router-dom #yarn yarn add typescript @types/node @types/react @types/react-dom @types/jest @types/react-router-dom
info Direct dependencies
├─ @types/jest@28.1.8
├─ @types/node@18.7.13
├─ @types/react-dom@18.0.6
├─ @types/react-router-dom@5.3.3
├─ @types/react@18.0.17
└─ typescript@4.8.2
info All dependencies
├─ @types/jest@28.1.8
├─ @types/node@18.7.13
├─ @types/react-dom@18.0.6
├─ @types/react-router-dom@5.3.3
├─ @types/react-router@5.1.18
├─ @types/react@18.0.17
└─ typescript@4.8.2
Done in 10.65s.
* 환경설정 파일 변경
1. jsconfig.json 파일 -> tsconfig.json 파일명 및 내용 변경
2.. jsx &. js 확장자 파일 ->. tsx 파일로 확장자 변경
- tsconfig.json 기본 설정 내용

tsconfig.json 기본 설정 내용
{
"compilerOptions": {
"target": "es6",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
타입스크립트로 변경
Word.tsx
~
interface IProps{
word: IWord,
}
export interface IWord{
day?: string,
eng?:string,
kor?: string,
isDone?: boolean,
id:number
}
const Word = ({word: w }:IProps) => {
~~
~~
function del() {
if (window.confirm("삭제 하시겠습니까?")) {
fetch(`http://localhost:3001/words/${word.id}`, {
method: "DELETE",
}).then((res) => {
if (res.ok) {
setWord({
...word,
id:0
});
}
});
}
}
~~
Day.tsx
import { useParams, useNavigate } from 'react-router-dom';
import Word, { IWord } from './Word';
import useFetch from '../hooks/useFetch';
const Day = () => {
const { day } = useParams<{day:string}>();
const days = useFetch(`http://localhost:3001/days`);
const words :IWord[] = useFetch(`http://localhost:3001/words?day=${day}`);
~~
~
DayList.tsx
~
interface IDay {
id: 3,
day: 3
}
const DayList = () => {
const days :IDay[] = useFetch("http://localhost:3001/days");
~
CreateWord.tsx
import { useRef } from 'react';
import useFetch from '../hooks/useFetch';
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';
import { IDay } from './DayList';
const CreateWord = () => {
const engRef = useRef<HTMLInputElement>(null);
const korRef = useRef<HTMLInputElement>(null);
const dayRef = useRef<HTMLSelectElement>(null);
const [isLoading, setIsLoading] = useState(false);
const days :IDay[] = useFetch("http://localhost:3001/days");
const navigate = useNavigate();
//useHistory =>eact-router-dom@6 이상 사용안함 = > useNavigate 으로 변경할것
//const history = useHistory();
function onSubmit(e : React.FormEvent) {
e.preventDefault();
if (!isLoading && dayRef.current && engRef.current && korRef.current) {
const eng = engRef.current;
const kor = korRef.current;
const day = dayRef.current.value;
~~
~
useFetch.ts
import { useState, useEffect } from 'react';
const useFetch = (url:string) => {
const [data, setData] = useState([]);
~
소스 : https://github.com/braverokmc79/React-for-beginners














댓글 ( 4)
댓글 남기기