정환님의 인프런 [한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지]강좌를 참고하여 작성하였습니다. 

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8#

 

한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지 - 인프런 | 강의

개념부터 독특한 프로젝트까지 함께 다뤄보며 자바스크립트와 리액트를 이 강의로 한 번에 끝내요. 학습은 짧게, 응용은 길게 17시간 분량의 All-in-one 강의!, 리액트, 한 강의로 끝장낼 수 있어요.

www.inflearn.com


Why React?

🍅첫 번째 이유 - 컴포넌트 기반 제작

  • 만약, 모든 요소가 다 같지만 딱 한 섹션만 서로 다른 페이지 100개가 있을 경우, 페이지들을 대조했을때 중복되는 코드가 너무 많다. 
  • 중복된 부분 중 수정해야 할 부분이 생긴다면 우리는 100개의 페이지를 전부 수정해야 하는 문제 발생 -1
    • -> Shotgun Surgery🧨(산탄총 수술)이라고 한다.
  • 컴포넌트화 : 다른 페이지에서도 공통적으로 같이 사용해야 하는 요소들을 컴포넌트로 만어 사용하는 방식. 
    • 공통으로 사용되는 요소에 수정 사항이나 문제가 발생했을 때 수정이 훨씬 수월해지고 쉬워진다. 
    • = 유지보수가 쉬워진다.
  • 리액트는 컴포넌트 기반의 UI 라이브러리
    • html 요소들을 컴포넌트화 해서 재사용할 수 있는 기술이 있다. 

🍅두 번째 이유 - 선언형 프로그래밍

명령형 프로그래밍
(절차를 하나하나 다 나열 해야함)
선언형 프로그래밍
(그냥 목적을 바로 말함)
1. 결과를 표시할 요소를 가져온다. 
(id=result)

2. 현재 결과값을 10진수 기준, 숫자형으로 변환해서 가져와 current라는 상수에 저장한다. 

3. Current상수테 저장된 값을 결과를 표시할 요소의 값에 plus라면
+1 해서 넣고, minus라면 -1해서 넣는다. 
1. Plus를 누르면 result값에 +1 한다. 
Minus를 눌렀다면 반대로 한다. 
  • 명령형 프로그래밍 방식에는 jQuery가 있고, 선언형 프로그래밍 방식에는 React.js가 있다. 

🍅세 번째 이유 - Virtual DOM

  • DOM(document object model): 문서 객체 모델

  • HTML을 트리 형태로 변환시켜 놓은 객체

  • 변환된 DOM은 이런 흐름에 따라 눈에 보이게 된다. 
  • 이런식으로 브라우저가 많은 일을 수행하기 때문에 노드의 수가 많아질수록, 변경이 잦아질수록 성능 저하로 이어질 수 있다.

  • Virtual DOM이란 실제 DOM에서 처리하는 방식이 아닌 Virtual DOM과 메모리에서 미리 처리하고 저장한 후, 실제 DOM과 동기화하는 프로그래밍 개념이다. 
  • 5번 업데이트 할 것을 한번만 업데이트 한다 정도의 개념. 

Create React App

🍅프로젝트 생성

  • npx create-react-app [프로젝트 이름] 명령어를 이용해서 리액트 프로젝트를 생성한다. 
  • 프로젝트 구조는 다음과 같다. 

🍅프로젝트 실행

  • pakage.json을 확인해보면 긴 문자열 명령을 별명을 붙여서 부를 수 있는 기능인 scripts가 있다.

  • npm start 명령어를 입력하면 프로젝트가 실행된다. 
  • 프로그램을 종료하고 싶다면 터미널에 ctrl(^)+c를 입력한다. 

🍅src > App.js

//App.js

import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;
  • App.js의 초기 상태.
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h2>Hello World</h2>
      </header>
    </div>
  );
}

export default App;

  • App.js의 header안의 내용을 지우고 h2태그를 추가했더니 화면이 다음과 같이 바꼈다. 

  • 개발자 도구로 확인본 결과는 다음과 같다. 
  • 결론적으로 App.js 파일에 있는 App()이라는 함수가 리턴하는 html들이 id가 root인 div 아래로 자식 요소로 들어갔다고 유추가 가능하다. 

🍅src > index.js

//index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
  • index.js의 초기 상태이다. 
  • 확인해 볼 수 있는 것 
    • root.render이라고 해서 root를 화면에 그리고 있다. 
    • React.StricMode태그 안에 <App />이라고 되어 있는데 상단의 import로 App을 불러와서 사용하고 있는 것이다. 
    • ** 강의에선 React.render으로 되어있지만 강의 수강 기준 현재는 createRoot를 이용하는 방법을 사용한다. 
  • 즉 아까 봤던 App.js에 있던 App()함수가 return하는 값이 id가 root인 div아래로 들어가게 된다. 

🍅public > index.html

<!-- index.html-->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>
  • 그렇다면 id가 root인 div는 어디에 위치할까?
  • 바로 public안에 있는 index.html안에 있다. 
  • 💡정리:
    • 리액트 앱이 실행이 되면 src밑에 index.js가 실행이 되면서 index.html안에 있는 id가 root인 div아래로 app.js 안에 있는 App()함수의 리턴값이 들어가게 되는 것. 

📁node_modules

일부 모듈만 캡쳐.

  • src, public 외의 또 다른 패키지. 
  • node.js 패키지의 구성 요소 중 하나로 외부 모듈을 저장하고 있는 폴더. 
  • 상당히 많은 모듈들을 가지고 있기 때문에 하나하나 설치할 수 없기에 create-react-app을 이용하여 한번에 설치를 했다. 
  • 사이즈가 상당히 크기 때문에 배포나 전송시 많은 시간이 소요될 수 있다. 
  • node-modules는 삭제해도 문제가 없다. 왜냐하면 pakage.json에 필요한 모듈이 명시가 되어있으며,  npm i (npm install)를 이용하여 설치할 수 있기 때문이다. 

📁public

  • favicon.ico : 웹사이트의 아이콘
  • index.html : 위에서 확인
  • logo192.png, logo512.png, manifest.jspn : 해당 강의에서 사용하지 않을 파일.
  • robots.txt : 구글이나 네이버가 웹사이트를 수집할 때, 수집이 가능/불가능한 경로를 명시하는 파일.

📁src

  • App.css : 스타일파일
  • App.js : 위에서 확인
  • App.test.js : 테스트를 위한 파일. 해당 강의에선 사용x
  • index.css : 스타일 파일
  • index.js : 위에서 확인
  • logo.svg : 해당 강의에선 사용x
  • reportWebVitals.js : 해당 강의에선 사용x
  • setupTests.js : 해당 강의에선 사용x

🍅JSX

//App.js

import './App.css';

function App() {
  let name = "cloudmato";
  return (
    <div className="App">
      <header className="App-header">
        <h2>Hello {name} World</h2>
      </header>
    </div>
  );
}

export default App;

  • app.js파일로 돌아가서 App함수 밑에 지역변수인 name을 선언해주고 중괄호{ }를 사용해서 <h2>안에 넣어주면 화면에 반영이 된다.
  • JSX :  javascript형식과 html형식을 합쳐서 사용할 수 있는 Javascript의 확장 문법 
  • React는 이렇게 App()이라는 함수를 만들고 return으로 JSX 문법의 html을 리턴해주면서 컴포넌트를 만든다. 

🍅 export default App;

  • common.js 모듈 시스템에서는 module.exports를 이용해서 모듈을 내보냈었다. 
  • ES 모듈 시스템에서는 export default 를 이용해서 모듈을 내보내게 된다. 
  • 내보낸 모듈은 다른 파일에서 import [이름] from [경로] 이런 식으로 사용하게 된다. 
    • [이름]은 다른 이름으로 변경이 가능하다. 

JSX

🍅컴포넌트 만들기 - header역할을 할 컴포넌트

  • src 디렉토리 아래 myheader.js 파일 생성
//myHeader.js

const MyHeader = () => {};

export default MyHeader;
//App.js

import './App.css';

import MyHeader from './MyHeader';

function App() {
  let name = "cloudmato";
  return (
    <div className="App">
      <MyHeader />
      <header className="App-header">
        <h2>Hello {name} World</h2>
      </header>
    </div>
  );
}

export default App;

  •  화살표함수로 MyHeader를 만든 후에 export default로 내보낸 후 App.js에 불러와주었다. 
//Myfooter.js

const MyFooter = () => {
    return <footer>MyFooter</footer>;
};

export default MyFooter;
  • Myfooter.js 파일을 생성한 후 export default를 이용해 내보내 주었다. 
//index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import MyFooter from './MyFooter';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <MyFooter />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals

  • index.js 에 <MyFooter />를 넣어주면 MyFooter가 렌더링 된다. 
  • 즉, index.js에서는 최상위 컴포넌트를 정의해줄 수 있다. 

🍅JSX 문법 - 1. 닫힘규칙

  • 예를 들어 <div></div> 등과 같이 닫힘 태그를 꼭 써주어야 한다. 
  • <br>, <hr> 같은 경우 <br /><hr /> 등과 같이 self-closing해주어야 한다. 

🍅JSX 문법 - 2. 최상위 태그 규칙

  • JSX로 컴포넌트를 만들어서 리턴하려면 반드시 하나의 최상위 태그로 다른 모든 태그들을 감싸주어야 한다. 
  • 최상위 태그: 가장 바깥에 있는 태그
//App.js

import './App.css';
import React from 'react';

import MyHeader from './MyHeader';

function App() {
  let name = "cloudmato";
  return (
    <React.Fragment>
      <MyHeader />
      <header className="App-header">
        <h2>Hello {name} World</h2>
      </header>
      </React.Fragment>
  );
}

export default App;
  • 만약 최상위 태그로 묶고 싶지 않다면 react.fragment라는 기능을 사용해주면 된다. 
//App.js

import './App.css';

import MyHeader from './MyHeader';

function App() {
  let name = "cloudmato";
  return (
    <>
      <MyHeader />
      <header className="App-header">
        <h2>Hello {name} World</h2>
      </header>
      </>
  );
}

export default App;
  • 이런 식으로 빈 태그를 만들어 줘도 된다. 

🍅JSX문법과 CSS 결합하기 - 1. css파일 이용하기

//App.js

import './App.css';

import MyHeader from './MyHeader';

function App() {
  let name = "cloudmato";
  return (
    <div className="App">
      <MyHeader />
      <h2>Hello {name} World</h2>
      <b id='boldText'>react.js</b>
    </div>
  );
}

export default App;
  • import를 이용하여 css파일을 불러와 사용할 수 있다. 
  • jsx문법에서는 <div className="App">처럼 className 속성을 사용해주어야 한다. 
/* App.css */

.App {
  text-align: center;
  background-color: orangered;
}


h2 {
  color: white;
}

#boldText {
  color: green;
}

  • 클래스, 태그, 아이디 수준의 css가 변경된 것을 확인할 수 있다. 

🍅JSX문법과 CSS 결합하기 - 1. 인라인 스타일링 방식 이용하기

//App.js

// import './App.css';

import MyHeader from './MyHeader';

function App() {
  let name = "cloudmato";

  const style = {
    App : {
      backgroundColor : "red",
    },
    h2 : {
      color: "black",
    },
    bold_text: {
      color: "green",
    },
  };

  return (
    <div className="App" style={style.App}>
      <MyHeader />
      <h2 style={style.h2}>Hello {name} World</h2>
      <b id='boldText' style={style.bold_text}>react.js</b>
    </div>
  );
}

export default App;

  • 스타일 객체를 만들어서 태그 안에 style 속성으로 넣어줘 스타일을 적용시킬 수 있다. 

🍅JSX의 JavaScript값 사용하기

  const func = () => {
    return 'func';
  }

  return (
    <div className="App" style={style.App}>
      <MyHeader />
      <h2 style={style.h2}>{func()} {name} {1+2}</h2>
      <b id='boldText' style={style.bold_text}>react.js</b>
    </div>
  );

  • { } 중괄호를 이용해서 함수, 변수, 계산식 등 문자열을 표현될 수 있다. 
  const number = 5;

  return (
    <div className="App" style={style.App}>
      <MyHeader />
      <h2 style={style.h2}>{func()} {name} {1+2}</h2>
      <b id='boldText' style={style.bold_text}>{number}는 {number % 2 === 0 ? "짝수" : "홀수"}</b>
    </div>
  );
}

  • 다음과 같이 조건부 렌더링도 가능하다. 

State(상태)

  • 계속해서 변화하는 특정 상태
  • 상태에 따라 각각 다른 동작을 함

  • React에서 말하는 상태, 즉 state는 컴포넌트가 갖는 계속 값이 바뀔 동적인 데이터. 
  • 이 상태를 바꾸는 관리는 컴포넌트가 하게 된다. 

🍅Counter 예제

// Counter.js

const Counter = () => {
    return (
        <div>
            <h2>0</h2>
            <button>+</button>
            <button>-</button>
        </div>
    )
}

export default Counter;
//App.js

import Counter from './Counter';
import MyHeader from './MyHeader';

function App() {
  return (
  <div>
    <MyHeader />
    <Counter />
  </div>
  );
}

export default App;

  • Counter.js파일을 만들어주고 App.js에 불러와 준 화면이다. 
  • 현재는 버튼을 눌러도 아무런 변화가 일어나지 않는다. 
// Counter.js

import React, {useState} from "react";  //state는 react의 기능이기 때문에 React를 import해야한다. 


const Counter = () => {

    const [count, setCount] = useState(0);

    const onIncrease = () => {
        setCount(count + 1);
    };
    
    const onDecrease = () => {
        setCount(count - 1);
    };


    return (
        <div>
            <h2>{count}</h2>
            <button onClick={onIncrease}>+</button>
            <button onClick={onDecrease}>-</button>
        </div>
    )
}

export default Counter;
  
  • 버튼을 누를 때(=count state가 변경될 때)마다 숫자가 변경되는 것을 확인할 수 있다. 
  • rerender : 컴포넌트는 자신이 가진 state, 상태가 변화하면 화면을 다시 그린다. 
    • 즉, 함수가 다시 호출 된다. 
  • 여러개의 state를 하나의 컴포넌트가 가져도 된다. 

Props

  • 컴포넌트에 데이터를 전달하는 방법

🍅실습

// Counter.js

import React, {useState} from "react";  //state는 react의 기능이기 때문에 React를 import해야한다. 


const Counter = () => {

    const [count, setCount] = useState(0);

    const onIncrease = () => {
        setCount(count + 1);
    };
    
    const onDecrease = () => {
        setCount(count - 1);
    };


    return (
        <div>
            <h2>{count}</h2>
            <button onClick={onIncrease}>+</button>
            <button onClick={onDecrease}>-</button>
        </div>
    )
}

export default Counter;
  • 이전 영상에서 만든 count.js 파일이다. 
//App.js

import Counter from './Counter';
import MyHeader from './MyHeader';

function App() {
  return (
  <div>
    <MyHeader />
    <Counter initialValue = {5}/>
  </div>
  );
}

export default App;
  • 여기서 Counter 컴포넌트는 App컴포넌트의 자식 요소로 배치되어 있다. 
  • 이때 App컴포넌트가 전달한 값을 Counter의 초기값으로 사용하려면 어떻게 해야 할까?
  • initailValue 속성을 이용한다. 다만, 이렇게 값을 준다고 바로 적용되진 않는다. 
// Counter.js

import React, {useState} from "react";  //state는 react의 기능이기 때문에 React를 import해야한다. 


const Counter = (props) => {
    console.log(props);

    const [count, setCount] = useState(0);

    const onIncrease = () => {
        setCount(count + 1);
    };
    
    const onDecrease = () => {
        setCount(count - 1);
    };


    return (
        <div>
            <h2>{count}</h2>
            <button onClick={onIncrease}>+</button>
            <button onClick={onDecrease}>-</button>
        </div>
    )
}

export default Counter;

  • props로 값을 받아오고 콘솔을 찍어보면 객체 안에 initialValue가 담겨서 온다. 
    <Counter a = {1} b = {2} initialValue = {5}/>

  • 이런 식으로 여러개의 prop을 전달해도 전부 객체에 담겨서 전달된다. 
  • 하지만 이렇게 전달하게 되면 굉장히 길어지기 때문에 불편하다. 
//App.js

import Counter from './Counter';
import MyHeader from './MyHeader';

function App() {

  const counterProps = {
    a : 1,
    b : 2,
    initialValue : 5
  };

  return (
  <div>
    <MyHeader />
    <Counter {...counterProps}/>
  </div>
  );
}

export default App;

  • 이렇게 객체에 담아서 스프레드 연산자(...)에 담아 전달을 하게 되도 이전 방법과 같이 객체에 담겨서 전달된다. 
// Counter.js

import React, {useState} from "react";  //state는 react의 기능이기 때문에 React를 import해야한다. 


const Counter = ({initialValue}) => {

    const [count, setCount] = useState(initialValue);

    const onIncrease = () => {
        setCount(count + 1);
    };
    
    const onDecrease = () => {
        setCount(count - 1);
    };


    return (
        <div>
            <h2>{count}</h2>
            <button onClick={onIncrease}>+</button>
            <button onClick={onDecrease}>-</button>
        </div>
    )
}

export default Counter;
  • 이런식으로 비구조적 할당을 해준다. 
// Counter.js

import React, {useState} from "react";  //state는 react의 기능이기 때문에 React를 import해야한다. 


const Counter = ({initialValue}) => {

    const [count, setCount] = useState(initialValue);

    const onIncrease = () => {
        setCount(count + 1);
    };
    
    const onDecrease = () => {
        setCount(count - 1);
    };


    return (
        <div>
            <h2>{count}</h2>
            <button onClick={onIncrease}>+</button>
            <button onClick={onDecrease}>-</button>
        </div>
    )
}

Counter.defaultProps = {
    initialValue: 0,
}

export default Counter;
  • defaultProps를 이용하여 기본값을 지정해 주었을 때, 필요한 정보가 유실되었어도 기본값을 할당해준다. 

🍅실습 - 홀/짝 판별

const OddEvenResult  = () => {
    return <></>
};

export default OddEvenResult;
  • 홀수를 판별해줄 컴포넌트를 다른 파일에 만들어준다. 
// Counter.js

import React, {useState} from "react";  //state는 react의 기능이기 때문에 React를 import해야한다. 
import OddEvenResult from "./OddEvenResult";


const Counter = ({initialValue}) => {

    const [count, setCount] = useState(initialValue);

    const onIncrease = () => {
        setCount(count + 1);
    };
    
    const onDecrease = () => {
        setCount(count - 1);
    };


    return (
        <div>
            <h2>{count}</h2>
            <button onClick={onIncrease}>+</button>
            <button onClick={onDecrease}>-</button>
            <OddEvenResult count = {count}/>
        </div>
    );
};

Counter.defaultProps = {
    initialValue: 1,
}

export default Counter;
  • 이때 판별할 수를 Counter에서 받아와야 하기 때문에 props전달을 위해 OddEvenResult를 Counter의 자식요소로 배치한 후, count값을 전달해준다. 
const OddEvenResult = ({count}) => {
    // console.log(count);
    console.log("RENDER!");

    return <>{count % 2 ===0 ? "짝수" : "홀수" }</>
};

export default OddEvenResult;
  • 받아온 count값으로 홀짝 여부를 반환해주는 컴포넌트이다. 

  • 이런 결과값이 나온다. 미관상의 이유로 더 수정해주도록 하자. 
const Container = ({children}) => {
    return (
    <div style={{ margin: 20, padding: 20, border: "1px solid gray"}}>
        {children}
    </div>
    );
};

export default Container;
  • Container 컴포넌트를 만들어주는데, 이때 children이라는 자식요소들을 props로 받아준다. 
//App.js

import Counter from './Counter';
import MyHeader from './MyHeader';
import Container from './Container';

function App() {

  const counterProps = {
    a : 1,
    b : 2,
    // initialValue : 5
  };

  return (
    <Container>
      <div>
        <MyHeader />
        <Counter {...counterProps}/>
      </div>
     </Container>
  );
}

export default App;
  • App.js에서 요소들을 Container으로 묶어주면 다음과 같은 결과물이 나온다. 

+ Recent posts