241114 리액트 (개요 / 컴포넌트 생성 / 배포 / 데이터 전달 (Props and State) / CSS 및 이미지 적용)

241114 에이콘 아카데미 수업에 기반하여 작성되었음을 알립니다.

리액트

리액트는 자바스크립트 라이브러리로 구성되어있다.
목적은 프론트엔드에서 MVC 패턴을 구사하기 위한 목적이 있다.
대표적인 예들은 리액트, 뷰 등이 있다.
현재는 리액트가 세상을 지배하고 있다.
리액트를 통해 클라이언트 사이드의 화면구성을 멋들어지게 할 수 있다.
우리의 목표는 RESTful 활용에 있다. 화면 구성은 각자 해보도록 하자
HTML 사용 시 RESTful을 사용하여 SPA를 구성할 수 있긴 하지만 번거로운 부분이 있다.
SPA 구성할 시 리액트를 사용한다면 더 쉽게 구현 가능하다.
리액트와 관련된 새로운 기술, 문법 등을 이해해보자.
리액트는 거대한 커뮤니티를 가지고 있다.
지속적인 버전 관리가 이루어지고 있으며, 다양한 레퍼런스 및 확장 라이브러리가 많다.
얻을 수 있는 지식이 지천에 깔려있다.
브라우저의 frame에 들어올 수 있는 것은 HTML밖에 없다.
그래서 여전히 DOM의 개념을 이해하고 운영할 수 있어야 한다. 리액트는 virtual DOM을 사용하고 있다. (중요)
virtual DOM이 리액트에서의 변화를 감지, 오리지널 돔과 비교하여 스스로 DOM을 갱신시킨다.
virtual DOM은 서버단도 자동 갱신해준다.
웹 뿐만 아니라 앱 개발에도 사용한다. 메타(페이스북)이 지원한다. 중대형 프로젝트 작업에서 선호!

자바스크립트를 확장한 문법인 JSX 기반 컴포넌트 구문을 꼭 이해해주어야 한다.
HTML과 아주 유사한 모양으로 자바스크립트를 꾸며놓았다. HTML의 웬만한 태그들을 모두 만들어놓았다.
이러한 기능은 브라우저가 인식을 하지 못해 브라우저가 인식할 수 있도록 웹팩, 바벨이라는 툴이 있다.
자바스크립트이기 때문에 별도로 저장할 수 있다. .js / .jsx로 저장한다.
자바스크립트는 ECMA6를 기준으로 이전과 이후로 나뉜다.

React는 JSX 코드로 Component를 작성하고, 컴포넌트의 상태(State)를 변화시키지 않고 관리한다. 변화가 일어나면 실제 브라우저의 DOM에 새로운 것을 적용하는 것이 아니라, JavaScript로 이루어진 Virtual DOM에 렌더링을 하고 기존의 DOM과 비교하여 변화가 일어난 곳만 업데이트 한다.
변화가 일어난 부분만 바꿔준다는 점이 큰 장점이다!

HTML에서 사용자를 가장 신경쓰이게 하는 것은 화면 깜박임이다. 그래서 SPA 구현이 중요하다.
리액트는 부분만 고쳐주기 때문에 SPA 구현에 큰 장점이 있다.

Component는 function 개념, 자바로 말하자면 Bean이다. 용어만 다를 뿐 개념은 같다.
JSX란? https://ko.legacy.reactjs.org/docs/introducing-jsx.html

index.html에 각종 컴포넌트를 사용하여 멋드러지게 꾸민 후 스프링에 쏘옥 넣어보자! 실질적인 서버에 줘야한다!

개발자들은 리눅스를 사용하는 것이다. 그래서 맥을 써야한다. 서버들은 리눅스로 만들기 때문이다.
리눅스를 꼭 알아야한다. 맥환경에 익숙해지고 맥에서 터미널을 잘 쓸 줄 알아야한다. LAMP??

App.js가 모든 실질적인 내용(컴포넌트)을 가지고 있다. export하여 index.js를 타고 index.html으로 넘어가게된다. import 아파치톰캣에서 index.html을 호출하면 된다.
index.html의 내용을 수정해보자! index.html의 내용이 궁극적으로 우리에게 보이는 것이다.

컴포넌트를 만드는 방법 class, function을 가지고 만들 수 있다. hook이 나온 후 class보다, function을 더 많이 씀
다형성이 필요한 경우, 하이테크한 컴포넌트를 만들때는 class를 써야한다.

필수로 이해! Props and State : Props란 부모 컴포넌트에서 자식 컴포넌트로 전달해 주는 데이터를 말한다.
State는 컴포넌트 내부에서 선언하며 내부에서 값을 변경할 수 있다. state는 동적인 데이터를 다룰 때 사용하며, 사용자와의 상호작용을 통해 데이터를 동적으로 변경할 때 사용한다.

App.css와 index.css가 하는 일은? 차이
App.css와 index.css는 일반적으로 React 프로젝트에서 스타일을 관리하는 데 사용되는 파일들로, 두 파일의 차이는 주로 스타일의 적용 범위와 목적에 있다.
1. App.css
     - 범위: App.js 컴포넌트 및 그 하위 컴포넌트들에 적용되는 스타일을 정의한다.
     - 용도: 주로 특정 컴포넌트의 스타일을 정의하거나 개별 컴포넌트별 스타일링을 위해 사용된다.
                이 파일은 App.js 파일에서만 import 되기 때문에, 앱의 특정 영역이나 특정 컴포넌트의 스타일을 관리하기 좋다.
2. index.css
     - 범위: 애플리케이션의 전체 범위에 적용되는 전역 스타일을 정의한다.
     - 용도: 기본적인 레이아웃, 전역적인 글꼴 스타일, body 또는 html 태그에 적용할 전역 스타일 등 앱 전반에 걸쳐 공통으로 적용해야 하는 스타일을 정의하는 데 사용된다. 보통 index.js 파일에서 import 되며, 모든 컴포넌트에 영향을 줄 수 있다.
 
그러므로 App.css는 특정 컴포넌트용 스타일이고, 모듈화된 스타일로 사용할 수 있다. index.css는 애플리케이션 전역에 적용되는 스타일로 사용된다. 필요에 따라 스타일을 분리하여 유지, 관리하기 쉽도록 구성할 수 있다.

* JSX에서 준수해야 할 규칙
- 반드시 부모 요소 하나가 감싸는 형태여야 한다. (root element가 하나 반드시 있어야한다.)
- 태그는 무조건 닫혀 있어야 한다. :br, input 같이 닫지 않는 태그는 <br></br>,<input></input> 이렇게 닫아도 되지만, self closing 방식으로 <br/>, <input/>이런 식으로 작성.
- 2개 이상 태그는 1개로 감싸져 있어야 한다. <div>태그로 감싸도 되고, 비어 있는 이름 태그인 fragment 태그( < > )를 이용해서 감싸주면 JSX 안에 감싸는 태그없이 잘 감싸서 실행이 된다.
기타 여러 규칙은 카페를 통해 자세하게 더 공부해보자!
https://cafe.daum.net/flowlife/QbpR/69


오늘 Props and State 꼭 이해해보자!!!

App.js

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

function App() { // function말고 class로 만들 수도 있다.
  return ( // jsx 안에 코드가 반드시 있어야 한다. <><> 빈 태그라도 있어야 함!
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          가자! <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;

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App'; // 같은 폴더 안에 있는 App.js를 import 자바스크립트의 경우 확장자 없어도 괜찮음
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();

App.js (수정)

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

function App() {
  return (
    <div className="App">
      연습용 // 이 부분을 꾸며주면 된다.
    </div>
  );
}

export default App;

결과

🚨 JSX의 내용이 비었을 때 위와 같은 에러 발생


리액트 실습 1 (컴포넌트 생성 : 클래스, 함수)

App.js

import { Component } from "react";
import Navdata from "./mydir/Navdata";

// class 컴포넌트(유닛화) : render를 써주어야 함
class Subject extends Component {
  render() {
    return(
      <header>
        <h1>머리글 : 웹문서</h1>
      </header>
    );
  }
}

// function 컴포넌트 : render 필요없음
function Welcome(props) { // js, props 부모가 자식에게 일방적으로 던져주는 값, 가독성을 위해 props로 쓴다. 이름은 어떤것을 써도 상관없다.
  let kbs = "공영방송"; // js
  return ( // 여기서부터 jsx
    <article> {/* jsx */}
       {props.who} 글 기사 내용<br/> {/* readonly 부모가 넘겨준 값만 참조하는 것 edit 할 수 없다. */}
       {kbs}
    </article>
  );
}

function App() { // 부모 컴포넌트
  return (
    <div className="App"> {/* JSX 주석 */}
      연습용
      <Subject></Subject> {/* 컴포넌트 호출 */}
      <Subject></Subject>
      <hr/>
      <Welcome who="홍길동"></Welcome>
      <br/>
      <Navdata msg="부모가 정보 전달" msg2="전달2"></Navdata>
    </div>
  );
}

export default App; // 어디선가 불려서 씀!

내부적으로 웹팩과 바벨이 돌고 있는 것을 확인할 수 있다.

js 파일을 추가로 만들고 싶다면 src 하위 폴더를 생성 후 만듦

💡 리액트 사용의 의의
페이지 이동 없이 이 안에서 모든 작업을 수행하는 SPA 운영을 해야한다.

그래서 <a>나 submit을 그대로 사용하여서 구현하는 행동은
일반적으로 리액트를 사용하는 철학에서 벗어나는 행동이다.

리액트 실습 2 (배포)

React에서 배포 - 1

(목표 : React가 지원하는 서버 사용, 이 방법은 잘 안씀)

  > npm run build          : 빌드 명령이 실행되면 build 폴더가 생성.
  > npm install -g serve   : npm을 이용하여 디렉토리에 상관없이 간단한 서버 설치.
  > npx serve                   : 웹 서버 실행
  > npx serve -s build      : 일회용 웹 서버를 실행.

정리 : 
   - 개발 중

      > npm start 또는 > npm run start 
   - 개발이 완료 후 배포 

     > npm run build

     > npx serve -s build 

결과

리액트 서버 사용

React에서 배포 - 2

(목표 : ApacheTomcat 서버 사용)

package.json

프로젝트 생성 후 배포할 서버의 URL을 알아야함! (http://127.0.0.1:8080/)

package.json에 "homepage": "http://localhost:8080/" 입력

... 생략
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "homepage":"http://127.0.0.1:8080/projectName" // Dynamic Web Project
  "homepage":"http://127.0.0.1:8080/" // SpringBoot
}
💡 AJAX 사용 시
fetch : "homepage": "http://localhost:8080/" URL 마지막에 / 써줌
axios :"homepage": "http://localhost:8080" URL 마지막에 / 빼줌

npm run build

해당 프로젝트 폴더 안에 build 폴더 생성 됨

 

Dynamic Web Project

- webapp에 build 폴더 내 파일 넣고, 서버 실행

SpringBoot

- static에 build 폴더 내 파일 넣고 , 서버 실행

결과

모델1 방식(Dynamic Web Project) 배포

SpringBoot 배포


리액트 실습 3 (props)

App.js

import MyName from "./mydir/MyName";
import MyNickName from "./mydir/MyName2";

function App() {
  return (
    <div className="App">
      <h2>props 연습</h2>
      <MyName></MyName>
      <MyName name="다다다"></MyName>
      <MyName name="다다다" addr="강남구 역삼동 123"></MyName>
      <hr/>
      <MyNickName></MyNickName>
      <MyNickName name="다다다" addr="강남구 역삼동 123"></MyNickName>
    </div>
  );
}

/*
import { Component } from "react";
class App extends Component {
  render() {
    return(
      <div className="App">
      <h2>props 연습</h2>
    </div>
    );
  }
}
*/
export default App;

MyName.js

import { Component } from "react";

class MyName extends Component {
    /*
    static defaultProps = {
        name:"하하하"
    }
    */
    render() {
        return(
            <div>
                안녕 나는 <b>{this.props.name}</b>&nbsp;&nbsp;
                사는 곳은 {this.props.addr}
            </div>
        );
    }
}

MyName.defaultProps = {
    name:"가가가"
}

export default MyName;

MyName2.js

function MyName2(props) {
    console.log(props, ' ', props.name, ' ', props.addr);
    return (
        <>
            안녕, 별명은 {props.name} 주소는 {props.addr}
        </>
    );
}

export default MyName2;

결과

* 자식 컴포넌트 내부를 수정한 경우

...
const MyName2 = ({name, addr}) => { // 값을 직접 받을 수도 있음
    let aa = name + "님";
    console.log(aa);
    return (
        <>
            반가워 별명은 {name + "님"} {addr} // 부모가 준 값을 edit 했을 때
        </> 
    );
}

export default MyName2;

부모가 준 값을 그대로 참조하지 않으면 위처럼 뜸! 무조건 readonly!


리액트 실습 4 (state : React Hooks)

(개념적 용어임)

Hook은 React 버전 16.8부터 React 요소로 새로 추가되었다. Hook을 이용하여 기존 Class 바탕의 코드를 작성할 필요 없이 상태 값(state)과 생명주기 기능(lifecycle features) 및 여러 React의 기능을 사용할 수 있다.

useState : 상태를 선언하고 관리하는 데 사용.

import React, { useState } from 'react'; // 지역변수 사용함을 선언

const Counter = () => {
  const [count, setCount] = useState(0); // 값을 변경해주는 setCount 함수를 이용해서 바꿔줘야함

  return (
    <div>
      <p>{count}</p>
      <button xxxxonClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

App.js (클래스 타입)

import { Component } from "react";

class App extends Component {
  state = {
    count:0 // 지역 변수 : state 상태 변수 - 컴포넌트 내부에서 사용(관리)하는 동적인 데이터
  };

  countUpdate(n) {
    this.setState({count : n});
  }

  render() {
    const {count} = this.state;
    return (
      <div>
        <h2>지역변수 state</h2>
        number : {count}&nbsp;&nbsp;
        <button onClick={() => {
          this.countUpdate(count + 1);
        }}>증가 1</button>
      </div>
    );
  }
}

export default App;

HookTest.jsx

// function type의 컴포넌트
import { useState } from "react";

const HookTest = () => {
    const [count, setCount] = useState(0); // [state변수명, state수정함수], useState(0); 0으로 초기치를 줌, 안줄수도 있음
    console.log(useState());

    return (
        <div>
            number : {count}&nbsp;&nbsp;
            <button onClick={() => setCount(count + 1)}>증가 2</button>
        </div>
    );
}

export default HookTest;

HookTest2.jsx

import { useState } from "react";

export default function HookTest2() {
    const [item, setItem] = useState(0);
    const incrementItem = () => setItem(item + 1);
    const decrementItem = () => setItem(item - 1);

    return (
        <div>
            number : {item}&nbsp;
            <button onClick={incrementItem}>증가</button>&nbsp;
            <button onClick={decrementItem}>감소</button>
        </div>
    );
}

// export default HookTest2;

App.js (함수 타입)

// 함수 타입
import { useState, useEffect } from "react";
import HookTest from "./mydir/HookTest";
import HookTest2 from "./mydir/HookTest2";

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

  const countUpdate = (n) => {
    setCount(n);
  };

  useEffect(() => {
    let a = 1;
    console.log(a);
  },[]);

  return (
    <div>
       <h2>지역변수 state(함수형)</h2>
        number : {count}&nbsp;&nbsp;
        <button onClick={() => {
          countUpdate(count + 1);
        }}>증가 1</button>
        <hr/>
        <HookTest/>
        <hr/>
        <HookTest2/>
    </div>
  );
}

export default App;

React Developer Tools

반응이 굉장히 느리다!

결과

변화하는 값만 버츄얼돔이 리렌더링 해주는 거임!

[값, 함수]

클래스 타입

함수 타입


리액트 실습 5 (CSS 적용, 이미지 불러오기)

App.css

.App {
  padding-left: 20px;
  padding-top: 30px;
}

.black_bar {
  background-color: black;
  width: 100%;
  color: white;
  padding: 5px;
  display: flex;
}

.image_bg {
  width: 350px;
  height: 350px;
  background-size: cover;
  background-repeat: no-repeat;
  background-image: url("./02.png");
}

HookTest2.jsx

import { useState } from "react";
import "../App.css";
import pic1 from "../01.png";

export default function HookTest2() {
    const [item, setItem] = useState(0);
    const incrementItem = () => setItem(item + 1);
    const decrementItem = () => setItem(item - 1);

    const mystyle = {color:'blue', textAlign:'center', fontSize:'30pt'};
    // 키밸류 형식의 스타일 적용, JSON 형식으로 만듦, 카멜표기법으로 사용해야함!

    return (
        <div className="App">
            <div>
                number : {item}&nbsp;
                <button onClick={incrementItem}>증가</button>&nbsp;
                <button onClick={decrementItem}>감소</button>
            </div>
            {/* CSS style 적용 연습 : style 속성 값은 {} 안에 적음 */}
            <h2 className="black_bar">리액트에서 스타일 적용</h2>
            <h2 style={mystyle}>리액트에서 스타일 적용2</h2>
            <h2 style={{color:"red", textAlign:"right"}}>리액트에서 스타일 적용3</h2>
            
            {/* 이미지 적용 연습 */}
            <div>
                <img src={pic1} alt="이미지 읽기" />
            </div>
            <div className="image_bg"></div>
            <div>
            <img style={{width:'300px'}} // 가급적 public은 건드리지 말자!, 웹팩의 관리 대상에서 제외 되어있다.
            src={`${process.env.PUBLIC_URL}/imgs/hero30.png`} alt="public 이미지 읽기" />
            </div>
        </div>
    );
}

// export default HookTest2;

결과