본문 바로가기

인강/노마드코더

[노마드코더] React 클론코딩 movie-app

React를 사용하기 위해서

react, react-dom 라이브러리를 import 해주어야 함.

 

react: 엔진과 비슷함. interactive한 UI를 만들 수 있게 해줌. element 생성, eventListener 추가.

react-dom: 모든 React element들을 HTML <body></body> 태그 부분에 둘 수 있도록 해줌. (JavaScript를 HTML element로 바꾸는 과정)

=> render() : React element들을 HTML로 만들어 배치한다. 사용자에게 보여준다.

 

React 규칙

1. HTML 코드를 직접 작성하지 않는다.

JavaScript로부터 시작해서 HTML이 생성됨. => JavaScript를 활용하여 element를 업데이트 할 수 있다.

유저에게 보여질 내용을 컨트롤 할 수 있다.

 

2. useState의 state를 변경하는 함수를 통해 state가 변경되면 모든 컴포넌트가 다시 렌더링되며, 필요한 부분들만 React가 리렌더링한다.

=> props가 변경되지 않으면 Memo를 활용하여, state가 변경될 때 모두 렌더링 되지 않고 필요하지 않은 부분은 리렌더링하지 않도록 막을 수 있다.

 

 

✅ React 코드

<script>
    const root = document.getElementById("root");
    const h3 = React.createElement(
      "h3",
      {
        id: "title",
        onMouseEnter: () => {
          console.log("mouse enter");
        },
      },
      "Hello, I'm a span"
    );
    const btn = React.createElement(
      "button",
      {
        style: { backgroundColor: "tomato" },
        onClick: () => {
          console.log("im clicked");
        },
      },
      "Click me"
    );
    const container = React.createElement("div", null, [h3, btn]);
    ReactDOM.render(container, root);
  </script>

 

 

✅ JSX 코드

  <script type="text/babel">
    const root = document.getElementById("root");
    const Title = () => (
      <h3
        id="title"
        onMouseEnter={() => {
          console.log("mouse enter");
        }}
      >
        Hello, I'm a span
      </h3>
    );
    const Button = () => (
      <button
        style={{ backgroundColor: "tomato" }}
        onClick={() => {
          console.log("im clicked");
        }}
      >
        Click me
      </button>
    );
    const Container = () => (
      <div>
        <Title />
        <Button />
      </div>
    );
    ReactDOM.render(<Container />, root);
  </script>

 

※ JSX 컴포넌트 구현

function Title() {
      return (
        <h3
          id="title"
          onMouseEnter={() => {
            console.log("mouse enter");
          }}
        >
          Hello, I'm a span
        </h3>
      );
    }

// 두 함수는 동일함

// 함수로 변환하면서 return() 안에 태그를 포함시킨 것임.
    const Title = () => (
      <h3
        id="title"
        onMouseEnter={() => {
          console.log("mouse enter");
        }}
      >
        Hello, I'm a span
      </h3>
    );

 

 

✅ React의 useState()를 사용하면 컴포넌트가 재생성된다. 그리고 바뀐 데이터만 리렌더링된다.

  <script type="text/babel">
    const root = document.getElementById("root");
    const App = () => {
      const [counter, setCounter] = React.useState(0);
      const onClick = () => {
        setCounter(counter + 1);
      };
      console.log("rendered");
      console.log(counter);
      return (
        <div>
          <h3>Total clicks: {counter}</h3>
          <button onClick={onClick}>Click me</button>
        </div>
      );
    };
    ReactDOM.render(<App />, root);
  </script>

=> 버튼이 클릭될 때마다 rendered\n{counter}가 console에 보여진다. 리렌더링되는 변경된 데이터: counter, 컴포넌트가 재생성되며 실행되는 rendered.

 

 

✅ useState의 set함수

      // 정상
      const [counter, setCounter] = React.useState(0);
      const onClick = () => {
        setCounter(counter + 1);
      };
      
      // 오류 발생. const는 재할당이 불가능하다. counter++과 동일함.
      const [counter, setCounter] = React.useState(0);
      const onClick = () => {
        setCounter(counter = counter + 1);
      };
      
      // counter의 복사가 여러번 발생하면서 반응속도가 느림.
      let [counter, setCounter] = React.useState(0);
      const onClick = () => {
        setCounter(counter++);
      };
      
      // 정상
      let [counter, setCounter] = React.useState(0);
      const onClick = () => {
        setCounter(++counter);
      };

 

 

✅ useState의 올바른 set함수 사용 방법. 현재 state로 새로운 state를 계산하는 법.

      // 가능은 하지만 추천하지 않음.
      const [counter, setCounter] = React.useState(0);
      const onClick = () => {
        setCounter(5);
      };
      
      // 올바른 방법. 현재 state를 매개변수로 받아 바뀐 상태를 반환한다.
      // ※ return 키워드가 생략된 모습 ※
      const [counter, setCounter] = React.useState(0);
      const onClick = () => {
        setCounter((current) => current + 1);
      };
      
      // 오류 발생. 단순히 `current + 1;`이라는 코드만 존재하고,
      // ※ 값을 반환하지 않고 있다. ※
      // setCounter 내부에서는 상태를 반환해야 한다!!!
      const [counter, setCounter] = React.useState(0);
      const onClick = () => {
        setCounter((current) => {
          current + 1;
        });
      };

=> setCounter()는 2가지 방법으로 사용할 수 있다.

1. 값을 직접 보내는 것. setCounter("");

2. 함수를 보내는 것. 이 때, 함수의 첫 번째 매개변수에는 기존의 state값이 들어있다.

 

 

💯💯💯  화살표 함수 return 키워드 생략

const add = (a, b) => {
  return a + b;
};

// 동일한 함수임.

const add = (a, b) => a + b;

 

💯💯💯

      const [flipped, setFlipped] = React.useState(false);
      
      const onFlip = () => setFlipped((current) => !current);
      
      // 동일한 함수임.
      
      const [flipped, setFlipped] = React.useState(false);

      const onFlip = () => {
        setFlipped((current) => {
          return !current;
        });
      };

 

 

✅ HTML <label/> 태그, <input/> 태그

<label/> 태그를 클릭하면 <input/>이 선택됨.(label htmlFor="", input id="")

=> JSX에서의 property와 HTML에서의 property는 다르다.(JSX: htmlFor, className / HTML: for, class)

 

 

✅ 인라인 형태

// 1. 인라인 함수
const add = (a, b) => a + b;

// 2. 인라인 스타일(React)
const style = { color: 'blue', fontSize: '16px' };

// 3. 인라인 조건문
const message = isLogged ? 'Welcome back!' : 'Please log in.';

// 4. 인라인 JSX(React), 익명 함수 사용.
<button onClick={() => console.log('Button clicked!')}>Click me</button>

=> 간결하고 직관적임. 가독성 향상. 익명 함수

 

 

 익명 함수.

function(){
}

() => {
}
  const [toDo, setToDo] = useState("");
  const [toDos, setToDos] = useState([]);
  
  const onSubmit = (event) => {
    event.preventDefault();
    setToDos((currentArray) => [toDo, ...currentArray]);
  };
  
  // 동일한 표현임.
  
  const onSubmit = (event) => {
    event.preventDefault();
    setToDos(function (currentArray) {
      return [toDo, ...currentArray];
    });
  };

 

 

✅ props

      // <Btn /> 컴포넌트에 대한 모든 정보를 매개변수로 받음.
      const Btn = (props) => {
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
            fontSize: props.big ? 18 : 16,
          }}
        >
          {props.text}
        </button>
      );
    };
    const App = () => {
      return (
        <div>
          <Btn text="Save Changes" big={true} />
          <Btn text="Continue" big={false} />
        </div>
      );
    };
      
      
      // props shortcut. Btn 컴포넌트의 매개변수로 Object를 받음.
      const Btn = ({ text, big }) => {
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
            fontSize: big ? 18 : 16,
          }}
        >
          {text}
        </button>
      );
    };
    const App = () => {
      return (
        <div>
          <Btn text="Save Changes" big={true} />
          <Btn text="Continue" big={false} />
        </div>
      );
    };

=> const Btn = ({text, big = false}) 과 같이 props의 기본값을 지정할 수 있다. 자바스크립트 문법.

 

 

✅ props로 넘기는 함수(onClick)

    const Btn = ({ text, onClick }) => {
      return (
        <button
          onClick={onClick}
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
          }}
        >
          {text}
        </button>
      );
    };
    
    const App = () => {
      const [value, setValue] = React.useState("Save Changes");
      const changeValue = () => {
        setValue("Revert Changes");
      };
      return (
        <div>
          <Btn text={value} onClick={changeValue} />
          <Btn text="Continue" />
        </div>
      );
    };

=> onClick은 사용자가 정의내린 이름이다. 이벤트 리스너가 아님. 직접 props를 HTML 태그에 넣어줘야 한다.

 

 

✅ 부모 컴포넌트의 state가 바뀌면 모든 자식 컴포넌트가 리렌더링된다. 하지만, Memo를 사용하면 props가 변경되지 않은 컴포넌트는 리렌더링하지 않는다. 최적화!!!

    const Btn = ({ text, changeValue }) => {
      console.log(text, "was rendered");
      return (
        <button
          onClick={changeValue}
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
          }}
        >
          {text}
        </button>
      );
    };
    
    const MemorizedBtn = React.memo(Btn);
    
    const App = () => {
      const [value, setValue] = React.useState("Save Changes");
      const changeValue = () => {
        setValue("Revert Changes");
      };
      return (
        <div>
          <MemorizedBtn text={value} changeValue={changeValue} />
          <MemorizedBtn text="Continue" />
        </div>
      );
    };

 

 

✅ props로 숫자 넘길 때 중괄호 사용. 자바스크립트 문법.

    const Btn = ({ text, fontSize }) => {
      console.log(text, "was rendered");
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
            fontSize,     // fontSize: fontSize 동일함. shortcut.
          }}
        >
          {text}
        </button>
      );
    };
    const App = () => {
      return (
        <div>
          <Btn text="Save Changes" fontSize={18} />
        </div>
      );
    };

=> HTML 태그의 속성값과 props가 동일할 때, shortcut 사용 가능.

 

 

✅ PropTypes. TypeScript 느낌? props의 속성을 지정해준다.

    Btn.propTypes = {
      text: PropTypes.string.isRequired,
      fontSize: PropTypes.number,
    };
    
    const App = () => {
      return (
        <div>
          <Btn text="Save Changes" fontSize={18} />
          <Btn text={"Continue"} />
        </div>
      );
    };

=> 여러 props를 전달받을 때, 한눈에 어떤 props를 가지고 있는지 확인할 수 있다.

 

 

✅ create-react-app

React 애플리케이션을 만들기 쉬워진다.

=> 많은 <script></script>들과 사전 설정을 준비해준다. 이를 통해,

1. 개발 서버에 접근하고

2. 자동으로 새로고침을 시켜주고

3. 즉각적으로 애플리케이션에 CSS를 적용시킨다.

 

package.json에서 <script></script>를 확인할 수 있다.

하나의 컴포넌트로 하나의 파일이 구성된다.

 

css를 module.css로 분리하여 js파일에 import 시킨다. 이때, css가 javascript 오브젝트로 변환된다. className={임포트명.btn}. HTML에서는 무작위 class 이름을 가진다.

 

🤔🤔🤔 css의 오브젝트 형태란?

JSX의 style={}에 오브젝트 형태로 css를 직접 입력하는 방식. 매우 힘듦.

하지만, module.css를 사용하면 오브젝트 형태가 아닌 css 문법을 사용하여 편하다.

 

🤔🤔🤔 HTML에서 무작위의 class 이름을 가지는 장점?

일일이 class명을 기억하지 않아도 된다. 랜덤으로 각각 다르게 작성해주기 때문이다.

module.css에서 css 문법을 사용해 작성하고, 필요한 스타일만 className={임포트명.btn} 형태로 사용할 수 있다.

💯 💯 💯

module.css 간에는 서로 독립적이므로 동일한 class명을 사용해도 된다. 다른 module.css라면 HTML에서 다르게 class명이 설정된다.

Ex) App.module.css에서의 .title과 Button.module.css에서의 .title은 서로 다른 class로 HTML에 나타난다.

 

 

✅ useEffect. Ex) 가장 처음 API를 불러온 후, state가 변경되더라도 또 다시 여러번 API를 호출할 필요 없음.

1. useEffect(함수, 빈 배열), dependencies가 빈 배열이기 때문에 React가 아무것도 지켜보지 않는다.

맨 처음 컴포넌트가 렌더링되는 순간 딱 한 번 코드를 실행하고, 다른 state변화에는 실행하지 않는다.

2. useEffect(함수, 코드를 실행하기 위해 변경되는 부분)

특정한 부분이 변경되었을 때만(update되는 경우), 코드를 실행시키고 싶은 경우. React가 dependencies를 살펴보다가, 변경되면 코드를 실행시킨다.

3. clean-up 함수. 컴포넌트가 destroy될 때, 코드를 실행시키고 싶은 경우.

 

🤔🤔🤔 가장 처음 컴포넌트가 렌더링될 때 2번씩 호출되는 이유?? 이 컴포넌트로 감싸지면서??? 추가 학습 필요

  const [counter, setValue] = useState(0);
  const [keyword, setKeyword] = useState("");
  const onClick = () => setValue((prev) => prev + 1);
  const onChange = (event) => setKeyword(event.target.value);
  console.log("i run all the time");
  useEffect(() => {
    console.log("I run only once.");
  }, []);
  useEffect(() => {
    console.log("I run when 'keyword' changes.");
  }, [keyword]);
  useEffect(() => {
    console.log("I run when 'counter' changes.");
  }, [counter]);
  useEffect(() => {
    console.log("I run when 'keyword || counter' changes.");
  }, [keyword, counter]);
  
  // 콘솔 출력 결과
  i run all the time
  i run all the time
  I run only once.
  I run when 'keyword' changes.
  I run when 'counter' changes.
  I run when 'keyword || counter' changes.
  I run only once.
  I run when 'keyword' changes.
  I run when 'counter' changes.
  I run when 'keyword || counter' changes.

 

🤔🤔🤔 useEffect는 화면이 다 그려지고 난 뒤에 실행된다!!!

=> return문이 다 실행될 때까지 기다린다. 콜백함수, 비동기 함수. 추가 학습 필요

 

🤔🤔🤔 clean-up 함수. 컴포넌트가 destroy 되는 때, 실행되는 함수. 특별한 경우에만 사용됨.

=> 컴포넌트가 종료될 때, 원하는 코드를 실행할 수 있다. 함수를 리턴한다.

function Hello() {
  function byFn() {
    console.log("destroyed");
  }
  function hiFn() {
    console.log("created");
    return byFn;
  }
  useEffect(hiFn, []);

  return <h1>Hello</h1>;
}

// 두 형태 다 가능함.
// 더 자주 쓰임. () => {}, 실행을 기다리는 중.
// foo();에서 ()가 빠진 형태 foo;
function Hello() {
  useEffect(() => {
    console.log("created");
    return () => console.log("destroyed");
  }, []);

  return <h1>Hello</h1>;
}

 

 

✅ map()란? 매개변수로 입력받은 함수를, 배열의 각 item에 대해서 적용시켜 새로운 배열을 return한다.

⁕ 매개변수로 입력받은 함수의, 첫번째 매개변수는 배열의 각 item을 나타낸다. 두번째 매개변수는 각 index를 나타낸다.

      <ul>
        {toDos.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      
      // 두 개는 동일한 표현이다. return문을 생략하느냐, 않느냐의 차이
      
      // ※ map()함수는 배열을 return 한다. ※
      // ※ 중괄호를 사용해 element를 나타낼 때는 return 키워드를 사용해야 한다. ※
      <ul>
        {toDos.map((item, index) => {
          return <li key={index}>{item}</li>;
        })}
      </ul>

 

💯💯💯 배열의 속성이 모든 item에 존재하지 않는 경우를 대비하자! 3가지 방법 모두 가능함.

        {movies.map((movie) =>
          movie.hasOwnProperty("genres") ? (
            <ul>
              {movie.genres.map((g) => (
                <li key={g}>{g}</li>
              ))}
            </ul>
          ) : null
        )}
 
        {movies.map((movie) => {
          movie.genres && movie.genres.map((g) => g);
        })}

        // Optional chaining 
        {movies.map((movie) => (
          <ul>
            {movie.genres?.map((genre) => (
              <li key={genre}>{genre}</li>
            ))}
          </ul>
        ))}

 

 

✅ async-await 문법

  // 세 함수 동일한 기능임.
  const getMovies = async () => {
    const json = await (
      await fetch(
        "https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year"
      )
    ).json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovies();
  }, []);  
  
  
  const getMovies = async () => {
    const response = await fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year"
    );
    const json = await response.json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovies();
  }, []);
  
  
  useEffect(() => {
    fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year"
    )
      .then((response) => response.json())
      .then((json) => {
        setMovies(json.data.movies);
        setLoading(false);
      });
  }, []);

 

 

✅ React Router Dom

: App.js 는 URL을 바라보고 그에 따른 component를 보여준다.

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./routes/Home";
import Detail from "./routes/Detail";

function App() {
  return (
    <>
      <Router>
        <Routes>
          <Route path="/" element={<Home />}></Route>
          <Route path="/movie" element={<Detail />}></Route>
        </Routes>
      </Router>
    </>
  );
}

=> Router의 종류에는 BrowserRouter, HashRouter가 존재하지만 주로 BrowserRouter 사용함. (일반적인 URL)

Routes는 한 번에 하나의 component만 렌더링 하도록 조절한다.

Link는 a태그와 달리 새로고침 없이 다른 페이지로 이동할 수 있는 컴포넌트이다.

 

 

✅ react-router 동적 URL

useParams: url의 변수값을 반환하는 함수(/movie/:id)

import { useParams } from "react-router-dom";

const Detail = () => {
  const x = useParams();
  console.log(x);		// {id: '58831'}
  
  const { id } = useParams();
  console.log(id);		// 58831

  return <h1>Detail</h1>;
};


function App() {
  return (
    <>
      <Router>
        <Routes>
          <Route path="/" element={<Home />}></Route>
          <Route path="/movie/:id" element={<Detail />}></Route>
        </Routes>
      </Router>
    </>
  );
}

=> url에 선언한 변수명대로 값을 반환함.

 

 

✅ React Published

: 만든 웹사이트의 production ready(코드 압축 및 최적화) code를 생성함.

{
  "name": "movie-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "gh-pages": "^6.1.1",
    "prop-types": "^15.8.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.22.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "deploy": "gh-pages -d build",		//추가
    "predeploy": "npm run build"		//추가
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "homepage": "https://dekoms.github.io/movie-app"		// 추가
}

1. npm run deploy를 입력하면 gh-pages -d build를 실행하며 배포 시작.

2. 배포 전에 predeploy 확인하여 자동으로 npm run build 실행하여 build 폴더 생성

3. homepage에 build 폴더 업로드.

 

 

✅ Breaking Change

: 툴 버전 업그레이드로 인한 코드 수정

=> React.js는 Breaking Change가 발생하지 않는다. 이전 기능에서 새로운 기능을 업데이트하고 추가만 했기 때문.