[React] React 입문 (ReactApp 생성/JSX/ESM/State/Props)

2023. 4. 7. 16:02개발공부 기강잡자/React | JavaScript | NodeJS

<Udemy 리액트 챌린지 2주차>

이정환 강사님의 강의 '한입 크기로 잘라먹는 리액트'를 수강하고 있습니다.

Udemy에서 진행하고 있는 리액트 챌린지에 참여하며 2주차 미션으로 "섹션 5 리액트 입문"을 수강한 내용을 정리합니다. 👍


목차

React가 필요한 이유

Create React App

React 특징

    JSX

    ES Module 시스템

JSX

    JSX 문법

    JSX와 CSS 결합하기

    인라인 style 적용

State

    Counter 예제

Props

    Prop 전달하기

    여러 개의 Props 전달

    Props로 컴포넌트 전달


React가 필요한 이유

  1. 웹사이트의 공통적인 부분의 (헤더, 메뉴, 푸터 등) 중복되는 코드를 줄여줌
    → 유지보수 시, 중복되는 부분을 모두 수정해줘야하는 문제(Shotgun Surgery)가 발생하는 것을 방지, 유지보수 효율성 증가
    • 공통적인 요소를 컴포넌트로 정의 → 필요한 페이지에서 컴포넌트를 사용할 수 있다.
      → 컴포넌트에 수정사항이 생길 경우, 컴포넌트를 사용하는 모든 페이지를 수정할 필요없이 컴포넌트의 소스만 수정하면 됨
    • React컴포넌트(Component) 기반의 UI 라이브러리
  2. 선언형 프로그래밍
    • 기존 명령형 프로그래밍 : 함수가 수행할 절차를 하나하나 나열해야한다.
      (요소를 가져오고 → 연산을 수행하고 → tag의 value를 수정하고..) - jQuery → 소스가 길어지는 문제 발생 가능
    • 선언형 프로그래밍 : 목적을 바로 말하면 됨 (React)
  3. Virtual DOM
    • DOM, Document Object Model → 브라우저가 HTML을 해석해서 보여줄 때, Tree 형태로 변환해 해석
    • 브라우저는 하나의 요소가 추가될 때마다 DOM을 추가함
      : 잦은 DOM의 변경 → 브라우저는 필요 이상의 연산으로 성능저하 발생
    • Virtual DOM
    • 실시간으로 HTML DOM의 변경을 하는게 아니라 가상의 돔에 미리 업데이트를 실행시켜 본 다음에 화면에 업데이트 시켜야할 부분을 파악한 다음에 브라우저 DOM에 적용한다.
      • 가상의 DOM 이기 때문에 화면에 렌더링을 하진 않는다.
      • State Change → Compute Diff → Re-render
      • 요소 5개가 추가될 때, 기존의 방식은 5개의 DOM 업데이트를 해야하지만, Virtual DOM 방식을 사용하면 5개를 한번에 업데이트 시킬 수 있음 → 과다 연산 해결!

Create React App

React.js : Node 기반의 Javascript UI 라이브러리

✔️ React 도 자바스크립트 라이브러리이기 때문에 React App을 만들기 위해선 React 라이브러리를 npm 에서 다운받아야 한다.

 

React App을 개발하기 위해서 추가적으로 사용되는 대표 라이브러리

  • Webpack : 다수의 자바스크립트 파일을 하나의 파일로 합쳐주는 모듈 번들 라이브러리 (모듈 번들러)
  • Babel : JSX 등의 쉽고 직관적인 자바스크립트 문법을 사용할 수 있도록해주는 라이브러리 (자바스크립트 컴파일러)

등등... 많은 패키지를 초보자가 직접 설치하고 설정하는데 어려움이 있을 수 있음
→ 따라서 필요한 패키지를 미리 설치하고 세팅까지 완료된 패키지 Boiler Plate를 사용하면 우리가  React를 바로 개발할 수 있다.

 

React App 생성하기 >  terminal에서 명령 실행

npx를 사용해서 React의 Boiler Plate 패키지를 설치한다.

npx creat-react-app [react앱이름] : [react앱이름] 으로 리액트 앱 생성 (리액트에 필요한 환경을 설치)

 

💡  npx  : 설치되어있지 않은 패키지를 한번만 쓰고 싶을때 사용하는 도구

(npx -v : npx 버전 확인

npm install -g npx : npx 없는 경우 npx 설치)

 

설치 완료! 🥳

React App 의 파일들

[package.json]

- 현재 프로젝트의 설정정보와 설치한 모듈의 정보를 담고 있는 파일 (JSON 포맷)

script > start : 리액트 실행시키는 스크립트 명령어

(npm start 를 명령하면 localhost:3000으로 React App을 실행할 수 있다!)

첫 실행화면 야호!🥳

내 컴퓨터는 (localhost) 웹서버가 된겨

(터미널에서 Ctrl + C / Command + C를 입력하면 종료가능)

💡 React는 Node.js 기반의 웹서버 위에서 동작한다!

 

[ /src/App.js ]

: 함수 App()가 HTML(페이지)을 리턴하고 있는 것을 확인할 수 있다.
className=’App’div요소를 리턴하고 있음

 

[ /src/index.js ]

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

ReactDOMRoot를 생성한다.

id=root인 요소에 렌더링 

root 요소는 /public/index.html 파일에 선언되어 있으며 App 컴포넌트를 생성하고 있다.

⇒ 따라서, 초기페이지인 index페이지에서 App 컴포넌트를 보여준다.

💡🤯 create-react-app 패키지를 설치하면 node_modules 폴더의 용량 엄청남 → 빼고 업로드 해도 된다!

Why? 지워져도 어떤 모듈을 써야하는지 package_lock.json, pacakage.json에 선언되어있다.
따라서 node_modules 를 빼고 업로드해도 되며, node_modules 가 없는 프로젝트를 다운받은 경우 npm i 명령어를 실행하면 자동으로 다운로드 받으면 된다.

 

[/public 디렉터리]

- index.html : 웹사이트 최초 인입 페이지

- favicon.ico : 웹사이트의 아이콘

- logo192.png, logo512.png, manifest.json → 모바일 환경 대응용 파일

- robots.txt : 검색엔진 최적화 파일 (SEO)

React 특징

1. JSX 문법

  • JS와 HTML을 섞어놓은 코딩 문법 → JSX (JavaScript Extension) : 값을 HTML에 쉽게 적용 가능하다!
function App() {
  let name = "유미닝";
  return (
    <div className="App">
      <header className="App-header">
        <h2>{name}</h2>
        <h3>HI React</h3>
      </header>
    </div>
  );
}

⇒ 위의 소스를 보면 JS 로 선언한 변수 name을 HTML 태그 내에서 사용하고 있다.

2. ES Modules 시스템 사용

  • ES 모듈 시스템의 모듈 내보내기 : export
export default App;

👉 App이라는 이름으로 모듈을 내보낸다.

 

  • ES 모듈 사용하기 : import
import App from './App';

👉 모듈을 사용할 때는 import로 가져와야 한다.

 

✔️ JSX 를 사용하면 유용한 컴포넌트들을 만들 수 있다.

 

MyHeader 컴포넌트 만들기

1. MyHeader.js 작성

const MyHeader = () => {
    return <header>헤더</header>;
};

export default MyHeader;

MyHeader라는 함수에 return할 컴포넌트를 작성한다.

export : ES모듈시스템을 사용하여 작성한 컴포넌트 MyHeader를 내보낸다. 

 

2. MyHeader 컴포넌트 사용하기 → App.js

import './App.css';
import MyHeader from './MyHeader';

function App() {
  let name = "유미닝";
  return (
    <div className="App">
      <MyHeader />  
      <header className="App-header">
        <h2>{name}</h2>
        <h3>HI React</h3>
      </header>
    </div>
  );
}

export default App;
  • ES모듈시스템을 사용하여 작성한 MyHeaderimport
  • <MyHeader /> 태그를 사용하여 컴포넌트를 사용한다.
💡 통상적으로 최상위 부모인 컴포넌트를 App 이라고 부른다.

 

JSX 문법

  • 닫힘 규칙
    : 열린 태그는 무조건 닫아야 한다.
<div></div>
<MyHeader />
<img />
<br />

<br> : 태그가 닫히지 않았으므로 오류 발생함

 

  • 최상위 태그 규칙
    - 최상위 태그 : 다른 모든 태그를 감싸는 태그
    - 최상위 태그는 하나여야 한다. (= 최상위 부모는 하나)
    - React.Fragment 태그로 최상위 태그를 대체할 수 있다.
import React from ‘react’
...

<React.Fragment>
    (하위 요소들..)
</React.Fragment>

 

빈 태그로 감싸도 된다. 👇

<>
    (하위 요소들..)
</>

 

JSX와 CSS 결합하기

JSX로 작성한 내용에 CSS 스타일 입히기

: html 태그의 class 속성 대신에 JSX 문법은 className를 사용한다.
ex) <div className=”App”></div>

 

style이 정의되어있는 css 파일 import

import './App.css'

👉 App.css 파일에 App 이라는 class에 적용할 style 내용이 정의되어있다.

⭐️ class 선택자 : .
⭐️ id 선택자 : #

인라인 스타일 사용하기

style 속성을 저장할 객체 생성를 생성하고, 객체의 프로퍼티를 참조하여 인라인 style을 적용할 수 있다!! (신기)

형식 :  <태그 style={객체명.프로퍼티명}></태그> 
ex)  <div style={style.bold_text}></div>

function App() {
  let name = "유미닝";

  const style = {
    App:{
      backgroundColor : "black" // Camel 표기법 사용
    },
    h2:{
      color:"red"
    },
    bold_text:{
      color:"green"
    },
  };  // style 정보를 담은 객체 생성

  return (
    <div style={style.App}> 
      <MyHeader />
      <header className="App-header">
        <h2>{name}</h2>
        <h3>HI React</h3>
        <b style={style.bold_text}>React.js</b>
      </header>
    </div>
  );
}

 

👉 전체 내용을 감싸는 div 태그의 인라인 style로 style객체의 App 프로퍼티의 값이 적용된다.
따라서 background의 색상이 black으로 설정된다.

 

JSX 를 사용하여 html 태그 내에서 함수 호출도 가능

    const func = ()=>{
        return 'func'
      };
      return (
        <div style={style.App}>  
          <MyHeader />
          <header className="App-header">
            <h2>{name}</h2>
            <h3>HI React</h3>
            <b style={style.bold_text}>React.js {func()}</b>
          </header>
        </div>
      );

 

html 태그안에서 함수 호출도 가능하다. 중괄호로 묶어 함수를 호출하면 된다.

위의 예제 같은 경우 func 함수가 리턴하는 'func' 문자열을 화면에 출력한다.

 

실행결과

( 숫자나 문자가 아닌 내용을(bool, 리스트 등) 중괄호로 {} 출력하려고 하면 렌더링 안됨)

 

조건부 렌더링

: 삼항연산자를 사용하여 조건에 따라 출력되는 값을 다르게 할 수 있다.

 

number 값에 따라 짝수/홀수를 출력하는 예제

const number = 5;
	return (
    	<div style={style.App}>
        	<MyHeader />
            <header className="App-header">
            	<h2>{name}</h2>
                <h3>HI React</h3>
                <b style={style.bold_text}>
                  {number} 는 : {number % 2 === 0 ? "짝수" : "홀수"}
                </b>
            </header>
        </div>
        );

👉 number 의 값에 따라 출력 값을 다르게 설정할 수 있다!

 

실행결과

(조건부 렌더링은 매우 많이 사용된다!)

 

State (상태) ⭐️⭐️ 중요

React는 상태를 가질 수 있다!

State : 계속해서 변화하는 특정 상태, 상태에 따라 각각 다른 동작을 한다.

 

사람의 경우
배고픔 [행동 : 배고프다고 함]→ (식사) → 배부름 [행동 : 소화시키려고 함] → (시간 흐름) → 적당함 [행동 : 일상생활] → (시간흐름) → 배고픔 .. 처럼 시간의 흐름에 따라 상태가 변화함

 

React 도 이러한 상태를 가지고, 상태에 맞는 동작을 할 수 있다.

ex) WebSite의 Dark Mode : 웹사이트가 갖는 Light 또는 Dark라는 상태에 따라 테마를 바꾼다.
→ 상태를 바꾸는건 컴포넌트가 직접 관리한다!

Counter 예제

button을 누를 때마다, count 상태를 변화시키는 예제

+ 버튼 : +1  |   - 버튼 : -1

 

1. Counter.js 작성

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

👉 Counter.js : +/- 버튼과 숫자를 출력하는 컴포넌트 생성 → export  Counter 모듈 내보냄

 

2. 내보낸 Counter를 App 에서 호출

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

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

export default App;

→ Counter를 import 한 다음, App()이 리턴하는  html 태그에 <Counter /> 컴포넌트 추가

 

3. Counter 컴포넌트에 기능 추가하기

1.  React와 useState import

- 상태는 React의 기능이다.

import React, {useState} from "react";

👉 React의 {useState} : 상태를 사용하겠다는 메소드 추가

 

2. 상태 생성

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

: Count의 값을 관리할 수 있는 State를 생성한다.


👉 useState(인자) 메소드

인자값 = 상태의 초기값으로 설정된다.


: 배열을 받아 비구조화할당을 통해서 0번째 인덱스로 count, 1번째 인덱스로 setCount를 받는다.
배열 인덱스 0 count : 상태의 으로 사용됨
배열 인덱스 1 setCount : 상태를 변화시키는 함수로 사용됨

 

3. 값을 증가/감소 시키는 함수 작성

const Counter = () => {
    const [count, setCount] = useState(0);
    
    const onIncrease = () =>{
        setCount(count + 1);    // 1증가
    };
    const onDecrease = () =>{
        setCount(count - 1);    // 1감소
    };

👉 setCount 함수를 사용하여 상태 변수 count의 값을 변화시킨다.

 

4. 버튼에 이벤트 연결

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

버튼을 클릭했을 때, 함수가 호출될 수 있도록 onClick 콜백함수에 함수를 지정한다.

기존 html과 다른 점
→ 설정 방식 : onclick=’onIncrease()’onClick={onIncrease} ( : 중괄호로 함수를 감싼다.)

(⭐️ onClick은 카멜케이스로 작성!)

 

Counter.js 전체 소스

import React, {useState, useSyncExternalStore} from "react";

const Counter = () => {
    // 버튼을 누르면 count 상태를 변화
    const [count, setCount] = useState(0);
    
    const onIncrease = () =>{
        setCount(count + 1);    // 1증가
    };
    const onDecrease = () =>{
        setCount(count - 1);    // 1감소
    };
    return (
        <div>
            <h2>{count}</h2>
            <button onClick={onIncrease}>+</button>
            <button onClick={onDecrease}>-</button>
        </div>
    );
};
export default Counter;

실행화면

→ 버튼이 눌릴 때마다, 값이 바뀌는 것을 확인할 수 있다.

 

💡 State 값이 바뀔 때마다 Counter라는 함수가 상태반환을 다시 한다는 점을 알 수 있다.
      => 화면을 새로 그린다. (reRender)

💡 상태는 여러 개일 수 있다. → 이름은 겹치면 안됨!

 

Props ⭐️⭐️ 중요

컴포넌트에 데이터를 전달하는 방법 : Props (프롭스)

Counter의 상태변수인 count초기값을 App 컴포넌트가 전달하는 값으로 사용하려면?
⇒ Props 기능 이용

 

1. App.js : 자식 컴포넌트에 Prop 기능으로 초기값 전달하기

<Counter InitialValue={5}/>

👉 Counter 컴포넌트를 생성할 때, InitialValue를 사용하여 초기값 Prop을 전달할 수 있다.

 

2. Counter에서는 전달받은 Prop을 매개변수로 받아와서 사용

const Counter = (props) => {
	console.log(props);  // 받은 props 출력

- 전달받은 props 값은 컴포넌트 생성 시 인자값으로 받을 수 있다.

 

Console

Console에 출력된 props 값을 확인해보면 Prop 값이 객체에 담겨 전달되고 있다.

- 부모에서 InitialValue로 전달한 값은 InitialValue 프로퍼티에 저장되어 있는 것을 확인할 수 있다.
따라서, 부모에서 자식 컴포넌트에 전달한 InitialValue를 사용하려면 객체의 프로퍼티에 접근하여 사용해야한다.)

 

✔️ 여러 개의 Prop을 (Props!) 전달할 수도 있다.

<Counter a={10} InitialValue={5}/>

위 처럼 부모 컴포넌트에서 여러 개의 Prop을 전달하면

Console

→ 이렇게 a라는 프로퍼티가 추가된 것을 볼 수 있다.
따라서 Prop 객체에 각각의 프로퍼티로 전달된다는 점을 알 수 있다.

 

✔️ 객체를 생성하여 Prop을 전달할 수도 있다!

function App() {
  const counterProps = {
    a:1,
    b:2,
    c:3,
    d:4,
    InitialValue:10
  };
  return (
    <div>  
      <MyHeader />
      <Counter {...counterProps}/>
    </div>
  );
}

👉 Prop으로 전달할 객체를 미리 선언하고 Spread 연산자() 를 사용하여 Props를 전달하면 가독성이 좋아진다!‼️

Console : 전달된 객체 Props

따라서 Props 중 InitialValue 값을 count 상태 변수의 값으로 사용하려면 props.InitialValue 로 접근해야 한다.

const Counter = (props) => {
    // 버튼을 누르면 count 상태를 변화
    const [count, setCount] = useState(props.InitialValue);

혹은, 비구조화 할당으로 원하는 프로퍼티만 받을 수도 있다.

const Counter = ({InitialValue}) => {
    // 버튼을 누르면 count 상태를 변화
    const [count, setCount] = useState(InitialValue);

👉 받은 Props 중 중괄호{}로 사용할 프로퍼티만 받을 수 있다.

 

🤔 만약 InitialValue 값을 전달 못받았다면

: undefinded를 전달받아 추후 에러가 발생할 수 있다.

 

이를 방지하기 위해서 👉 defaultProps를 선언한다

 

Counter.js에 선언

Counter.defaultProps = {
    InitialValue:0,
}

→ 부모 컴포넌트에서 InitialValue를 전달받지 못하면, Counter는 defaultProps의 값을 기본값으로 사용한다. (에러 방지 성공!)

 

동적 데이터 전달

State 도 전달이 가능하다!

 

상태변수인 count의 값이 홀수인지 짝수인지 실시간으로 알려주는 예제로 State를 전달해보자!

짝수 / 홀수 판단 컴포넌트 만들기 👉 OddEvenResult 컴포넌트에선 Counter의 State인 count 값을 받아와서 짝수, 홀수를 판단해야함

 

1. OddEvenResult 컴포넌트 생성

: 짝수/홀수 판단하여 출력하는 컴포넌트

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

(우선 빈 컴포넌트로 생성)

 

 

2. Counter의 자식 컴포넌트로 OddEvenResult 추가

<OddEvenResult count={count}/>

👉 Props 로 count값 전달

 

3. OddEvenResult에서 Counter 컴포넌트의 상태변수인 count 값을 매개변수로 받아서 짝수, 홀수 판단 결과 출력

const OddEvenResult = ({count}) =>{
    console.log(count);
    return <>{count % 2 === 0 ? "짝수" : "홀수"}</>;
};
export default OddEvenResult;

실행결과

 

→ 컴포넌트는 부모가 넘겨주는 Props가 변경될 때마다, 다시 렌더를 한다. (reRender)
→ 또한 부모의 상태변수가 변할 때마다 자식 컴포넌트도 계속 리렌더 된다.

 
정리
  • state가 변할 때마다
  • props의 값이 변할 때마다
  • 부모가 리렌더 되면 자식도 ReRender 된다.

 

컴포넌트Props 로 전달 가능

컴포넌트를 Props로 전달받아 감싸는 Container 컴포넌트를 구현해보자!

 

1. Props로 받은 컴포넌트를 감싸는 Container 컴포넌트

const Container = ({children}) =>{
    return <div style={{margin:20, padding:20, border:"1px solid gray"}}>
        {children}
    </div>
};

export default Container;

👉 Container 로 감싼 자식 컴포넌트를 div로 감싸 style을 적용한다.

 

2. App.js 에서 Container 컴포넌트로 모든 html 요소를 감싸기 

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

 

👉 Container 태그로 감싼 컴포넌트들은 Props로 Container에 전달된다. 

 

실행결과

👉 border style이 적용된 div로 다른 요소를 감싸는 Container 컴포넌트를 만들 수 있게 되었다!

 


수강한 지 2주가 지난 시점에서 다시 정리하려니까 조금 힘들다. 그리고 개념 같은게 정확하게 기억나질 않다보니까 설명을 작성하는데 애를 먹기도 했다. 🥲 그래서 강의를 다시 들으며 정리하기도 했는데, 그만큼 한번의 수강만으로는 내용을 완전히 습득하지 못했다는 거겠지...

이게 배워나가는 과정이라 그런거겠지..? 히히

그래도 이제 본격적으로 리액트 사용할 수 있게 되니 들뜬다. 긴가민가한 개념도 정리하고 지나갈 수 있어서 좋았다.

사실 그냥 대충 배우고 넘어갈수도 있었는데 이번 유데미 챌린지 통해서 미션을 성공해나가니 매우 뿌듯하고 좋은 기회 얻어서 너무 좋다~~~

끝까지 마무리 잘해야지.

 

출처 : Udemy '한입 크기로 잘라먹는 리액트' (이정환)