[코딩앙마] React JS 10~17차

김서윤·2024년 1월 22일

[코딩앙마] React JS

목록 보기
2/2
post-thumbnail

라우터 구현

react-router-dom

  • App.js
    import DayList from "./component/DayList";
    import Header from "./component/Header";
    import Day from "./component/Day";
    import { BrowserRouter, Route, Routes } from "react-router-dom";
    
    function App() {
      return (
        <BrowserRouter>
          <div className="App">
            <Header />
            <Routes>
              <Route exact path="/" element={<DayList />} />
              <Route path="/day" element={<Day />} />
            </Routes>
          </div>
        </BrowserRouter>
      );
    }
    
    export default App;

→ 강의에서는 switch 사용 but 오류 발생

→ Routes로 변경 후 element 사용 → 해결

→ 버전 차이?

  • Header.js
    import { Link } from "react-router-dom";
    
    export default function Header() {
      return (
        <div className="header">
          <h1>
            <Link to="/">토익 영단어(고급)</Link>
          </h1>
          <div className="menu">
            <a href="#x" className="link">
              단어 추가
            </a>
            <a href="#x" className="link">
              Day 추가
            </a>
          </div>
        </div>
      );
    }

import { Link } from "react-router-dom"; : 라우터의 Link 기능 임포트

<Link to="/"> : 처음의 화면

  • DayList.js
    import { Link } from "react-router-dom";
    import dummy from "../db/data.json";
    
    export default function DayList() {
      console.log(dummy);
      return (
        <ul className="list_day">
          {dummy.days.map((day) => (
            <li key={day.id}>
              <Link to={`/day/${day.day}`}>Day {day.day}</Link>
            </li>
          ))}
        </ul>
      );
    }

{/day/${day.day}} : 각 day 별 페이지로 연결

  • Day.js
    import dummy from "../db/data.json";
    import { useParams } from "react-router-dom";
    
    export default function Day() {
      //   dummy.words;
    
      const day = useParams().day;
      const wordList = dummy.words.filter((word) => word.day === Number(day));
    
      return (
        <>
          <h2>Day {day}</h2>
          <table>
            <tbody>
              {wordList.map((word) => (
                <tr key={word.id}>
                  <td>{word.eng}</td>
                  <td>{word.kor}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </>
      );
    }

useParams : 각 day별 단어를 가져올 수 있게 하는 기능

Number(day) : json에 day를 숫자값으로 저장해줬기 때문에 숫자로 입력해야 데이터 사용 가능

  • EmptyPage.js
    import { Link } from "react-router-dom";
    
    export default function EmptyPage() {
      return (
        <>
          <h2>잘못된 접근입니다.</h2>
          <Link to="/">돌아가기</Link>
        </>
      );
    }

→ 없는 주소에 접속했을 때 나오는 페이지

  • App.js
    import DayList from "./component/DayList";
    import Header from "./component/Header";
    import Day from "./component/Day";
    import { BrowserRouter, Route, Routes } from "react-router-dom";
    import EmptyPage from "./component/EmptyPage";
    
    function App() {
      return (
        <BrowserRouter>
          <div className="App">
            <Header />
            <Routes>
              <Route exact path="/" element={<DayList />} />
              <Route path="/day/:day" element={<Day />} />
              <Route path="*" element={<EmptyPage />} />
            </Routes>
          </div>
        </BrowserRouter>
      );
    }
    
    export default App;

<Route path="*" element={<EmptyPage />} /> : 맨 아래에 없는 주소의 페이지에 대한 정의

json-server, REST API

Word 컴포넌트 생성

  • Word.js
    import { useState } from "react";
    
    export default function Word({ word }) {
      const [isShow, setIsShow] = useState(false);
      const [isDone, setIsDone] = useState(word.isDone);
    
      function toggleShow() {
        setIsShow(!isShow);
      }
    
      function toggleDone() {
        setIsDone(!isDone);
      }
    
      return (
        <tr 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>
      );
    }

isShow, isDone → useState로 값 설정

toggleShow, toggleDone → 함수로 체크 박스 설정

  • Day.js
    import dummy from "../db/data.json";
    import { useParams } from "react-router-dom";
    import Word from "./Word";
    
    export default function Day() {
      //   dummy.words;
    
      const day = useParams().day;
      const wordList = dummy.words.filter((word) => word.day === Number(day));
    
      return (
        <>
          <h2>Day {day}</h2>
          <table>
            <tbody>
              {wordList.map((word) => (
                <Word word={word} key={word.id} />
              ))}
            </tbody>
          </table>
        </>
      );
    }

import Word from "./Word"; : Word 컴포넌트 임포트

<Word word={word} key={word.id} /> : Word 컴포넌트를 key 값을 설정해주며 가져오기

json-server 사용

: 명령어 사용 후 port를 3001로 수정하면 json 값이 넘어온 것 확인 가능 (명령어로 port 3001로 설정해줌)

REST API

→ method로 CRUD 기능을 하는 것

  • Create : POST
  • Read : GET
  • Update : PUT
  • Delete : DELETE

useEffect, fetch()로 API 호출

→ dummy 값을 지우고 json 사용

useEffect

: Hook → useState와 마찬가지로 리액트에서 임포트

: 어떤 상태값이 바뀌었을 때 동작하는 함수 작성 가능

  • DayList.js → 설명을 위한 코드 작성
    import { useEffect, useState } from "react";
    import { Link } from "react-router-dom";
    
    export default function DayList() {
      const [days, setDays] = useState([]);
      const [count, setCount] = useState(0);
    
      function onClick() {
        setCount(count + 1);
      }
    
      function onClick2() {
        setDays([
          ...days,
          {
            id: Math.random(),
            day: 1,
          },
        ]);
      }
    
      useEffect(() => {
        console.log("Count change");
      }, []);
    
      return (
        <>
          <ul className="list_day">
            {days.map((day) => (
              <li key={day.id}>
                <Link to={`/day/${day.day}`}>Day {day.day}</Link>
              </li>
            ))}
          </ul>
          <button onClick={onClick}>{count}</button>
          <button onClick={onClick2}>Day change</button>
        </>
      );
    }
useEffect(() => {
    console.log("Count change");
  }, [count]);

→ count(의존성 배열의 값)이 변경되었을 때만 함수(useEffect)가 실행

→ 의존성 배열

useEffect(() => {
    console.log("Count change");
  }, []);

→ 현재는 API 호출을 위해 사용하기 때문에 한번만 호출

[count] 부분(의존성 배열 부분)을 빈 배열을 입력

useEffect + API 호출

  • DayList.js
    import { useEffect, useState } from "react";
    import { Link } from "react-router-dom";
    
    export default function DayList() {
      const [days, setDays] = useState([]);
    
      useEffect(() => {
        fetch("http://localhost:3001/days")
          .then((res) => {
            return res.json();
          })
          .then((data) => {
            setDays(data);
          });
      }, []);
    
      return (
        <ul className="list_day">
          {days.map((day) => (
            <li key={day.id}>
              <Link to={`/day/${day.day}`}>Day {day.day}</Link>
            </li>
          ))}
        </ul>
      );
    }
  • Day.js
    import { useEffect, useState } from "react";
    import { useParams } from "react-router-dom";
    import Word from "./Word";
    
    export default function Day() {
      const day = useParams().day;
      const [words, setWords] = useState([]);
    
      useEffect(() => {
        fetch(`http://localhost:3001/words?day=${day}`)
          .then((res) => {
            return res.json();
          })
          .then((data) => {
            setWords(data);
          });
      }, [day]);
    
      return (
        <>
          <h2>Day {day}</h2>
          <table>
            <tbody>
              {words.map((word) => (
                <Word word={word} key={word.id} />
              ))}
            </tbody>
          </table>
        </>
      );
    }

→ 두 코드의 api를 가져와서 사용하는 useEffect 코드가 비슷

→ Custom Hook 제작 가능

Custom Hooks

Hook 만들기

→ src > hooks 폴더 만들기

→ useFetch.js 파일 생성

  • useFetch.js
    import { useEffect, useState } from "react";
    
    export default function useFetch(url) {
      const [data, setData] = useState([]);
    
      useEffect(() => {
        fetch(url)
          .then((res) => {
            return res.json();
          })
          .then((data) => {
            setData(data);
          });
      }, [url]);
    
      return data;
    }

→ 변하는 부분이 url이기 때문에 입력받는 값으로

→ 공통되는 함수이므로 data, setData 로 변경

data 값 return

useFetch를 사용하여 수정한 파일

  • DayList.js
    import { Link } from "react-router-dom";
    import useFetch from "../hooks/useFetch";
    
    export default function DayList() {
      const days = useFetch("http://localhost:3001/days");
    
      return (
        <ul className="list_day">
          {days.map((day) => (
            <li key={day.id}>
              <Link to={`/day/${day.day}`}>Day {day.day}</Link>
            </li>
          ))}
        </ul>
      );
    }
  • Day.js
    import { useParams } from "react-router-dom";
    import useFetch from "../hooks/useFetch";
    import Word from "./Word";
    
    export default function Day() {
      const day = useParams().day;
      const words = useFetch(`http://localhost:3001/words?day=${day}`);
    
      return (
        <>
          <h2>Day {day}</h2>
          <table>
            <tbody>
              {words.map((word) => (
                <Word word={word} key={word.id} />
              ))}
            </tbody>
          </table>
        </>
      );
    }

PUT(수정), DELETE(삭제)

PUT(수정)

  • Word.js
    import { useState } from "react";
    
    export default function Word({ word }) {
      const [isShow, setIsShow] = useState(false);
      const [isDone, setIsDone] = useState(word.isDone);
    
      function toggleShow() {
        setIsShow(!isShow);
      }
    
      function toggleDone() {
        // setIsDone(!isDone);
        fetch(`http://localhost:3001/words/${word.id}`, {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            ...word,
            isDone: !isDone,
          }),
        }).then((res) => {
          if (res.ok) {
            setIsDone(!isDone);
          }
        });
      }
    
      return (
        <tr 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>
      );
    }

function toggleDone() : useFetch 사용 및 수정 가능하게 변경

method: "PUT" : 게시글 수정

"Content-Type": "application/json" : 보내는 리소스의 타입이 json

DELETE(삭제)

  • Word.js
    import { useState } from "react";
    
    export default function 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() {
        // setIsDone(!isDone);
        fetch(`http://localhost:3001/words/${word.id}`, {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
          },
          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 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>
      );
    }

method: "DELETE" : 게시글 삭제

→ 삭제하는 값은 return 할 값이 없음

{ word: w } : 새로운 변수명 가능

return null; : null 값을 줌으로써 공간을 비우고 새로고침하지 않고도 페이지에 바로 반영

POST(생성), useHistory()

POST(생성), useNavigate()

  • Header.js
    import { Link } from "react-router-dom";
    
    export default function Header() {
      return (
        <div className="header">
          <h1>
            <Link to="/">토익 영단어(고급)</Link>
          </h1>
          <div className="menu">
            <Link to="/create_word" className="link">
              단어 추가
            </Link>
            <Link to="/create_day" className="link">
              Day 추가
            </Link>
          </div>
        </div>
      );
    }
  • App.js
    import DayList from "./component/DayList";
    import Header from "./component/Header";
    import Day from "./component/Day";
    import { BrowserRouter, Route, Routes } from "react-router-dom";
    import EmptyPage from "./component/EmptyPage";
    import CreateWord from "./component/CreateWord";
    import CreateDay from "./component/CreateDay";
    
    function App() {
      return (
        <BrowserRouter>
          <div className="App">
            <Header />
            <Routes>
              <Route exact 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 { useNavigate } from "react-router-dom";
    import useFetch from "../hooks/useFetch";
    
    export default function CreateWord() {
      const days = useFetch("http://localhost:3001/days");
      const navigate = useNavigate();
    
      function onSubmit(e) {
        e.preventDefault();
    
        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("생성이 완료 되었습니다");
            navigate(`/day/${dayRef.current.value}`);
          }
        });
      }
    
      const engRef = useRef(null);
      const korRef = useRef(null);
      const dayRef = useRef(null);
    
      return (
        <form onSubmit={onSubmit}>
          <div className="input_area">
            <label>Eng</label>
            <input type="text" placeholder="computer" ref={engRef} />
          </div>
          <div className="input_area">
            <label>Kor</label>
            <input type="text" placeholder="컴퓨터" ref={korRef} />
          </div>
          <div className="input_area">
            <label>Day</label>
            <select ref={dayRef}>
              {days.map((day) => (
                <option key={day.id} value={day.day}>
                  {day.day}
                </option>
              ))}
            </select>
          </div>
          <button>저장</button>
        </form>
      );
    }

useRef() : dom에 접근할 수 있게 해줌 (ex. 스크롤 위치 확인, 포커스를 줄 때)

current : 해당 요소에 접근 가능

vaule : input에 입력된 값을 얻을 수 있음

→ 현재 버전에서 history 지원 X navigate 사용 : 해당 페이지로 이동 기능

  • CreateDay.js
    import useFetch from "../hooks/useFetch";
    import { useNavigate } from "react-router-dom";
    
    export default function CreateWord() {
      const days = useFetch("http://localhost:3001/days");
      const navigate = useNavigate();
    
      function addDay() {
        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>
      );
    }

→ 단어 추가와 비슷하지만 day만 추가해주는 기능

마무리 (추가 강의)

느린 네트워크 환경

  • DayList.js
    import { Link } from "react-router-dom";
    import useFetch from "../hooks/useFetch";
    
    export default function DayList() {
      const days = useFetch("http://localhost:3001/days");
    
      if (days.length === 0) {
        return <span>Loading...</span>;
      }
    
      return (
        <ul className="list_day">
          {days.map((day) => (
            <li key={day.id}>
              <Link to={`/day/${day.day}`}>Day {day.day}</Link>
            </li>
          ))}
        </ul>
      );
    }
  • Day.js
    import { useParams } from "react-router-dom";
    import useFetch from "../hooks/useFetch";
    import Word from "./Word";
    
    export default function Day() {
      const day = useParams().day;
      const words = useFetch(`http://localhost:3001/words?day=${day}`);
    
      return (
        <>
          <h2>Day {day}</h2>
          {words.length === 0 && <span>Loading...</span>}
          <table>
            <tbody>
              {words.map((word) => (
                <Word word={word} key={word.id} />
              ))}
            </tbody>
          </table>
        </>
      );
    }

통신 중 추가 불가능하게 하는 기능

  • CreateWord.js
    import { useRef, useState } from "react";
    import { useNavigate } from "react-router-dom";
    import useFetch from "../hooks/useFetch";
    
    export default function CreateWord() {
      const days = useFetch("http://localhost:3001/days");
      const navigate = useNavigate();
      const [isLoading, setIsLoading] = useState(false);
    
      function onSubmit(e) {
        e.preventDefault();
    
        if (!isLoading) {
          setIsLoading(true);
          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("생성이 완료 되었습니다");
              navigate(`/day/${dayRef.current.value}`);
              setIsLoading(false);
            }
          });
        }
      }
    
      const engRef = useRef(null);
      const korRef = useRef(null);
      const dayRef = useRef(null);
    
      return (
        <form onSubmit={onSubmit}>
          <div className="input_area">
            <label>Eng</label>
            <input type="text" placeholder="computer" ref={engRef} />
          </div>
          <div className="input_area">
            <label>Kor</label>
            <input type="text" placeholder="컴퓨터" ref={korRef} />
          </div>
          <div className="input_area">
            <label>Day</label>
            <select 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>
      );
    }

→ 여러번 클릭하여 빈 값이 추가되는 것을 막는 역할 (Loading 관련 부분)

부록 : 타입스크립트

→ 프로젝트 초기부터 사용하는 것이 좋음

→ but 이후에도 적용 가능하니 늦었다고 생각 X

타입스크립트

js 파일 → .ts

jsx 파일 (html 사용 파일) → .tsx로 변경

  • Word.tsx
    import { useState } from "react";
    
    interface IProps {
      word: IWord;
    }
    
    export interface IWord {
      day: string;
      eng: string;
      kor: string;
      isDone: boolean;
      id: number;
    }
    
    export default function Word({ word: w }: IProps) {
      const [word, setWord] = useState(w);
      const [isShow, setIsShow] = useState(false);
      const [isDone, setIsDone] = useState(word.isDone);
    
      function toggleShow() {
        setIsShow(!isShow);
      }
    
      function toggleDone() {
        // setIsDone(!isDone);
        fetch(`http://localhost:3001/words/${word.id}`, {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
          },
          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({
                ...word,
                id: 0,
              });
            }
          });
        }
      }
    
      if (word.id === 0) {
        return null;
      }
    
      return (
        <tr 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>
      );
    }

typeany : 남발 X

interface → 여러개의 property에 각각 다른 타입 입력 가능

typescriptproperty 사용의 편리성 향상

setWord({ id: 0 })id 외의 값들을 옵션(ex. day?: string;)으로 하여 오류 해결

but 삭제 외에는 모든 값들이 필요해서 좋은 방법 X → ...word, 로 다른 값들을 넣어주는 방법으로 해결

export interface → 다른 파일에서도 인터페이스 사용 가능

  • Day.tsx
    import { useParams } from "react-router-dom";
    import useFetch from "../hooks/useFetch";
    import Word, { IWord } from "./Word";
    
    export default function Day() {
      // const day = useParams().day;
      const { day } = useParams<{ day: string }>();
      const words: IWord[] = useFetch(`http://localhost:3001/words?day=${day}`);
    
      return (
        <>
          <h2>Day {day}</h2>
          {words.length === 0 && <span>Loading...</span>}
          <table>
            <tbody>
              {words.map((word) => (
                <Word word={word} key={word.id} />
              ))}
            </tbody>
          </table>
        </>
      );
    }

key={word.id} → word가 IWord라는 것을 못 찾기 때문에 오류 발생

→ Word에서 export로 해주고 임포트하여 설정 후 해결 가능

{ day } → useParams에서 가져오는 객체 내부에 day가 있는지 확신 X로 인한 오류 발생

→ <> generic 속에 타입을 설정하여 오류 해결

  • DayList.tsx
    import { Link } from "react-router-dom";
    import useFetch from "../hooks/useFetch";
    
    export interface IDay {
      id: number;
      day: number;
    }
    
    export default function DayList() {
      const days: IDay[] = useFetch("http://localhost:3001/days");
    
      if (days.length === 0) {
        return <span>Loading...</span>;
      }
    
      return (
        <ul className="list_day">
          {days.map((day) => (
            <li key={day.id}>
              <Link to={`/day/${day.day}`}>Day {day.day}</Link>
            </li>
          ))}
        </ul>
      );
    }

IDay 라는 인터페이스를 만들어서 사용

  • CreateWord.tsx
    import React, { useRef, useState } from "react";
    import { useNavigate } from "react-router-dom";
    import useFetch from "../hooks/useFetch";
    import { IDay } from "./DayList";
    
    export default function CreateWord() {
      const days: IDay[] = useFetch("http://localhost:3001/days");
      const navigate = useNavigate();
      const [isLoading, setIsLoading] = useState(false);
    
      function onSubmit(e: React.FormEvent) {
        e.preventDefault();
    
        if (!isLoading && dayRef.current && engRef.current && korRef.current) {
          setIsLoading(true);
    
          const day: dayRef.current.value;
          const eng: engRef.current.value;
          const kor: korRef.current.value;
    
          fetch(`http://localhost:3001/words/`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              day,
              eng,
              kor,
              isDone: false,
            }),
          }).then((res) => {
            if (res.ok) {
              alert("생성이 완료 되었습니다");
              navigate(`/day/${day}`);
              setIsLoading(false);
            }
          });
        }
      }
    
      const engRef = useRef<HTMLInputElement>(null);
      const korRef = useRef<HTMLInputElement>(null);
      const dayRef = useRef<HTMLSelectElement>(null);
    
      return (
        <form onSubmit={onSubmit}>
          <div className="input_area">
            <label>Eng</label>
            <input type="text" placeholder="computer" ref={engRef} />
          </div>
          <div className="input_area">
            <label>Kor</label>
            <input type="text" placeholder="컴퓨터" ref={korRef} />
          </div>
          <div className="input_area">
            <label>Day</label>
            <select 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>
      );
    }

submit하는 이벤트 → React.FormEvent

Refcurrent는 렌더링이 완료된 이후에도 없을 수 있음 → null인지 아닌지 체크해주는 것이 필요

dayRef.current && engRef.current && korRef.current → 값이 없으면 실행하지 X

  • useFetch.ts
    import { useEffect, useState } from "react";
    
    export default function useFetch(url: string) {
      const [data, setData] = useState([]);
    
      useEffect(() => {
        fetch(url)
          .then((res) => {
            return res.json();
          })
          .then((data) => {
            setData(data);
          });
      }, [url]);
    
      return data;
    }

핵심 요약

💻 라우터 설치 명령어
npm install react-router-dom

💻 json 서버 설치 명령어
npm install -g json-server
💻 서버 위치, port 설정 명령어
json-server --watch ./src/db/data.json --port 3001
→ 두 명령어 동시 사용

💻 typescript 설치 명령어

  • npm install typescript @types/node @types/react/ @types/react-dom @types/jest
    create-react-app으로 만들어진 프로젝트에 적용하려면 뒤에까지 꼭 입력
  • npm install typescript @types/node @types/react @types/react-dom @types/jest @types/react-router-dom
    → 추가 설치한 라우터 돔까지

0개의 댓글