코드잇 Codeit/Front-End

[코드잇] React 웹 개발 시작하기

꼽파 2024. 1. 18. 23:02


  • 1. React 시작하기

  • 2. React 개발 기초

  • 3. React 배포하기

  • 1. 리액트 시작하기

    • 리액트 개발을 위한 환경 : Node.js, VScode, Chrome
    • Node.js 설치 : LTS 버전이 더 안전함

    • React 개발자도구

    https://react.dev/

     

    React

    React is the library for web and native user interfaces. Build user interfaces out of individual pieces called components written in JavaScript. React is designed to let you seamlessly combine components written by independent people, teams, and organizati

    react.dev


    2. 리액트 개발 기초

    프로젝트 세팅

    프로젝트 생성하기

    현재 디렉토리에 리액트 프로젝트가 생성됨.

    npm init react-app

     

    개발모드 실행하기

    npm run start

     

    public 폴더 설정

    • public 폴더 내 index.html을 제외한 나머지 파일은 삭제함.

    • index.html 파일 내에서는 필요한 것만 남기고 지워주기

    - head : 인코딩을 결정하는 meta 태그와 title 태그만 남겨두기

    - body : div(root id를 가짐)태그 빼고 삭제

    - html lang="ko", title: 주사위게임

     

    src 파일 설정

    import ReactDOM from 'react-dom/client';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<h1>안녕 리액트!</h1>);

     index.js 파일만 남겨두고 불필요한 부분 삭제


    인덱스 파일에서 하는 일

    index.html

    • 웹브라우저에서 가장 먼저 실행되는 파일

    index.js 

    • index.html 파일 실행 후 실행됨.
    • 리액트 코드들 중에서 가장 먼저 실행되는 파일

    react의 render 메소드는 보통 index.js 파일에서 한번만 실행함.


    JSX

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<p className="hello">안녕 리액트!</p>);

    jsx는 자바스크립트의 확장된 문법이라 HTML 문법을 완전히 그대로 사용할 수는 없음.
    ex. class와 for

    <p className="hello"></p>

    for은 label 태그에서 input태그와 함께 사용됨.
    'htmlfor' 라고 작성해야됨.

    <input id="name" type="text" onBlur="" onFocus="" onMouseDown="">

    소문자로 작성한 이벤트 핸들러는 두 번째 단어부터 첫 글자를 대문자로 작성해줘야 함.
    →  CamelCase로 작성됨.

     

    HTML에서 비표준 속성을 다룰 때 활용하는 data-* 속성

    카멜 케이스(Camel Case)가 아니라 기존의 HTML 문법 그대로 작성

    <div>
        상태 변경: 
        <button className="btn" data-status="대기중">대기중</button>
        <button className="btn" data-status="진행중">진행중</button>
        <button className="btn" data-status="완료">완료</button>
      </div>,

    프래그먼트

    import ReactDOM from 'react-dom/client';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<div>
      <p>안녕</p>
      <p>리액트!</p>
      </div>
    );

     

    div태그를 굳이 만들고 싶지 않은 경우

    import { Fragment } from 'react';  // 자동으로 생김
    import ReactDOM from 'react-dom/client';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <Fragment>
      <p>안녕</p>
      <p>리액트!</p>
      </Fragment>
    );

    import ReactDOM from 'react-dom/client';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <>
      <p>안녕</p>
      <p>리액트!</p>
      </>
    );

    · 리액트에서 JSX 문법을 활용할 때는 반드시 하나의 팩으로 감싸주어야 함.
    · 감싸는 태그가 불필요한 경우 프래그먼트를 활용해서 해결할 수 있음.


    JSX에서 자바스크립트 사용하기

    JSX에서 자바스크립트 문법을 사용하려면 중괄호로 감싸줘야함.

    import ReactDOM from 'react-dom/client';
    
    const product = 'Macbook';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <h1>나만의 {product.toUpperCase()} 주문하기</h1>
    );

    되도록 변수를 바깥에 설정해서 JSX 문법 속에서는 읽기 편하게 코드를 작성하자.

     

    JSX에서 자바스크립트 문법을 사용하려면 중괄호로 감싸줘야함.

    import ReactDOM from 'react-dom/client';
    
    const product = 'Macbook';
    const imageUrl = 'https://commons.wikimedia.org/wiki/File:MacBook_LMSD_Issue_2009.jpeg#/media/파일:MacBook_LMSD_Issue_2009.jpeg'
    
    function handleClick() {
      alert('곧 도착합니다!')
    }
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <>
        <h1>나만의 {product} 주문하기</h1>
        <img src= {imageUrl} alt="제품사진"></img>
        <button onClick= {handleClick}>확인</button>
      </>
    );


    중괄호 안에서는 자바스크립트의 표현식만 사용할 수 있기 때문에 
    if문, for문, 함수 선언 같은 자바스크립트 문장은 사용할 수 없음.


    컴포넌트

    import ReactDOM from 'react-dom/client';
    
    const element = <h1>안녕 리액트!</h1>;
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(element);

     

    element 라는 변수를 콘솔에 출력해보면 자바스크립트의 객체로 나옴.

    리액트에서는 이 객체를 '리액트 element'라고 부름.
    리액트가 이 객체를 해석해서 rendering을 함.

    리액트 element는 리액트로 화면을 구성하는데 있어서 가장 핵심적인 요소임.

     

    리액트 element를 함수형태로 만들어내면 JS문법을 작성할 때 custom 태그처럼 사용할 수 있음.

    import ReactDOM from 'react-dom/client';
    
    function Hello() {
    	return <h1>안녕 리액트</h1>;
    }
    
    const element = (
      <>
        <Hello />
        <Hello />
        <Hello />
      </>
    );
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(element);

     

    이 hello라고 하는 함수를 리액트 컴포넌트라고 함. 
    본격적으로 리액트를 개발할 때에는 리액트 element를 리액트 component로 만들어서 활용함.
    - 함수 이름 첫글자를 대문자로 쓰기
    - JSX 문법으로 만든 리액트 element를 리턴해야함.

     

    index.js

    import ReactDOM from 'react-dom/client';
    import App from './App';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<App />);

    App.js

    function App() {
        return <div>App 컴포넌트!</div>;
    }
    
    export default App;

    App.js

    import Dice from './Dice';
    
    function App() {
        return (
        <div>
            <Dice />
        </div>
        );
    }
    
    export default App;

     

    Dice.js

    import diceBlue01 from './assets/dice-blue-1.svg';  // 해당 경로의 이미지 파일 불러오기
    
    function Dice() {
        return <img src={diceBlue01} alt="주사위" />;
    }
    
    /*
    이렇게 파일 경로를 바로 작성하면 안 나옴.
    function Dice() {
        return <img src={'./assets/dice-blue-1.svg'} alt="주사위" />;
    }
    */
    
    export default Dice;

     

    index.js

    import ReactDOM from 'react-dom/client';
    import App from './App';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<App />);


    Props

    import Dice from './Dice';
    
    function App() {
        return (
            <div>
                <Dice color="blue" />
            </div>
        );
    }
    
    export default App;

    Element 탭의 html에서 color 속성을 어디서도 찾아볼 수 없음.

    이 color 속성은 html 태그가 아니라 리액트 컴포넌트에 지정해준 속성이기 때문임.

     

    리액트에서는 컴포넌트에 지정한 속성을 'props'라고 함. (properties)

    props는 컴포넌트에 전달된 속성을 모두 가리키므로, 각 속성은 prop이라고 부름.

    컴포넌트 태그에 지정해 준 속성은 하나의 객체 형태로 컴포넌트 함수의 첫 번째 파라미터로 전달됨.

     

    컴포넌트에 전달하는 prop값에 따라 렌더되는 모습을 다양하게 변경할 수 있음.

    import diceBlue01 from './assets/dice-blue-1.svg';
    import diceRed01 from './assets/dice-red-1.svg';
    
    function Dice(props) {
        const diceImg = props.color === 'red' ? diceRed01 : diceBlue01;
        return <img src={diceImg} alt="주사위" />;
    }
    
    export default Dice;

     

    props에 color와 num을 지정해줌.

    App에서는 num에 중괄호 씌우고 숫자 적어주기

    function Dice({ color = "blue", num = "1"}) {
        const src = DICE_IMAGES[color][num - 1];
        const alt = `${color} ${num}`;
        return <img src={src} alt={alt} />;
    }
    function App() {
        return (
            <div>
                <Dice color="red" 
                num={2}/>  
            </div>
        );
    }

    브라우저에서 직접 props의 속성값 변경하면 바로 렌더링됨.


    children

    · children : 컴포넌트의 자식들을 값으로 갖는 prop

    · JSX 문법으로 컴포넌트를 작성할 때 컴포넌트를 단일 태그가 아니라 여는 태그와 닫는 태그의 형태로 작성하면, 그 안에 작성된 코드가 children 값에 담기게 됨.
    · 리액트에서 단순히 보여지기만 하는 값을 다룰 때에는 어떤 prop을 만드는 것보다 children prop을 활용하는 게 코드를 직관적으로 구성하는 데 도움이 된다.

     

    Button.js

    function Button({ text }) {
        return <button>{text}</button>
    }
    
    export default Button;

    App.js

    function App() {
        return (
            <div>
                <div>
                    <Button text="던지기" />
                    <Button text="처음부터" />
                </div>
                <Dice color="red" 
                num={2}/>  
            </div>
        );
    }

     

    Button.js

    function Button({ children }) {
        return <button>{ children }</button>
    }
    
    export default Button;

    App.js

    function App() {
        return (
            <div>
                <div>
                    <Button>던지기</Button>
                    <Button>처음부터</Button>
                </div>
                <Dice color="red" 
                num={2}/>  
            </div>
        );
    }

     


    State

    State는 리액트에서 변수 같은 건데, state를 바꾸면 리액트가 알아서 화면을 새로 랜더링 해줌.
    리액트에서는 state 값 변경될 때마다 화면을 새롭게 렌더함.

     

    ex. '던지기' 버튼을 누르면 주사위 숫자가 바뀌면서 HTML 이미지 요소가 변함.
    - HTML로만 구현 : 주사위마다 HTML 페이지를 만들고 이동시킴
    - 자바스크립트 : HTML 요소 노드 속성만 바꿈 → 다른 이미지들 출력되도록 함
      (im src="./aaa/dice-  .svg">

     

    import { useState } from 'react';

     

    useState 함수는 파라미터로 초기값을 전달 받고 

    함수가 실행된 다음에는 배열의 형태로 요소 두 개를 리턴함.

    const [num, setNum] = useState(1);


    Destructuring 문법으로 작성함.
    • 첫번째  요소 : state 값 - 현재 변수의 값
    • 두번째 요소 : setter 함수 - 함수를 호출할 때 파라미터로 전달하는 값으로 state값이 변경됨

    state를 사용할 때는 변수에 새로운 값을 할당하면서 값을 변경하는게 아니라,
    반드시 이 setter 함수를 통해서만 값을 변경해야 함.

     

    App.js

    // 랜덤으로 숫자를 생성하는 함수
    function random(n) {
        return Math.ceil(Math.random() * n);
    }
    
    function App() {
        const [num, setNum] = useState(1);
    
        // num state를 랜덤하게 변경하는 함수
        const handleRollClick = () => {
            const nextNum = random(6);
            setNum(nextNum);
        };
    
        // num state를 1로 변경하는 함수 (처음부터)
        const handleClearClick = () => {
            setNum(1);
        };

     

    참조형 state

    · 배열은 기본형이 아니라 참조형임.
    · gameHistory값은 기록들을 갖는 배열 자체를 값으로 갖는 것이 아니라, 배열을 가리키고 있는 주소값을 가지고 있음.
    · 메소드를 이용해서 배열의 새로운 요소를 집어 넣더라도 history 변수가 갖고 있는 배열의 주소값은 변하지 않음.
    → 리액트는 state가 변경되었다고 판단하지 않음.

    // 랜덤으로 숫자를 생성하는 함수
    function random(n) {
        return Math.ceil(Math.random() * n);
    }
    
    function App() {
        const [num, setNum] = useState(1);  // 주사위 숫자
        const [sum, setSum] = useState(0);  // 총점
        const [gameHistory, setGameHistory] = useState([]);  // 게임 히스토리
    
        // num state를 랜덤하게 변경하는 함수 (던지기)
        const handleRollClick = () => {
            const nextNum = random(6);
            setNum(nextNum);
            setSum(sum + nextNum);  // 총점 : 주사위를 던질때마다 새로 계속해서 더함
            // gameHistory.push(nextNum); (잘못된 방법)
            setGameHistory([...gameHistory, nextNum]);
        };
    
        // num state를 1로 변경하는 함수 (처음부터)
        const handleClearClick = () => {
            setNum(1);
            setSum(0);
            setGameHistory([]);
        };

     

    리액트에서는 배열이나 객체와 같은 참조형 타입의 상태를 업데이트할 때, 
    · 이전 상태를 직접 수정 (X)
    · 새로운 복사본을 만들어서 업데이트 (O) (spread, slice)

    배열의 경우

    const originalArray = [1, 2, 3];
    
    // 새로운 배열을 만들어서 기존 배열에 4를 추가
    const newArray = [...originalArray, 4];
    
    // 기존 배열은 변경되지 않음
    console.log(originalArray); // [1, 2, 3]
    console.log(newArray); // [1, 2, 3, 4]

     

    객체의 경우

    const originalObject = { name: 'John', age: 25 };
    
    // 새로운 객체를 만들어서 age를 26으로 업데이트
    const newObject = { ...originalObject, age: 26 };
    
    // 기존 객체는 변경되지 않음
    console.log(originalObject); // { name: 'John', age: 25 }
    console.log(newObject); // { name: 'John', age: 26 }

    컴포넌트 재사용 & 코드 정리

    컴포넌트 장점

    component 부품
    · 반복적인 일이 줄어든다
    · 버그가 생겼을 때 문제되는 곳만 고치면 됨
    · 일을 쉽게 나눌 수 있음 → 협업이 편리함

     

    컴포넌트를 재사용하여 똑같은 부분을 하나 더 추가함

    → 나, 상대 디자인 따로 해줄 수 있음

     

    Board.js

                <div>
                    <h2>{name}</h2>
                    <Dice color={color} num={num} />  
                    <h2>총점</h2>
                    <p>{sum}</p>
                    <h2>기록</h2>
                    <p>{gameHistory.join(' → ')}</p>
                </div>

     

    App.js

    import Board from './Board';
    
    function App() {
        return (
            <div>
              <Board name="나" color="blue" />
              <Board name="상대" color="red" />
            </div>
        );
    }
    
    export default App;

     

    현재 코드에 state를 많이 사용하는 것을 볼 수 있음
    · 현재 주사위의 숫자값 = 기록의 마지막 값
    · 총점 = 기록이 갖고 있는 모든 숫자의 합
    → 주사위 던진 기록만 있으면 총점이나 현재 주사위의 숫자값은 충분히 구할 수 있음


    리액트가 렌더링하는 방식

    Virtual DOM
    · 기본적으로 HTML 요소들은 DOM 트리라고 하는 자료구조로 저장이 되어 있음.
    · 리액트 내부에서는 DOM트리를 본따서 만든 virtual DOM을 사용함.
    · 리액트는 그 모습을 실제 DOM 트리에 바로 반영하는 것이 아니라 일단 Virtual DOM에다가 적용함.
    · 화면을 바꿀 준비만 하고 실제로는 아직 반영하지 않음.
    · state변경 전 virtual dom과 변경 후의 virtual dom을 비교함.
    바뀐 부분만 찾아낸 다음 각각에 해당하는 실제 DOM 노드를 변경함.

    장점
    ·  개발자가 직접 DOM노드를 신경쓸 필요가 없음. 무슨 데이터를 어떻게 보여줄지만 신경쓰면 됨.
    ·  변경 사항들을 리액트가 적당히 모아서 처리할 수 있음.

    리액트를 사용하면 virtual dom으로 효율적인 화면 처리가 가능하다.


    인라인 스타일

    · HTML style 속성처럼 리액트에서도 인라인 스타일 적용이 가능함.
    · 문자열이 아닌 객체로 style 속성값을 지정해주어야 함.

    · 속성 쓸 때는 '-' 빼고 카멜 표기법으로 써주기 (ex. backgroundColor)

    const style = {
        속성: '값',
    };

    button.js

    const style = {
        backgroundColor: 'pink',  // 카멜케이스
    };
    
    function Button({ children, onClick }) {
        return <button style={style} onClick = {onClick}>
                { children }
            </button>
    }
    
    export default Button;

    CSS 클래스네임

    index.js

    import './index.css'

     

    index.css

    body {
        background-color: #191f2c;
        color: #fff;
    }

     

    Elements 탭에서 head 태그 안에 style 태그로 작성한 스타일 코드가 들어있는 걸 확인할 수 있음.
    자바스크립트 파일에서 CSS 파일을 import 하게 되면 head 태그 안에 style 태그가 자동으로 작성됨.

     

    className은 카멜표기법으로 문자열로 써주기

           <div>
                <Button className="App-button" color="blue" onClick = {handleRollClick}>던지기</Button>
                <Button className="App-button" color="red" onClick = {handleClearClick}>처음부터</Button>
            </div>
    <button className="HandButton" onClick={handleClick}>
      <HandIcon className="HandButtonIcon" value={value} />
    </button>

     

    CSS 스타일 속성 중에는 여백을 주는 margin과 같은 요소 내부보다는 외부에 영향을 주는 속성이 있음.
    이런 속성은 컴포넌트 내부보다는 외부에서 정리를 하는 것이 좋음.

     

    Button.js

    .App .App-button {
        margin: 6px;
    }

    App.js

    import './App.css';
    
            <div>
                <Button className="App-button" color="blue" onClick = {handleRollClick}>던지기</Button>
                <Button className="App-button" color="red" onClick = {handleClearClick}>처음부터</Button>
            </div>

    왜 버튼 스타일을 굳이 부모컴포넌트에서 스타일을 지정해주는 것일까?

    • App 컴포넌트 관럼에서는 자식 요소들 간의 여백을 조절할 수 있어서 직관적으로 스타일을 다룰 수 있게 됨.
    • 버튼 내부 스타일은 Button.js에서 다루는 게 좋겠지만, margin과 같이 요소의 외부적으로 영향을 미칠 스타일 속성은 더 상위 컴포넌트에서 다루는 것이 좋음.

     

     props에서 className prop을 전달받을 수 있도록 하면 재사용성이 훨씬 더 높아진다

    function Nav({ className = '' }) {
      // className 속성을 받아오고, 만약 속성이 제공되지 않았을 경우 빈 문자열로 초기화
      const classNames = `Nav ${className}`;
      // ...
    
      return (
        <ul className="Nav">
          {/* ... */}
        </ul>
      );
    }

     

    className 라이브러리 사용

    <Button isPending={true} color="red" size="large" invert={false}>
      Click me
    </Button>
    <button className="Button pending red large">
      Click me
    </button>

     

    import classNames from 'classnames';
    
    function Button({ isPending, color, size, invert, children }) {
      return (
        <button
          className={classNames(
            'Button',
            isPending && 'pending',  // isPending이 true일때만 pending이 실행됨
            color,
            size,
            invert && 'invert',
          )}>
         { children }
       </button >
      );
    }
    
    export default Button;

     

     

    https://www.npmjs.com/package/classnames

     

    classnames

    A simple utility for conditionally joining classNames together. Latest version: 2.5.1, last published: 20 days ago. Start using classnames in your project by running `npm i classnames`. There are 41795 other projects in the npm registry using classnames.

    www.npmjs.com


    3. React 배포하기

    빌드하기

    · 배포 : 작성한 소스코드를 다른 사람이 쓸 수 있는 형태로 만드는 것

    · 빌드 : 브라우저가 해석할 수 있고, 웹 서버가 사용하기 좋도록 만듣는 과정 

    - JSX 문법 코드는 웹브라우저가 그대로 해석할 수 없음.
    - 순수한 JS 코드로 변환한 다음 웹 서버에서 제공되어야 함.

    실행 중인 서버 종료하기

    ctrl + c

     

    개발된 프로젝트 빌드하기

    npm run build

     

    빌드가 완료되면 build라는 폴더가 새롭게 생겨난 게 보임.

    build 폴더 안에 있는 파일들을 웹 서버로 제공하면 프로젝트가 배포되는 것임.

     

    배포하기 전 server라는 프로그램으로 간단한 서버를 실행하기

     

    serve 프로그램 설치하기

    npm install serve

     

    빌드한 것 로컬에서 실행하기

    serve 프로그램이 실행됨.

    npx serve build

     

    서버가 켜지면 터미널에 접속할 수 있는 주소가 나타남.

    해당 주소는 인터넷에 공개된 것이 아니라서 본인 컴퓨터에서만 확인할 수 있음.

     

    종료하려면 Ctrl + C를 누르면 된다.


    웹사이트 배포하기(AWS S3)

    클라우드 컴퓨팅
    · 개발에 필요한 기능을 클라우드로 사용하는 서비스
    · 요즘 가장 많이 사용하는 AWS(Amazon Web Service)임.

    • S3 : 구글 드라이브 같은 저장소
    • 버킷 : S3에서 파일을 모아두는 큰 단위

    https://aws.amazon.com/ko/

     

    클라우드 서비스 | 클라우드 컴퓨팅 솔루션| Amazon Web Services

    Amazon Q로 일하는 신세계에 오신 것을 환영합니다

    aws.amazon.com

     

     

     

    버킷이름은 중복되지 않도록 지정해야함.

     

    모든 퍼블릭 액세스 차단 해제 후 마지막 경고문구에 체크

     

    만들기 후 버킷 목록 확인

     

    옵션 설정

    정적 웹사이트 호스팅 (파일을 웹사이트로 만들기) - 편집 - 활성화

     

    오류문서 : 파일이 없는 경로로 접속하는 경우 보여줄 HTML 파일

    index.html으로 지정해주면 경로처리를 리액트로 할 수 있는 장점이 있음.

     

    권한 메뉴

    버킷 정책 편집 - 정책 생성기

     

     

    Select Type of Policy : S3 Bucket Policy

    Principal : * (정책을 모든 사용자에게 적용할 것이라는 의미)

    AWS Service : Amazon S3

    Actions : GetObject

    ARN : 버킷 ARN 복사해서 붙여 넣기 */ 

    (*/ : 이 버킷에 있는 모든 파일에다 이 정책을 적용한다는 의미)

    Add Statement 버튼 누르기

    Generate Policy 버튼 누르기

    JSON 문자열로 정책이 만들어짐

     

    우리가 배포할 버킷 정책에 복사 붙여넣기 함.

    변경사항 저장 버튼 클릭

    빌드 폴더 안에 있는 파일을 모두 업로드한 후, 업로드 버튼 클릭

     

    커스텀 도메인 달기 (AWS Route53)

    AWS 호스팅 영역에서 기본 값으로 생성된 레코드 중에서 NS 유형에 해당하는 값/트래픽 라우팅 대상의 값을 복사해커스텀 네임서버로 등록한다.


    브라우저가 리액트 알아 듣는 원리

    웹 브라우저에서는 JSX 문법을 사용할 수 없다.

     

    JSX가 어떻게 순수 자바스크립트로 번역되는지 확인할 수 있다.

    https://babeljs.io/

     

    Babel · Babel

    The compiler for next generation JavaScript

    babeljs.io

     

    트랜스파일링, 트랜스파일러(Transpilling, Transpiler)

     

    번들링(Bundling)

    자바스크립트 파일이 압축되어 있는 것을 확인할 수 있음.

    이런 묶음 파일을 번들이라고 함.

     

    요약

    JSX로 작성한 파일들은 트랜스파일링을 통해 순수 자바스크립트로 번역되고,

    번역된 코드들은 번들링을 통해 웹브라우저가 다운받기 좋도록 묶음으로 만들어져야 실행이 됨.

    728x90