
코딩앙마
강의 목록 : 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)  
댓글 남기기