React

 

 

코딩앙마

 

강의 목록  :    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

 

 

 

 

 

 

 

 

 

react

 

about author

PHRASE

Level 60  라이트

인간은 영(靈)과 육(肉)으로 되어 있다. 따라서 종종, 그리고 특별히 젊은 시절에는 육에 관심이 많고 육에 관심이 많고 육에 정성을 기울인다. 그러나 그럼에도 불구하고 인간의 가장 본질적인 것은 육에 있는 것이 아니라 영에 있다. 따라서 힘써 관심을 기울이고 정성을 기울일 것은 육이 아니라 영이다. 이 점은 계속 염두해 두어야 하며, 진정한 삶이란 영,

댓글 ( 4)

댓글 남기기

작성